commit 1b96a031d2b7f7275fd39c4597e32ffef21371e3 Author: Tha_14 Date: Thu Feb 22 21:43:11 2024 +0200 Initial commit diff --git a/Antidote.xcodeproj/project.pbxproj b/Antidote.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7f1bf66 --- /dev/null +++ b/Antidote.xcodeproj/project.pbxproj @@ -0,0 +1,3062 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1105B18D1EA09B1A0035B213 /* ChatFauxOfflineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1105B18C1EA09B1A0035B213 /* ChatFauxOfflineHeaderView.swift */; }; + 1105B18E1EA09B1A0035B213 /* ChatFauxOfflineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1105B18C1EA09B1A0035B213 /* ChatFauxOfflineHeaderView.swift */; }; + 1105B18F1EA09B1A0035B213 /* ChatFauxOfflineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1105B18C1EA09B1A0035B213 /* ChatFauxOfflineHeaderView.swift */; }; + 1109019B1D83417500BC5751 /* PinInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1109019A1D83417500BC5751 /* PinInputView.swift */; }; + 1109019C1D83417500BC5751 /* PinInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1109019A1D83417500BC5751 /* PinInputView.swift */; }; + 110E078F1EB0756C00B2CA9D /* ResultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110E078E1EB0756C00B2CA9D /* ResultsExtension.swift */; }; + 110E07901EB0756C00B2CA9D /* ResultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110E078E1EB0756C00B2CA9D /* ResultsExtension.swift */; }; + 110E07911EB0756C00B2CA9D /* ResultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110E078E1EB0756C00B2CA9D /* ResultsExtension.swift */; }; + 110E425C1C00DC5E001A3CA2 /* StaticTableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110E425B1C00DC5E001A3CA2 /* StaticTableController.swift */; }; + 110E49E01BF925FD00D1FE6F /* ActiveSessionNavigationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110E49DF1BF925FD00D1FE6F /* ActiveSessionNavigationCoordinator.swift */; }; + 111782781DC52BDB000C1721 /* OCTManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782771DC52BDB000C1721 /* OCTManagerMock.swift */; }; + 111782791DC52BDB000C1721 /* OCTManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782771DC52BDB000C1721 /* OCTManagerMock.swift */; }; + 1117827B1DC52C3A000C1721 /* OCTSubmanagerBootstrapMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117827A1DC52C3A000C1721 /* OCTSubmanagerBootstrapMock.swift */; }; + 1117827C1DC52C3A000C1721 /* OCTSubmanagerBootstrapMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117827A1DC52C3A000C1721 /* OCTSubmanagerBootstrapMock.swift */; }; + 1117827E1DC52CBA000C1721 /* OCTSubmanagerCallsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117827D1DC52CBA000C1721 /* OCTSubmanagerCallsMock.swift */; }; + 1117827F1DC52CBA000C1721 /* OCTSubmanagerCallsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117827D1DC52CBA000C1721 /* OCTSubmanagerCallsMock.swift */; }; + 1117828D1DC531AD000C1721 /* OCTSubmanagerChatsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117828C1DC531AD000C1721 /* OCTSubmanagerChatsMock.swift */; }; + 1117828E1DC531AD000C1721 /* OCTSubmanagerChatsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117828C1DC531AD000C1721 /* OCTSubmanagerChatsMock.swift */; }; + 111782931DC53371000C1721 /* OCTSubmanagerFilesMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782921DC53371000C1721 /* OCTSubmanagerFilesMock.swift */; }; + 111782941DC53371000C1721 /* OCTSubmanagerFilesMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782921DC53371000C1721 /* OCTSubmanagerFilesMock.swift */; }; + 111782961DC53458000C1721 /* OCTSubmanagerFriendsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782951DC53458000C1721 /* OCTSubmanagerFriendsMock.swift */; }; + 111782971DC53458000C1721 /* OCTSubmanagerFriendsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782951DC53458000C1721 /* OCTSubmanagerFriendsMock.swift */; }; + 111782991DC53545000C1721 /* OCTSubmanagerObjectsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782981DC53545000C1721 /* OCTSubmanagerObjectsMock.swift */; }; + 1117829A1DC53545000C1721 /* OCTSubmanagerObjectsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782981DC53545000C1721 /* OCTSubmanagerObjectsMock.swift */; }; + 1117829C1DC5363C000C1721 /* OCTSubmanagerUserMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117829B1DC5363C000C1721 /* OCTSubmanagerUserMock.swift */; }; + 1117829D1DC5363C000C1721 /* OCTSubmanagerUserMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117829B1DC5363C000C1721 /* OCTSubmanagerUserMock.swift */; }; + 1117829F1DC53AB4000C1721 /* ToxFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117829E1DC53AB4000C1721 /* ToxFactory.swift */; }; + 111782A01DC53AB4000C1721 /* ToxFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117829E1DC53AB4000C1721 /* ToxFactory.swift */; }; + 111782AA1DC64391000C1721 /* ScreenshotsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782A91DC64391000C1721 /* ScreenshotsUITests.swift */; }; + 111782BB1DC643A3000C1721 /* OCTManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782771DC52BDB000C1721 /* OCTManagerMock.swift */; }; + 111782BC1DC643A3000C1721 /* OCTSubmanagerBootstrapMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117827A1DC52C3A000C1721 /* OCTSubmanagerBootstrapMock.swift */; }; + 111782BD1DC643A3000C1721 /* OCTSubmanagerCallsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117827D1DC52CBA000C1721 /* OCTSubmanagerCallsMock.swift */; }; + 111782BE1DC643A3000C1721 /* OCTSubmanagerChatsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117828C1DC531AD000C1721 /* OCTSubmanagerChatsMock.swift */; }; + 111782C01DC643A3000C1721 /* OCTSubmanagerFilesMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782921DC53371000C1721 /* OCTSubmanagerFilesMock.swift */; }; + 111782C11DC643A3000C1721 /* OCTSubmanagerFriendsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782951DC53458000C1721 /* OCTSubmanagerFriendsMock.swift */; }; + 111782C21DC643A3000C1721 /* OCTSubmanagerObjectsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111782981DC53545000C1721 /* OCTSubmanagerObjectsMock.swift */; }; + 111782C31DC643A3000C1721 /* OCTSubmanagerUserMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117829B1DC5363C000C1721 /* OCTSubmanagerUserMock.swift */; }; + 111782D21DC64796000C1721 /* BaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB651C1F5694006AA9E5 /* BaseCell.swift */; }; + 111782D31DC64796000C1721 /* BaseCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB671C1F569A006AA9E5 /* BaseCellModel.swift */; }; + 111782D41DC64796000C1721 /* ChatBaseTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F276AD1D3EBEF700C613AA /* ChatBaseTextCell.swift */; }; + 111782D51DC64796000C1721 /* ChatBaseTextCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F276BA1D3EBF3000C613AA /* ChatBaseTextCellModel.swift */; }; + 111782D61DC64796000C1721 /* ChatEditable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1136605B1CDE5D5E0092C27A /* ChatEditable.swift */; }; + 111782D71DC64796000C1721 /* ChatGenericFileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E711CA580ED008C097E /* ChatGenericFileCell.swift */; }; + 111782D81DC64796000C1721 /* ChatGenericFileCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E741CA5811C008C097E /* ChatGenericFileCellModel.swift */; }; + 111782D91DC64796000C1721 /* ChatIncomingCallCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1166349F1C6E8D0F0072C980 /* ChatIncomingCallCell.swift */; }; + 111782DA1DC64796000C1721 /* ChatIncomingCallCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634A81C6E8D1A0072C980 /* ChatIncomingCallCellModel.swift */; }; + 111782DB1DC64796000C1721 /* ChatIncomingFileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCAB1CA1B398000CD310 /* ChatIncomingFileCell.swift */; }; + 111782DC1DC64796000C1721 /* ChatIncomingFileCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCB41CA1B3AE000CD310 /* ChatIncomingFileCellModel.swift */; }; + 111782DD1DC64796000C1721 /* ChatIncomingTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B252F91C4A614D0068F47C /* ChatIncomingTextCell.swift */; }; + 111782DE1DC64796000C1721 /* ChatListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE0BDEF1C4522BF00DCE357 /* ChatListCell.swift */; }; + 111782DF1DC64796000C1721 /* ChatListCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE0BDF21C4522C800DCE357 /* ChatListCellModel.swift */; }; + 111782E01DC64796000C1721 /* ChatMovableDateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253011C4A61D00068F47C /* ChatMovableDateCell.swift */; }; + 111782E11DC64796000C1721 /* ChatMovableDateCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253031C4A61D50068F47C /* ChatMovableDateCellModel.swift */; }; + 111782E21DC64796000C1721 /* ChatOutgoingCallCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634AB1C6F407D0072C980 /* ChatOutgoingCallCell.swift */; }; + 111782E31DC64796000C1721 /* ChatOutgoingCallCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634AE1C6F40820072C980 /* ChatOutgoingCallCellModel.swift */; }; + 111782E41DC64796000C1721 /* ChatOutgoingFileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E621CA57B14008C097E /* ChatOutgoingFileCell.swift */; }; + 111782E51DC64796000C1721 /* ChatOutgoingFileCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E6B1CA57B19008C097E /* ChatOutgoingFileCellModel.swift */; }; + 111782E61DC64796000C1721 /* ChatOutgoingTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B252FD1C4A615E0068F47C /* ChatOutgoingTextCell.swift */; }; + 111782E71DC64796000C1721 /* ChatProgressBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCDC1CA1FD55000CD310 /* ChatProgressBridge.swift */; }; + 111782E81DC64796000C1721 /* ChatProgressProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCD61CA1FC5B000CD310 /* ChatProgressProtocol.swift */; }; + 111782E91DC64796000C1721 /* FriendListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB691C1F5C28006AA9E5 /* FriendListCell.swift */; }; + 111782EA1DC64796000C1721 /* FriendListCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB6B1C1F5C31006AA9E5 /* FriendListCellModel.swift */; }; + 111782EB1DC64796000C1721 /* StaticTableAvatarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4861C10D122005AC8C6 /* StaticTableAvatarCell.swift */; }; + 111782EC1DC64796000C1721 /* StaticTableAvatarCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4841C10D119005AC8C6 /* StaticTableAvatarCellModel.swift */; }; + 111782ED1DC64796000C1721 /* StaticTableBaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C04BB6C1C17389200F58488 /* StaticTableBaseCell.swift */; }; + 111782EE1DC64796000C1721 /* StaticTableBaseCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4781C0E478D005AC8C6 /* StaticTableBaseCellModel.swift */; }; + 111782EF1DC64796000C1721 /* StaticTableButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4801C0FA0A4005AC8C6 /* StaticTableButtonCell.swift */; }; + 111782F01DC64796000C1721 /* StaticTableButtonCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A47A1C0F6178005AC8C6 /* StaticTableButtonCellModel.swift */; }; + 111782F11DC64796000C1721 /* StaticTableChatButtonsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116513211C2C74940066AF06 /* StaticTableChatButtonsCell.swift */; }; + 111782F21DC64796000C1721 /* StaticTableChatButtonsCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116513231C2C749E0066AF06 /* StaticTableChatButtonsCellModel.swift */; }; + 111782F31DC64796000C1721 /* StaticTableDefaultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115DFB891C177F5C00F18DB5 /* StaticTableDefaultCell.swift */; }; + 111782F41DC64796000C1721 /* StaticTableDefaultCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115DFB951C177F6500F18DB5 /* StaticTableDefaultCellModel.swift */; }; + 111782F51DC64796000C1721 /* StaticTableInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634DF1C7B8D770072C980 /* StaticTableInfoCell.swift */; }; + 111782F61DC64796000C1721 /* StaticTableInfoCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634E81C7B8D7C0072C980 /* StaticTableInfoCellModel.swift */; }; + 111782F71DC64796000C1721 /* StaticTableMultiChoiceButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634EE1C7B94530072C980 /* StaticTableMultiChoiceButtonCell.swift */; }; + 111782F81DC64796000C1721 /* StaticTableMultiChoiceButtonCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634F11C7B945A0072C980 /* StaticTableMultiChoiceButtonCellModel.swift */; }; + 111782F91DC64796000C1721 /* StaticTableSelectableCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A47C1C0F632C005AC8C6 /* StaticTableSelectableCellModel.swift */; }; + 111782FA1DC64796000C1721 /* StaticTableSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253FA1C52C0A10068F47C /* StaticTableSwitchCell.swift */; }; + 111782FB1DC64796000C1721 /* StaticTableSwitchCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253FD1C52C0AA0068F47C /* StaticTableSwitchCellModel.swift */; }; + 111782FC1DC6479E000C1721 /* AlertAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F03411CB31DD60009ABE1 /* AlertAudioPlayer.swift */; }; + 111782FD1DC6479E000C1721 /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B2534E1C4AD2550068F47C /* AudioPlayer.swift */; }; + 111782FE1DC6479E000C1721 /* AvatarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4881C15FB2B005AC8C6 /* AvatarManager.swift */; }; + 111782FF1DC6479E000C1721 /* ChatListTableManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C2EF3791C4E2DDE006E7AB1 /* ChatListTableManager.swift */; }; + 111783001DC6479E000C1721 /* FilePreviewControllerDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E311CA2FBD1008C097E /* FilePreviewControllerDataSource.swift */; }; + 111783011DC6479E000C1721 /* FriendListDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDC091A1C34081900DC0D63 /* FriendListDataSource.swift */; }; + 111783021DC6479E000C1721 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1160B67D1D5F9993002DF75B /* KeychainManager.swift */; }; + 111783031DC6479E000C1721 /* NotificationObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6B1E1C510A1E00A1ECB5 /* NotificationObject.swift */; }; + 111783041DC6479E000C1721 /* ProfileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDDB2051BD5376200B65D79 /* ProfileManager.swift */; }; + 111783051DC6479E000C1721 /* ProfileSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C1FEB281D8C10EB008C2ADE /* ProfileSettings.swift */; }; + 111783061DC6479E000C1721 /* Results.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FC10561D32E54A00CE863E /* Results.swift */; }; + 111783071DC6479E000C1721 /* ResultsChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FC10591D32EED000CE863E /* ResultsChange.swift */; }; + 111783081DC6479E000C1721 /* TabBarAbstractItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253651C4BAD3C0068F47C /* TabBarAbstractItem.swift */; }; + 111783091DC6479E000C1721 /* TabBarBadgeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253691C4BAD560068F47C /* TabBarBadgeItem.swift */; }; + 1117830A1DC6479E000C1721 /* TabBarProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B2536C1C4BAD5D0068F47C /* TabBarProfileItem.swift */; }; + 1117830B1DC6479E000C1721 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1173F06D1BC5D9BA00B88B7B /* Theme.swift */; }; + 1117830C1DC6479E000C1721 /* ToxFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1117829E1DC53AB4000C1721 /* ToxFactory.swift */; }; + 1117830D1DC6479E000C1721 /* UserDefaultsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8D71BC922EB00B91107 /* UserDefaultsManager.swift */; }; + 1117830E1DC647BD000C1721 /* KeyboardNotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112422651BDD2032004D7926 /* KeyboardNotificationController.swift */; }; + 1117830F1DC647BD000C1721 /* LaunchPlaceholderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1160B6891D5FD8DE002DF75B /* LaunchPlaceholderController.swift */; }; + 111783101DC647BD000C1721 /* LaunchPlaceholderBoard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 11DDEAFC1D5FD9FE0000E2BE /* LaunchPlaceholderBoard.storyboard */; }; + 111783111DC647BD000C1721 /* PortraitNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C07151C1BCD2E5B003A27B5 /* PortraitNavigationController.swift */; }; + 111783121DC647C1000C1721 /* LoginBaseController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8E61BC929C500B91107 /* LoginBaseController.swift */; }; + 111783131DC647C1000C1721 /* LoginChoiceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8E81BC929F700B91107 /* LoginChoiceController.swift */; }; + 111783141DC647C1000C1721 /* LoginCreateAccountController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CBBC7C01BDFC62700099A5E /* LoginCreateAccountController.swift */; }; + 111783151DC647C1000C1721 /* LoginCreatePasswordController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112950861D63AFD800C9CE0F /* LoginCreatePasswordController.swift */; }; + 111783161DC647C1000C1721 /* LoginFormController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C07151E1BCD501D003A27B5 /* LoginFormController.swift */; }; + 111783171DC647C1000C1721 /* LoginGenericCreateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112950831D63AFCE00C9CE0F /* LoginGenericCreateController.swift */; }; + 111783181DC647C1000C1721 /* LoginLogoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8F21BC9880000B91107 /* LoginLogoController.swift */; }; + 111783191DC647CF000C1721 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0ED91BC584D900F3DA5B /* AppCoordinator.swift */; }; + 1117831A1DC647CF000C1721 /* CoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EDD1BC58DCC00F3DA5B /* CoordinatorProtocol.swift */; }; + 1117831B1DC647CF000C1721 /* TopCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116DCE311CAAF47100B693EC /* TopCoordinatorProtocol.swift */; }; + 1117831C1DC647D4000C1721 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EE81BC5A66D00F3DA5B /* LoginCoordinator.swift */; }; + 1117831D1DC647D4000C1721 /* LoginCreateAccountCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1129508A1D6851EB00C9CE0F /* LoginCreateAccountCoordinator.swift */; }; + 1117831E1DC647D9000C1721 /* ActiveSessionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EE61BC59B1400F3DA5B /* ActiveSessionCoordinator.swift */; }; + 1117831F1DC647D9000C1721 /* ActiveSessionNavigationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110E49DF1BF925FD00D1FE6F /* ActiveSessionNavigationCoordinator.swift */; }; + 111783201DC647D9000C1721 /* AutomationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11771E2B1CA5C3F200EC259E /* AutomationCoordinator.swift */; }; + 111783211DC647D9000C1721 /* CallCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1180FD9F1C384E29005F3EA1 /* CallCoordinator.swift */; }; + 111783221DC647D9000C1721 /* ChatsTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EF21BC5A6AF00F3DA5B /* ChatsTabCoordinator.swift */; }; + 111783231DC647D9000C1721 /* FriendsTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EEA1BC5A68600F3DA5B /* FriendsTabCoordinator.swift */; }; + 111783241DC647D9000C1721 /* NotificationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C2EF3701C4D4D5C006E7AB1 /* NotificationCoordinator.swift */; }; + 111783251DC647D9000C1721 /* PinAuthorizationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3271861D79C19C00347490 /* PinAuthorizationCoordinator.swift */; }; + 111783261DC647D9000C1721 /* ProfileTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EEC1BC5A69000F3DA5B /* ProfileTabCoordinator.swift */; }; + 111783271DC647D9000C1721 /* RunningCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CB1F9501D58CA4000105858 /* RunningCoordinator.swift */; }; + 111783281DC647D9000C1721 /* SettingsTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EF01BC5A6A500F3DA5B /* SettingsTabCoordinator.swift */; }; + 111783291DC647DF000C1721 /* InterfaceIdiom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253A31C4EA8040068F47C /* InterfaceIdiom.swift */; }; + 1117832A1DC647DF000C1721 /* UserStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFCD0A1C2745230046BD94 /* UserStatus.swift */; }; + 1117832B1DC647E4000C1721 /* NSDateFormatterExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1180FD9D1C38497A005F3EA1 /* NSDateFormatterExtension.swift */; }; + 1117832C1DC647E4000C1721 /* NSTimerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253B61C503FA10068F47C /* NSTimerExtension.swift */; }; + 1117832D1DC647E4000C1721 /* NSURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116DCE3A1CAAF85300B693EC /* NSURLExtension.swift */; }; + 1117832E1DC647E4000C1721 /* OCTManagerConfigurationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B9C6821BD598FC0083C2A5 /* OCTManagerConfigurationExtension.swift */; }; + 1117832F1DC647E4000C1721 /* OCTSubmanagerObjectsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FC105C1D32F29700CE863E /* OCTSubmanagerObjectsExtension.swift */; }; + 111783301DC647E4000C1721 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C7475C41BC698510098B1A4 /* StringExtension.swift */; }; + 111783311DC647E4000C1721 /* UIAlertControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11770D761CBC1C7A00D34D6E /* UIAlertControllerExtension.swift */; }; + 111783321DC647E4000C1721 /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634B11C7087A80072C980 /* UIApplicationExtension.swift */; }; + 111783331DC647E4000C1721 /* UIColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119AD1CB1BDEDD9C000C5CB8 /* UIColorExtension.swift */; }; + 111783341DC647E4000C1721 /* UIFontExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F03321CB2E9C20009ABE1 /* UIFontExtension.swift */; }; + 111783351DC647E4000C1721 /* UIImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8EF1BC982FF00B91107 /* UIImageExtension.swift */; }; + 111783361DC647E4000C1721 /* UIViewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8F51BC98F8300B91107 /* UIViewControllerExtension.swift */; }; + 111783371DC647F8000C1721 /* ErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119AD1C91BDED6E9000C5CB8 /* ErrorHandling.swift */; }; + 111783381DC647F8000C1721 /* ExceptionHandling.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CDC092B1C34102F00DC0D63 /* ExceptionHandling.m */; }; + 111783391DC647F8000C1721 /* HelperFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E640771C2DC15400D24C6D /* HelperFunctions.swift */; }; + 1117833A1DC647F8000C1721 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EE01BC591FC00F3DA5B /* Logger.swift */; }; + 1117833B1DC647FF000C1721 /* Reach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11771E351CA5C6E900EC259E /* Reach.swift */; }; + 1117833C1DC64810000C1721 /* BubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253131C4A79A50068F47C /* BubbleView.swift */; }; + 1117833D1DC64810000C1721 /* CallButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112E4EE41C676553004312CF /* CallButton.swift */; }; + 1117833E1DC64810000C1721 /* ChatInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A21EFC1C45BDF200E80A89 /* ChatInputView.swift */; }; + 1117833F1DC64810000C1721 /* ChatPrivateTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253551C4AEA400068F47C /* ChatPrivateTitleView.swift */; }; + 111783401DC64810000C1721 /* CopyLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634D81C70F1260072C980 /* CopyLabel.swift */; }; + 111783411DC64810000C1721 /* ExtendedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CBBC7CC1BDFC6C600099A5E /* ExtendedTextField.swift */; }; + 111783421DC64810000C1721 /* FullscreenPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B9C6961BD80B080083C2A5 /* FullscreenPicker.swift */; }; + 111783431DC64810000C1721 /* ImageViewWithStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFCD0C1C27488E0046BD94 /* ImageViewWithStatus.swift */; }; + 111783441DC64810000C1721 /* IncompressibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253AD1C4EC47D0068F47C /* IncompressibleView.swift */; }; + 111783451DC64810000C1721 /* iPadFriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113AD8561CA9C70F00D981B5 /* iPadFriendsButton.swift */; }; + 111783461DC64810000C1721 /* iPadNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113AD8491CA97A3000D981B5 /* iPadNavigationView.swift */; }; + 111783471DC64810000C1721 /* LoadingImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E771CA5819B008C097E /* LoadingImageView.swift */; }; + 111783481DC64810000C1721 /* NotificationWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116513251C2D636A0066AF06 /* NotificationWindow.swift */; }; + 111783491DC64810000C1721 /* PinInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1109019A1D83417500BC5751 /* PinInputView.swift */; }; + 1117834A1DC64810000C1721 /* ProgressCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCBF1CA1DB05000CD310 /* ProgressCircleView.swift */; }; + 1117834B1DC64810000C1721 /* QRScannerAimView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113D564C1C2F437B00B3D3E8 /* QRScannerAimView.swift */; }; + 1117834C1DC64810000C1721 /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8EC1BC9801200B91107 /* RoundedButton.swift */; }; + 1117834D1DC64810000C1721 /* StaticBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFCD101C2A02350046BD94 /* StaticBackgroundView.swift */; }; + 1117834E1DC64810000C1721 /* UserStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFCD0E1C27499D0046BD94 /* UserStatusView.swift */; }; + 1117834F1DC64810000C1721 /* ViewPassingGestures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E6406C1C2D6C3500D24C6D /* ViewPassingGestures.swift */; }; + 111783691DC64D50000C1721 /* QuickLookPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E3D1CA30C13008C097E /* QuickLookPreviewController.swift */; }; + 1117836A1DC64D5A000C1721 /* CallIncomingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112E4ED51C668C02004312CF /* CallIncomingController.swift */; }; + 1117836B1DC64D68000C1721 /* CallBaseController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114962941C64051A001E5435 /* CallBaseController.swift */; }; + 1117836C1DC64D75000C1721 /* AddFriendController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E640751C2DBAE200D24C6D /* AddFriendController.swift */; }; + 1117836D1DC64D75000C1721 /* CallActiveController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112E4EDE1C675C58004312CF /* CallActiveController.swift */; }; + 1117836E1DC64D75000C1721 /* ChangeAutodownloadImagesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E7A1CA5BF1F008C097E /* ChangeAutodownloadImagesController.swift */; }; + 1117836F1DC64D75000C1721 /* ChangePasswordController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114962911C5E1BE0001E5435 /* ChangePasswordController.swift */; }; + 111783701DC64D75000C1721 /* ChangePinTimeoutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116046181D8D434B002287C8 /* ChangePinTimeoutController.swift */; }; + 111783711DC64D75000C1721 /* ChangeUserStatusController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1149626B1C5CE3DF001E5435 /* ChangeUserStatusController.swift */; }; + 111783721DC64D75000C1721 /* ChatListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE0BDE51C45229500DCE357 /* ChatListController.swift */; }; + 111783731DC64D75000C1721 /* ChatPrivateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A21EFA1C45BDB100E80A89 /* ChatPrivateController.swift */; }; + 111783741DC64D75000C1721 /* EnterPinController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C19367D1D79CF7E005EA0B2 /* EnterPinController.swift */; }; + 111783751DC64D75000C1721 /* FAQController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11AEBE701DA1876F00D04B59 /* FAQController.swift */; }; + 111783761DC64D75000C1721 /* FriendCardController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116513181C2C6C980066AF06 /* FriendCardController.swift */; }; + 111783771DC64D75000C1721 /* FriendListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB471C1F4164006AA9E5 /* FriendListController.swift */; }; + 111783781DC64D75000C1721 /* FriendRequestController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634EB1C7B90F20072C980 /* FriendRequestController.swift */; }; + 111783791DC64D75000C1721 /* FriendSelectController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11770D691CBC120C00D34D6E /* FriendSelectController.swift */; }; + 1117837A1DC64D75000C1721 /* PrimaryIpadController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6AA11C4E679100A1ECB5 /* PrimaryIpadController.swift */; }; + 1117837B1DC64D75000C1721 /* ProfileDetailsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114962741C5CEB0B001E5435 /* ProfileDetailsController.swift */; }; + 1117837C1DC64D75000C1721 /* ProfileMainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C9A07001BE793BA0003D6C7 /* ProfileMainController.swift */; }; + 1117837D1DC64D75000C1721 /* QRScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113D56551C2F459E00B3D3E8 /* QRScannerController.swift */; }; + 1117837E1DC64D75000C1721 /* QRViewerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB411C1E2075006AA9E5 /* QRViewerController.swift */; }; + 1117837F1DC64D75000C1721 /* SettingsAboutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6B4E1C528AAA00A1ECB5 /* SettingsAboutController.swift */; }; + 111783801DC64D75000C1721 /* SettingsAdvancedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6B511C528AB600A1ECB5 /* SettingsAdvancedController.swift */; }; + 111783811DC64D75000C1721 /* SettingsMainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6B451C5289E200A1ECB5 /* SettingsMainController.swift */; }; + 111783821DC64D75000C1721 /* StaticTableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110E425B1C00DC5E001A3CA2 /* StaticTableController.swift */; }; + 111783831DC64D75000C1721 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B2535C1C4BACDB0068F47C /* TabBarController.swift */; }; + 111783841DC64D75000C1721 /* TextEditController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB351C1DEF2E006AA9E5 /* TextEditController.swift */; }; + 111783851DC64D75000C1721 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119AD1BD1BDED261000C5CB8 /* TextViewController.swift */; }; + 112422661BDD2032004D7926 /* KeyboardNotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112422651BDD2032004D7926 /* KeyboardNotificationController.swift */; }; + 112495371DE7ABFF00EF45C4 /* KeyboardObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112495361DE7ABFF00EF45C4 /* KeyboardObserver.swift */; }; + 1124953E1DE7AC0300EF45C4 /* KeyboardObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112495361DE7ABFF00EF45C4 /* KeyboardObserver.swift */; }; + 1124953F1DE7AC0400EF45C4 /* KeyboardObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112495361DE7ABFF00EF45C4 /* KeyboardObserver.swift */; }; + 112950841D63AFCE00C9CE0F /* LoginGenericCreateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112950831D63AFCE00C9CE0F /* LoginGenericCreateController.swift */; }; + 112950851D63AFCE00C9CE0F /* LoginGenericCreateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112950831D63AFCE00C9CE0F /* LoginGenericCreateController.swift */; }; + 112950871D63AFD800C9CE0F /* LoginCreatePasswordController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112950861D63AFD800C9CE0F /* LoginCreatePasswordController.swift */; }; + 112950881D63AFD800C9CE0F /* LoginCreatePasswordController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112950861D63AFD800C9CE0F /* LoginCreatePasswordController.swift */; }; + 1129508B1D6851EB00C9CE0F /* LoginCreateAccountCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1129508A1D6851EB00C9CE0F /* LoginCreateAccountCoordinator.swift */; }; + 1129508C1D6851EB00C9CE0F /* LoginCreateAccountCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1129508A1D6851EB00C9CE0F /* LoginCreateAccountCoordinator.swift */; }; + 112E4ED61C668C02004312CF /* CallIncomingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112E4ED51C668C02004312CF /* CallIncomingController.swift */; }; + 112E4ED71C668C02004312CF /* CallIncomingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112E4ED51C668C02004312CF /* CallIncomingController.swift */; }; + 112E4EDF1C675C58004312CF /* CallActiveController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112E4EDE1C675C58004312CF /* CallActiveController.swift */; }; + 112E4EE01C675C58004312CF /* CallActiveController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112E4EDE1C675C58004312CF /* CallActiveController.swift */; }; + 112E4EE51C676553004312CF /* CallButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112E4EE41C676553004312CF /* CallButton.swift */; }; + 112E4EE61C676553004312CF /* CallButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112E4EE41C676553004312CF /* CallButton.swift */; }; + 113187441DD63B6600E6FAA2 /* ChatOutgoingTextCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113187431DD63B6600E6FAA2 /* ChatOutgoingTextCellModel.swift */; }; + 113187451DD63B6600E6FAA2 /* ChatOutgoingTextCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113187431DD63B6600E6FAA2 /* ChatOutgoingTextCellModel.swift */; }; + 113187461DD63B6600E6FAA2 /* ChatOutgoingTextCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113187431DD63B6600E6FAA2 /* ChatOutgoingTextCellModel.swift */; }; + 1131D6C51CA9D8BC00B4531C /* import-profile.html in Resources */ = {isa = PBXBuildFile; fileRef = 1131D6C71CA9D8BC00B4531C /* import-profile.html */; }; + 1136605C1CDE5D5E0092C27A /* ChatEditable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1136605B1CDE5D5E0092C27A /* ChatEditable.swift */; }; + 113AD84A1CA97A3000D981B5 /* iPadNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113AD8491CA97A3000D981B5 /* iPadNavigationView.swift */; }; + 113AD84B1CA97A3000D981B5 /* iPadNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113AD8491CA97A3000D981B5 /* iPadNavigationView.swift */; }; + 113AD8571CA9C70F00D981B5 /* iPadFriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113AD8561CA9C70F00D981B5 /* iPadFriendsButton.swift */; }; + 113AD8581CA9C70F00D981B5 /* iPadFriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113AD8561CA9C70F00D981B5 /* iPadFriendsButton.swift */; }; + 113BBBA71EA88EAC00540E6C /* ChatTypingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113BBBA61EA88EAC00540E6C /* ChatTypingHeaderView.swift */; }; + 113BBBA81EA88EAC00540E6C /* ChatTypingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113BBBA61EA88EAC00540E6C /* ChatTypingHeaderView.swift */; }; + 113BBBA91EA88EAC00540E6C /* ChatTypingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113BBBA61EA88EAC00540E6C /* ChatTypingHeaderView.swift */; }; + 113D564D1C2F437B00B3D3E8 /* QRScannerAimView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113D564C1C2F437B00B3D3E8 /* QRScannerAimView.swift */; }; + 113D56561C2F459E00B3D3E8 /* QRScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113D56551C2F459E00B3D3E8 /* QRScannerController.swift */; }; + 113E96421E4BB302000282FC /* AppStoreLocalizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 113E96461E4BB302000282FC /* AppStoreLocalizable.strings */; }; + 113E96431E4BB302000282FC /* AppStoreLocalizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 113E96461E4BB302000282FC /* AppStoreLocalizable.strings */; }; + 113E96441E4BB302000282FC /* AppStoreLocalizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 113E96461E4BB302000282FC /* AppStoreLocalizable.strings */; }; + 113F03081CAFCC9D0009ABE1 /* default-theme.yaml in Resources */ = {isa = PBXBuildFile; fileRef = 1173F0711BC5D9DA00B88B7B /* default-theme.yaml */; }; + 113F030A1CAFCCB90009ABE1 /* SnapshotBaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F03091CAFCCB90009ABE1 /* SnapshotBaseTest.swift */; }; + 113F030C1CAFCCDD0009ABE1 /* ChatIncomingCallCellSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F030B1CAFCCDD0009ABE1 /* ChatIncomingCallCellSnapshotTest.swift */; }; + 113F030F1CAFCE7B0009ABE1 /* ChatIncomingFileCellSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F030E1CAFCE7B0009ABE1 /* ChatIncomingFileCellSnapshotTest.swift */; }; + 113F03111CAFD1610009ABE1 /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 113F03101CAFD1610009ABE1 /* icon.png */; }; + 113F03141CAFD5EE0009ABE1 /* CellSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F03131CAFD5EE0009ABE1 /* CellSnapshotTest.swift */; }; + 113F03171CAFD9370009ABE1 /* MockedChatProgressProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F03161CAFD9370009ABE1 /* MockedChatProgressProtocol.swift */; }; + 113F03191CAFDB0D0009ABE1 /* ChatIncomingTextCellSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F03181CAFDB0D0009ABE1 /* ChatIncomingTextCellSnapshotTest.swift */; }; + 113F031B1CAFDBF70009ABE1 /* ChatListCellSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F031A1CAFDBF70009ABE1 /* ChatListCellSnapshotTest.swift */; }; + 113F031D1CAFE0DE0009ABE1 /* ChatMovableDateCellSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F031C1CAFE0DE0009ABE1 /* ChatMovableDateCellSnapshotTest.swift */; }; + 113F031F1CAFE2130009ABE1 /* ChatOutgoingCallCellSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F031E1CAFE2130009ABE1 /* ChatOutgoingCallCellSnapshotTest.swift */; }; + 113F03211CAFE2B50009ABE1 /* ChatOutgoingFileCellSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F03201CAFE2B50009ABE1 /* ChatOutgoingFileCellSnapshotTest.swift */; }; + 113F03231CAFE5190009ABE1 /* ChatOutgoingTextCellSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F03221CAFE5190009ABE1 /* ChatOutgoingTextCellSnapshotTest.swift */; }; + 113F03331CB2E9C20009ABE1 /* UIFontExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F03321CB2E9C20009ABE1 /* UIFontExtension.swift */; }; + 113F03341CB2E9C20009ABE1 /* UIFontExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F03321CB2E9C20009ABE1 /* UIFontExtension.swift */; }; + 113F03421CB31DD60009ABE1 /* AlertAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F03411CB31DD60009ABE1 /* AlertAudioPlayer.swift */; }; + 113F03431CB31DD60009ABE1 /* AlertAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113F03411CB31DD60009ABE1 /* AlertAudioPlayer.swift */; }; + 1143E4331DCE1A5600BE7250 /* import-profile.html in Resources */ = {isa = PBXBuildFile; fileRef = 1131D6C71CA9D8BC00B4531C /* import-profile.html */; }; + 1149626C1C5CE3DF001E5435 /* ChangeUserStatusController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1149626B1C5CE3DF001E5435 /* ChangeUserStatusController.swift */; }; + 1149626D1C5CE3DF001E5435 /* ChangeUserStatusController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1149626B1C5CE3DF001E5435 /* ChangeUserStatusController.swift */; }; + 114962751C5CEB0B001E5435 /* ProfileDetailsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114962741C5CEB0B001E5435 /* ProfileDetailsController.swift */; }; + 114962761C5CEB0B001E5435 /* ProfileDetailsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114962741C5CEB0B001E5435 /* ProfileDetailsController.swift */; }; + 114962921C5E1BE0001E5435 /* ChangePasswordController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114962911C5E1BE0001E5435 /* ChangePasswordController.swift */; }; + 114962931C5E1BE0001E5435 /* ChangePasswordController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114962911C5E1BE0001E5435 /* ChangePasswordController.swift */; }; + 114962951C64051A001E5435 /* CallBaseController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114962941C64051A001E5435 /* CallBaseController.swift */; }; + 114962961C64051A001E5435 /* CallBaseController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114962941C64051A001E5435 /* CallBaseController.swift */; }; + 114D2D401E34D6E400662713 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114D2D3F1E34D6E400662713 /* SnapshotHelper.swift */; }; + 1156A4791C0E478D005AC8C6 /* StaticTableBaseCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4781C0E478D005AC8C6 /* StaticTableBaseCellModel.swift */; }; + 1156A47B1C0F6178005AC8C6 /* StaticTableButtonCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A47A1C0F6178005AC8C6 /* StaticTableButtonCellModel.swift */; }; + 1156A47D1C0F632C005AC8C6 /* StaticTableSelectableCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A47C1C0F632C005AC8C6 /* StaticTableSelectableCellModel.swift */; }; + 1156A4811C0FA0A4005AC8C6 /* StaticTableButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4801C0FA0A4005AC8C6 /* StaticTableButtonCell.swift */; }; + 1156A4851C10D119005AC8C6 /* StaticTableAvatarCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4841C10D119005AC8C6 /* StaticTableAvatarCellModel.swift */; }; + 1156A4871C10D122005AC8C6 /* StaticTableAvatarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4861C10D122005AC8C6 /* StaticTableAvatarCell.swift */; }; + 1156A4891C15FB2B005AC8C6 /* AvatarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4881C15FB2B005AC8C6 /* AvatarManager.swift */; }; + 115C3BD61DC6D2B900903A47 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9C7475C01BC698110098B1A4 /* Localizable.strings */; }; + 115CE3E81EB06F54001C08A0 /* ChatBottomStatusViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115CE3E71EB06F54001C08A0 /* ChatBottomStatusViewManager.swift */; }; + 115CE3E91EB06F54001C08A0 /* ChatBottomStatusViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115CE3E71EB06F54001C08A0 /* ChatBottomStatusViewManager.swift */; }; + 115CE3EA1EB06F54001C08A0 /* ChatBottomStatusViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115CE3E71EB06F54001C08A0 /* ChatBottomStatusViewManager.swift */; }; + 115DFB8A1C177F5C00F18DB5 /* StaticTableDefaultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115DFB891C177F5C00F18DB5 /* StaticTableDefaultCell.swift */; }; + 115DFB961C177F6500F18DB5 /* StaticTableDefaultCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115DFB951C177F6500F18DB5 /* StaticTableDefaultCellModel.swift */; }; + 116046191D8D434B002287C8 /* ChangePinTimeoutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116046181D8D434B002287C8 /* ChangePinTimeoutController.swift */; }; + 1160461A1D8D434B002287C8 /* ChangePinTimeoutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116046181D8D434B002287C8 /* ChangePinTimeoutController.swift */; }; + 1160B67E1D5F9993002DF75B /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1160B67D1D5F9993002DF75B /* KeychainManager.swift */; }; + 1160B67F1D5F9993002DF75B /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1160B67D1D5F9993002DF75B /* KeychainManager.swift */; }; + 1160B6821D5FC7CC002DF75B /* KeychainManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1160B6801D5FC7CC002DF75B /* KeychainManagerTests.swift */; }; + 1160B68B1D5FD8DE002DF75B /* LaunchPlaceholderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1160B6891D5FD8DE002DF75B /* LaunchPlaceholderController.swift */; }; + 1160B68C1D5FD8DE002DF75B /* LaunchPlaceholderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1160B6891D5FD8DE002DF75B /* LaunchPlaceholderController.swift */; }; + 11628E321CA2FBD1008C097E /* FilePreviewControllerDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E311CA2FBD1008C097E /* FilePreviewControllerDataSource.swift */; }; + 11628E331CA2FBD1008C097E /* FilePreviewControllerDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E311CA2FBD1008C097E /* FilePreviewControllerDataSource.swift */; }; + 11628E3E1CA30C13008C097E /* QuickLookPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E3D1CA30C13008C097E /* QuickLookPreviewController.swift */; }; + 11628E3F1CA30C13008C097E /* QuickLookPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E3D1CA30C13008C097E /* QuickLookPreviewController.swift */; }; + 11628E631CA57B14008C097E /* ChatOutgoingFileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E621CA57B14008C097E /* ChatOutgoingFileCell.swift */; }; + 11628E641CA57B14008C097E /* ChatOutgoingFileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E621CA57B14008C097E /* ChatOutgoingFileCell.swift */; }; + 11628E6C1CA57B19008C097E /* ChatOutgoingFileCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E6B1CA57B19008C097E /* ChatOutgoingFileCellModel.swift */; }; + 11628E6D1CA57B19008C097E /* ChatOutgoingFileCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E6B1CA57B19008C097E /* ChatOutgoingFileCellModel.swift */; }; + 11628E721CA580ED008C097E /* ChatGenericFileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E711CA580ED008C097E /* ChatGenericFileCell.swift */; }; + 11628E731CA580ED008C097E /* ChatGenericFileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E711CA580ED008C097E /* ChatGenericFileCell.swift */; }; + 11628E751CA5811C008C097E /* ChatGenericFileCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E741CA5811C008C097E /* ChatGenericFileCellModel.swift */; }; + 11628E761CA5811C008C097E /* ChatGenericFileCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E741CA5811C008C097E /* ChatGenericFileCellModel.swift */; }; + 11628E781CA5819B008C097E /* LoadingImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E771CA5819B008C097E /* LoadingImageView.swift */; }; + 11628E791CA5819B008C097E /* LoadingImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E771CA5819B008C097E /* LoadingImageView.swift */; }; + 11628E7B1CA5BF1F008C097E /* ChangeAutodownloadImagesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E7A1CA5BF1F008C097E /* ChangeAutodownloadImagesController.swift */; }; + 11628E7C1CA5BF1F008C097E /* ChangeAutodownloadImagesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11628E7A1CA5BF1F008C097E /* ChangeAutodownloadImagesController.swift */; }; + 1164763319794D3300DB20B8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1164763219794D3300DB20B8 /* Foundation.framework */; }; + 1164763519794D3300DB20B8 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1164763419794D3300DB20B8 /* CoreGraphics.framework */; }; + 1164763719794D3300DB20B8 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1164763619794D3300DB20B8 /* UIKit.framework */; }; + 1164764519794D3300DB20B8 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1164764419794D3300DB20B8 /* Images.xcassets */; }; + 1164C8D81BC922EB00B91107 /* UserDefaultsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8D71BC922EB00B91107 /* UserDefaultsManager.swift */; }; + 1164C8E71BC929C500B91107 /* LoginBaseController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8E61BC929C500B91107 /* LoginBaseController.swift */; }; + 1164C8E91BC929F700B91107 /* LoginChoiceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8E81BC929F700B91107 /* LoginChoiceController.swift */; }; + 1164C8ED1BC9801200B91107 /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8EC1BC9801200B91107 /* RoundedButton.swift */; }; + 1164C8F01BC982FF00B91107 /* UIImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8EF1BC982FF00B91107 /* UIImageExtension.swift */; }; + 1164C8F31BC9880000B91107 /* LoginLogoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8F21BC9880000B91107 /* LoginLogoController.swift */; }; + 1164C8F61BC98F8300B91107 /* UIViewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8F51BC98F8300B91107 /* UIViewControllerExtension.swift */; }; + 116513191C2C6C980066AF06 /* FriendCardController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116513181C2C6C980066AF06 /* FriendCardController.swift */; }; + 116513221C2C74940066AF06 /* StaticTableChatButtonsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116513211C2C74940066AF06 /* StaticTableChatButtonsCell.swift */; }; + 116513241C2C749E0066AF06 /* StaticTableChatButtonsCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116513231C2C749E0066AF06 /* StaticTableChatButtonsCellModel.swift */; }; + 116513261C2D636A0066AF06 /* NotificationWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116513251C2D636A0066AF06 /* NotificationWindow.swift */; }; + 116634A01C6E8D0F0072C980 /* ChatIncomingCallCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1166349F1C6E8D0F0072C980 /* ChatIncomingCallCell.swift */; }; + 116634A11C6E8D0F0072C980 /* ChatIncomingCallCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1166349F1C6E8D0F0072C980 /* ChatIncomingCallCell.swift */; }; + 116634A91C6E8D1A0072C980 /* ChatIncomingCallCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634A81C6E8D1A0072C980 /* ChatIncomingCallCellModel.swift */; }; + 116634AA1C6E8D1A0072C980 /* ChatIncomingCallCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634A81C6E8D1A0072C980 /* ChatIncomingCallCellModel.swift */; }; + 116634AC1C6F407D0072C980 /* ChatOutgoingCallCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634AB1C6F407D0072C980 /* ChatOutgoingCallCell.swift */; }; + 116634AD1C6F407D0072C980 /* ChatOutgoingCallCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634AB1C6F407D0072C980 /* ChatOutgoingCallCell.swift */; }; + 116634AF1C6F40820072C980 /* ChatOutgoingCallCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634AE1C6F40820072C980 /* ChatOutgoingCallCellModel.swift */; }; + 116634B01C6F40820072C980 /* ChatOutgoingCallCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634AE1C6F40820072C980 /* ChatOutgoingCallCellModel.swift */; }; + 116634B21C7087A80072C980 /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634B11C7087A80072C980 /* UIApplicationExtension.swift */; }; + 116634B31C7087A80072C980 /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634B11C7087A80072C980 /* UIApplicationExtension.swift */; }; + 116634CF1C70E46C0072C980 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 116634CE1C70E46C0072C980 /* Launch Screen.storyboard */; }; + 116634D91C70F1260072C980 /* CopyLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634D81C70F1260072C980 /* CopyLabel.swift */; }; + 116634DA1C70F1260072C980 /* CopyLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634D81C70F1260072C980 /* CopyLabel.swift */; }; + 116634E01C7B8D770072C980 /* StaticTableInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634DF1C7B8D770072C980 /* StaticTableInfoCell.swift */; }; + 116634E11C7B8D770072C980 /* StaticTableInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634DF1C7B8D770072C980 /* StaticTableInfoCell.swift */; }; + 116634E91C7B8D7C0072C980 /* StaticTableInfoCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634E81C7B8D7C0072C980 /* StaticTableInfoCellModel.swift */; }; + 116634EA1C7B8D7C0072C980 /* StaticTableInfoCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634E81C7B8D7C0072C980 /* StaticTableInfoCellModel.swift */; }; + 116634EC1C7B90F20072C980 /* FriendRequestController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634EB1C7B90F20072C980 /* FriendRequestController.swift */; }; + 116634ED1C7B90F20072C980 /* FriendRequestController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634EB1C7B90F20072C980 /* FriendRequestController.swift */; }; + 116634EF1C7B94530072C980 /* StaticTableMultiChoiceButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634EE1C7B94530072C980 /* StaticTableMultiChoiceButtonCell.swift */; }; + 116634F01C7B94530072C980 /* StaticTableMultiChoiceButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634EE1C7B94530072C980 /* StaticTableMultiChoiceButtonCell.swift */; }; + 116634F21C7B945A0072C980 /* StaticTableMultiChoiceButtonCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634F11C7B945A0072C980 /* StaticTableMultiChoiceButtonCellModel.swift */; }; + 116634F31C7B945A0072C980 /* StaticTableMultiChoiceButtonCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116634F11C7B945A0072C980 /* StaticTableMultiChoiceButtonCellModel.swift */; }; + 11687F2E1DC64F0D0029B93F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0ED31BC5842800F3DA5B /* AppDelegate.swift */; }; + 116DCE321CAAF47100B693EC /* TopCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116DCE311CAAF47100B693EC /* TopCoordinatorProtocol.swift */; }; + 116DCE331CAAF47100B693EC /* TopCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116DCE311CAAF47100B693EC /* TopCoordinatorProtocol.swift */; }; + 116DCE3B1CAAF85300B693EC /* NSURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116DCE3A1CAAF85300B693EC /* NSURLExtension.swift */; }; + 116DCE3C1CAAF85300B693EC /* NSURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116DCE3A1CAAF85300B693EC /* NSURLExtension.swift */; }; + 1173F06E1BC5D9BA00B88B7B /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1173F06D1BC5D9BA00B88B7B /* Theme.swift */; }; + 1173F0701BC5D9CA00B88B7B /* ThemeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1173F06F1BC5D9CA00B88B7B /* ThemeTest.swift */; }; + 1173F0721BC5D9DA00B88B7B /* default-theme.yaml in Resources */ = {isa = PBXBuildFile; fileRef = 1173F0711BC5D9DA00B88B7B /* default-theme.yaml */; }; + 11770D6A1CBC120C00D34D6E /* FriendSelectController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11770D691CBC120C00D34D6E /* FriendSelectController.swift */; }; + 11770D6B1CBC120C00D34D6E /* FriendSelectController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11770D691CBC120C00D34D6E /* FriendSelectController.swift */; }; + 11770D771CBC1C7A00D34D6E /* UIAlertControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11770D761CBC1C7A00D34D6E /* UIAlertControllerExtension.swift */; }; + 11770D781CBC1C7A00D34D6E /* UIAlertControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11770D761CBC1C7A00D34D6E /* UIAlertControllerExtension.swift */; }; + 11771E2C1CA5C3F200EC259E /* AutomationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11771E2B1CA5C3F200EC259E /* AutomationCoordinator.swift */; }; + 11771E2D1CA5C3F200EC259E /* AutomationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11771E2B1CA5C3F200EC259E /* AutomationCoordinator.swift */; }; + 11771E361CA5C6E900EC259E /* Reach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11771E351CA5C6E900EC259E /* Reach.swift */; }; + 11771E371CA5C6E900EC259E /* Reach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11771E351CA5C6E900EC259E /* Reach.swift */; }; + 1180FD9E1C38497A005F3EA1 /* NSDateFormatterExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1180FD9D1C38497A005F3EA1 /* NSDateFormatterExtension.swift */; }; + 1180FDA01C384E29005F3EA1 /* CallCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1180FD9F1C384E29005F3EA1 /* CallCoordinator.swift */; }; + 1183BCAC1CA1B398000CD310 /* ChatIncomingFileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCAB1CA1B398000CD310 /* ChatIncomingFileCell.swift */; }; + 1183BCAD1CA1B398000CD310 /* ChatIncomingFileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCAB1CA1B398000CD310 /* ChatIncomingFileCell.swift */; }; + 1183BCB51CA1B3AE000CD310 /* ChatIncomingFileCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCB41CA1B3AE000CD310 /* ChatIncomingFileCellModel.swift */; }; + 1183BCB61CA1B3AE000CD310 /* ChatIncomingFileCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCB41CA1B3AE000CD310 /* ChatIncomingFileCellModel.swift */; }; + 1183BCC01CA1DB05000CD310 /* ProgressCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCBF1CA1DB05000CD310 /* ProgressCircleView.swift */; }; + 1183BCC71CA1DB09000CD310 /* ProgressCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCBF1CA1DB05000CD310 /* ProgressCircleView.swift */; }; + 1183BCD71CA1FC5B000CD310 /* ChatProgressProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCD61CA1FC5B000CD310 /* ChatProgressProtocol.swift */; }; + 1183BCD81CA1FC5B000CD310 /* ChatProgressProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCD61CA1FC5B000CD310 /* ChatProgressProtocol.swift */; }; + 1183BCDD1CA1FD55000CD310 /* ChatProgressBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCDC1CA1FD55000CD310 /* ChatProgressBridge.swift */; }; + 1183BCDE1CA1FD55000CD310 /* ChatProgressBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1183BCDC1CA1FD55000CD310 /* ChatProgressBridge.swift */; }; + 1193AB361C1DEF2E006AA9E5 /* TextEditController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB351C1DEF2E006AA9E5 /* TextEditController.swift */; }; + 1193AB421C1E2075006AA9E5 /* QRViewerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB411C1E2075006AA9E5 /* QRViewerController.swift */; }; + 1193AB481C1F4164006AA9E5 /* FriendListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB471C1F4164006AA9E5 /* FriendListController.swift */; }; + 1193AB661C1F5694006AA9E5 /* BaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB651C1F5694006AA9E5 /* BaseCell.swift */; }; + 1193AB681C1F569A006AA9E5 /* BaseCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB671C1F569A006AA9E5 /* BaseCellModel.swift */; }; + 1193AB6A1C1F5C28006AA9E5 /* FriendListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB691C1F5C28006AA9E5 /* FriendListCell.swift */; }; + 1193AB6C1C1F5C31006AA9E5 /* FriendListCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB6B1C1F5C31006AA9E5 /* FriendListCellModel.swift */; }; + 119AD1BE1BDED261000C5CB8 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119AD1BD1BDED261000C5CB8 /* TextViewController.swift */; }; + 119AD1CA1BDED6E9000C5CB8 /* ErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119AD1C91BDED6E9000C5CB8 /* ErrorHandling.swift */; }; + 119AD1CC1BDEDD9C000C5CB8 /* UIColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119AD1CB1BDEDD9C000C5CB8 /* UIColorExtension.swift */; }; + 11A21EFB1C45BDB100E80A89 /* ChatPrivateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A21EFA1C45BDB100E80A89 /* ChatPrivateController.swift */; }; + 11A21EFD1C45BDF200E80A89 /* ChatInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A21EFC1C45BDF200E80A89 /* ChatInputView.swift */; }; + 11AEBE711DA1876F00D04B59 /* FAQController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11AEBE701DA1876F00D04B59 /* FAQController.swift */; }; + 11AEBE721DA1876F00D04B59 /* FAQController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11AEBE701DA1876F00D04B59 /* FAQController.swift */; }; + 11B252FA1C4A614D0068F47C /* ChatIncomingTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B252F91C4A614D0068F47C /* ChatIncomingTextCell.swift */; }; + 11B252FE1C4A615E0068F47C /* ChatOutgoingTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B252FD1C4A615E0068F47C /* ChatOutgoingTextCell.swift */; }; + 11B253021C4A61D00068F47C /* ChatMovableDateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253011C4A61D00068F47C /* ChatMovableDateCell.swift */; }; + 11B253041C4A61D50068F47C /* ChatMovableDateCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253031C4A61D50068F47C /* ChatMovableDateCellModel.swift */; }; + 11B253141C4A79A50068F47C /* BubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253131C4A79A50068F47C /* BubbleView.swift */; }; + 11B2534F1C4AD2550068F47C /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B2534E1C4AD2550068F47C /* AudioPlayer.swift */; }; + 11B253501C4AD9CF0068F47C /* isotoxin_Calltone.aac in Resources */ = {isa = PBXBuildFile; fileRef = 11B253491C4AD2200068F47C /* isotoxin_Calltone.aac */; }; + 11B253511C4AD9CF0068F47C /* isotoxin_Hangup.aac in Resources */ = {isa = PBXBuildFile; fileRef = 11B2534A1C4AD23A0068F47C /* isotoxin_Hangup.aac */; }; + 11B253521C4AD9CF0068F47C /* isotoxin_NewMessage.aac in Resources */ = {isa = PBXBuildFile; fileRef = 11B2534B1C4AD23A0068F47C /* isotoxin_NewMessage.aac */; }; + 11B253531C4AD9CF0068F47C /* isotoxin_Ringtone.aac in Resources */ = {isa = PBXBuildFile; fileRef = 11B2534C1C4AD23A0068F47C /* isotoxin_Ringtone.aac */; }; + 11B253541C4AD9CF0068F47C /* isotoxin_RingtoneWhileCall.aac in Resources */ = {isa = PBXBuildFile; fileRef = 11B2534D1C4AD23A0068F47C /* isotoxin_RingtoneWhileCall.aac */; }; + 11B253561C4AEA400068F47C /* ChatPrivateTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253551C4AEA400068F47C /* ChatPrivateTitleView.swift */; }; + 11B2535D1C4BACDB0068F47C /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B2535C1C4BACDB0068F47C /* TabBarController.swift */; }; + 11B253661C4BAD3C0068F47C /* TabBarAbstractItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253651C4BAD3C0068F47C /* TabBarAbstractItem.swift */; }; + 11B2536A1C4BAD560068F47C /* TabBarBadgeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253691C4BAD560068F47C /* TabBarBadgeItem.swift */; }; + 11B2536D1C4BAD5D0068F47C /* TabBarProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B2536C1C4BAD5D0068F47C /* TabBarProfileItem.swift */; }; + 11B253A41C4EA8040068F47C /* InterfaceIdiom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253A31C4EA8040068F47C /* InterfaceIdiom.swift */; }; + 11B253A51C4EA8040068F47C /* InterfaceIdiom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253A31C4EA8040068F47C /* InterfaceIdiom.swift */; }; + 11B253AE1C4EC47D0068F47C /* IncompressibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253AD1C4EC47D0068F47C /* IncompressibleView.swift */; }; + 11B253AF1C4EC47D0068F47C /* IncompressibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253AD1C4EC47D0068F47C /* IncompressibleView.swift */; }; + 11B253B71C503FA10068F47C /* NSTimerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253B61C503FA10068F47C /* NSTimerExtension.swift */; }; + 11B253B81C503FA10068F47C /* NSTimerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253B61C503FA10068F47C /* NSTimerExtension.swift */; }; + 11B253FB1C52C0A10068F47C /* StaticTableSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253FA1C52C0A10068F47C /* StaticTableSwitchCell.swift */; }; + 11B253FC1C52C0A10068F47C /* StaticTableSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253FA1C52C0A10068F47C /* StaticTableSwitchCell.swift */; }; + 11B253FE1C52C0AA0068F47C /* StaticTableSwitchCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253FD1C52C0AA0068F47C /* StaticTableSwitchCellModel.swift */; }; + 11B253FF1C52C0AA0068F47C /* StaticTableSwitchCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253FD1C52C0AA0068F47C /* StaticTableSwitchCellModel.swift */; }; + 11B3F8701CE095D2001927D8 /* ChatEditable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1136605B1CDE5D5E0092C27A /* ChatEditable.swift */; }; + 11B9C6831BD598FC0083C2A5 /* OCTManagerConfigurationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B9C6821BD598FC0083C2A5 /* OCTManagerConfigurationExtension.swift */; }; + 11B9C6971BD80B080083C2A5 /* FullscreenPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B9C6961BD80B080083C2A5 /* FullscreenPicker.swift */; }; + 11C33AC41DC961DC008DBC49 /* LoginChoiceViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C33AC31DC961DC008DBC49 /* LoginChoiceViewSnapshotTest.swift */; }; + 11CFCD0B1C2745230046BD94 /* UserStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFCD0A1C2745230046BD94 /* UserStatus.swift */; }; + 11CFCD0D1C27488E0046BD94 /* ImageViewWithStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFCD0C1C27488E0046BD94 /* ImageViewWithStatus.swift */; }; + 11CFCD0F1C27499D0046BD94 /* UserStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFCD0E1C27499D0046BD94 /* UserStatusView.swift */; }; + 11CFCD111C2A02350046BD94 /* StaticBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFCD101C2A02350046BD94 /* StaticBackgroundView.swift */; }; + 11D17CDD1DD11E58006B2910 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1164764419794D3300DB20B8 /* Images.xcassets */; }; + 11D17CE91DD11F10006B2910 /* dummy-photo.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 11D17CE81DD11F10006B2910 /* dummy-photo.jpg */; }; + 11D17CEA1DD11F10006B2910 /* dummy-photo.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 11D17CE81DD11F10006B2910 /* dummy-photo.jpg */; }; + 11DDEAFD1D5FD9FE0000E2BE /* LaunchPlaceholderBoard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 11DDEAFC1D5FD9FE0000E2BE /* LaunchPlaceholderBoard.storyboard */; }; + 11E6406D1C2D6C3500D24C6D /* ViewPassingGestures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E6406C1C2D6C3500D24C6D /* ViewPassingGestures.swift */; }; + 11E640761C2DBAE200D24C6D /* AddFriendController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E640751C2DBAE200D24C6D /* AddFriendController.swift */; }; + 11E640781C2DC15400D24C6D /* HelperFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E640771C2DC15400D24C6D /* HelperFunctions.swift */; }; + 11F08D131DE0610B00F80F5F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 11F08D171DE0610B00F80F5F /* InfoPlist.strings */; }; + 11F08D141DE0610B00F80F5F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 11F08D171DE0610B00F80F5F /* InfoPlist.strings */; }; + 11F08D151DE0610B00F80F5F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 11F08D171DE0610B00F80F5F /* InfoPlist.strings */; }; + 11F276AE1D3EBEF700C613AA /* ChatBaseTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F276AD1D3EBEF700C613AA /* ChatBaseTextCell.swift */; }; + 11F276AF1D3EBEF700C613AA /* ChatBaseTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F276AD1D3EBEF700C613AA /* ChatBaseTextCell.swift */; }; + 11F276BB1D3EBF3000C613AA /* ChatBaseTextCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F276BA1D3EBF3000C613AA /* ChatBaseTextCellModel.swift */; }; + 11F276BC1D3EBF3000C613AA /* ChatBaseTextCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F276BA1D3EBF3000C613AA /* ChatBaseTextCellModel.swift */; }; + 11F83BDB1CAFB2A20074FE11 /* SwiftSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F83BDA1CAFB2A20074FE11 /* SwiftSupport.swift */; }; + 11FA0ED41BC5842800F3DA5B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0ED31BC5842800F3DA5B /* AppDelegate.swift */; }; + 11FA0EDA1BC584D900F3DA5B /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0ED91BC584D900F3DA5B /* AppCoordinator.swift */; }; + 11FA0EDE1BC58DCC00F3DA5B /* CoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EDD1BC58DCC00F3DA5B /* CoordinatorProtocol.swift */; }; + 11FA0EE11BC591FC00F3DA5B /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EE01BC591FC00F3DA5B /* Logger.swift */; }; + 11FA0EE71BC59B1400F3DA5B /* ActiveSessionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EE61BC59B1400F3DA5B /* ActiveSessionCoordinator.swift */; }; + 11FA0EE91BC5A66D00F3DA5B /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EE81BC5A66D00F3DA5B /* LoginCoordinator.swift */; }; + 11FA0EEB1BC5A68600F3DA5B /* FriendsTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EEA1BC5A68600F3DA5B /* FriendsTabCoordinator.swift */; }; + 11FA0EED1BC5A69000F3DA5B /* ProfileTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EEC1BC5A69000F3DA5B /* ProfileTabCoordinator.swift */; }; + 11FA0EF11BC5A6A500F3DA5B /* SettingsTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EF01BC5A6A500F3DA5B /* SettingsTabCoordinator.swift */; }; + 11FA0EF31BC5A6AF00F3DA5B /* ChatsTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EF21BC5A6AF00F3DA5B /* ChatsTabCoordinator.swift */; }; + 11FC10571D32E54A00CE863E /* Results.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FC10561D32E54A00CE863E /* Results.swift */; }; + 11FC10581D32E54A00CE863E /* Results.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FC10561D32E54A00CE863E /* Results.swift */; }; + 11FC105A1D32EED000CE863E /* ResultsChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FC10591D32EED000CE863E /* ResultsChange.swift */; }; + 11FC105B1D32EED000CE863E /* ResultsChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FC10591D32EED000CE863E /* ResultsChange.swift */; }; + 11FC105D1D32F29700CE863E /* OCTSubmanagerObjectsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FC105C1D32F29700CE863E /* OCTSubmanagerObjectsExtension.swift */; }; + 11FC105E1D32F29700CE863E /* OCTSubmanagerObjectsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FC105C1D32F29700CE863E /* OCTSubmanagerObjectsExtension.swift */; }; + 4E3D0F9527C9751300D5A068 /* LinearProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3D0F9427C9751300D5A068 /* LinearProgressBar.swift */; }; + 4E4B267F27C0DCAE00E07A66 /* Call.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4B267E27C0DCAE00E07A66 /* Call.swift */; }; + 4E4B268527C0DCB500E07A66 /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4B268427C0DCB500E07A66 /* CallManager.swift */; }; + 4E4B268B27C0DCBC00E07A66 /* ProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4B268A27C0DCBC00E07A66 /* ProviderDelegate.swift */; }; + 4E4EEA1927DCEC67008B0E77 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4EEA1827DCEC67008B0E77 /* LocationManager.swift */; }; + 4E4EEA1F27DCECD3008B0E77 /* Bus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4EEA1E27DCECD3008B0E77 /* Bus.swift */; }; + 8B8E9C45924FB18EB2C8044E /* libPods-ScreenshotsUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A1B3D525D11BDD4F3E9B831 /* libPods-ScreenshotsUITests.a */; }; + 96166DAAD67996D5A6819984 /* libPods-AntidoteTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 72586F4D098584EF43024224 /* libPods-AntidoteTests.a */; }; + 9C04BB6D1C17389200F58488 /* StaticTableBaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C04BB6C1C17389200F58488 /* StaticTableBaseCell.swift */; }; + 9C07151D1BCD2E5B003A27B5 /* PortraitNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C07151C1BCD2E5B003A27B5 /* PortraitNavigationController.swift */; }; + 9C07151F1BCD501D003A27B5 /* LoginFormController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C07151E1BCD501D003A27B5 /* LoginFormController.swift */; }; + 9C19367E1D79CF7E005EA0B2 /* EnterPinController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C19367D1D79CF7E005EA0B2 /* EnterPinController.swift */; }; + 9C19367F1D79CF7E005EA0B2 /* EnterPinController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C19367D1D79CF7E005EA0B2 /* EnterPinController.swift */; }; + 9C1FEB291D8C10EB008C2ADE /* ProfileSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C1FEB281D8C10EB008C2ADE /* ProfileSettings.swift */; }; + 9C1FEB2A1D8C10EB008C2ADE /* ProfileSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C1FEB281D8C10EB008C2ADE /* ProfileSettings.swift */; }; + 9C2EF3711C4D4D5C006E7AB1 /* NotificationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C2EF3701C4D4D5C006E7AB1 /* NotificationCoordinator.swift */; }; + 9C2EF37A1C4E2DDE006E7AB1 /* ChatListTableManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C2EF3791C4E2DDE006E7AB1 /* ChatListTableManager.swift */; }; + 9C3271871D79C19C00347490 /* PinAuthorizationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3271861D79C19C00347490 /* PinAuthorizationCoordinator.swift */; }; + 9C3271881D79C19C00347490 /* PinAuthorizationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3271861D79C19C00347490 /* PinAuthorizationCoordinator.swift */; }; + 9C7475BE1BC698110098B1A4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9C7475C01BC698110098B1A4 /* Localizable.strings */; }; + 9C7475C51BC698510098B1A4 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C7475C41BC698510098B1A4 /* StringExtension.swift */; }; + 9C9A07011BE793BA0003D6C7 /* ProfileMainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C9A07001BE793BA0003D6C7 /* ProfileMainController.swift */; }; + 9CB1F9511D58CA4000105858 /* RunningCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CB1F9501D58CA4000105858 /* RunningCoordinator.swift */; }; + 9CB1F9521D58CA4000105858 /* RunningCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CB1F9501D58CA4000105858 /* RunningCoordinator.swift */; }; + 9CBBC7C11BDFC62700099A5E /* LoginCreateAccountController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CBBC7C01BDFC62700099A5E /* LoginCreateAccountController.swift */; }; + 9CBBC7CD1BDFC6C600099A5E /* ExtendedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CBBC7CC1BDFC6C600099A5E /* ExtendedTextField.swift */; }; + 9CDC091B1C34081900DC0D63 /* FriendListDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDC091A1C34081900DC0D63 /* FriendListDataSource.swift */; }; + 9CDC09251C34083100DC0D63 /* FriendListDataSourceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDC09241C34083100DC0D63 /* FriendListDataSourceTest.swift */; }; + 9CDC092C1C34102F00DC0D63 /* ExceptionHandling.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CDC092B1C34102F00DC0D63 /* ExceptionHandling.m */; }; + 9CDC092D1C34102F00DC0D63 /* ExceptionHandling.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CDC092B1C34102F00DC0D63 /* ExceptionHandling.m */; }; + 9CDDB2061BD5376200B65D79 /* ProfileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDDB2051BD5376200B65D79 /* ProfileManager.swift */; }; + 9CDE31E61E489B2700333E0D /* ChatInputViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDE31E51E489B2700333E0D /* ChatInputViewManager.swift */; }; + 9CDE31E71E489B2700333E0D /* ChatInputViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDE31E51E489B2700333E0D /* ChatInputViewManager.swift */; }; + 9CDE31E81E489B2700333E0D /* ChatInputViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDE31E51E489B2700333E0D /* ChatInputViewManager.swift */; }; + 9CE0BDE61C45229500DCE357 /* ChatListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE0BDE51C45229500DCE357 /* ChatListController.swift */; }; + 9CE0BDF01C4522BF00DCE357 /* ChatListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE0BDEF1C4522BF00DCE357 /* ChatListCell.swift */; }; + 9CE0BDF31C4522C800DCE357 /* ChatListCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE0BDF21C4522C800DCE357 /* ChatListCellModel.swift */; }; + 9CEA6A1F1D957EFC0045F000 /* antidote-acknowledgements.html in Resources */ = {isa = PBXBuildFile; fileRef = 9CEA6A1E1D957EFC0045F000 /* antidote-acknowledgements.html */; }; + 9CEA6A201D957EFC0045F000 /* antidote-acknowledgements.html in Resources */ = {isa = PBXBuildFile; fileRef = 9CEA6A1E1D957EFC0045F000 /* antidote-acknowledgements.html */; }; + 9CEE6AA21C4E679100A1ECB5 /* PrimaryIpadController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6AA11C4E679100A1ECB5 /* PrimaryIpadController.swift */; }; + 9CEE6AB81C4E8FDC00A1ECB5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0ED31BC5842800F3DA5B /* AppDelegate.swift */; }; + 9CEE6AB91C4E8FDC00A1ECB5 /* BaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB651C1F5694006AA9E5 /* BaseCell.swift */; }; + 9CEE6ABA1C4E8FDC00A1ECB5 /* BaseCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB671C1F569A006AA9E5 /* BaseCellModel.swift */; }; + 9CEE6ABB1C4E8FDC00A1ECB5 /* ChatIncomingTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B252F91C4A614D0068F47C /* ChatIncomingTextCell.swift */; }; + 9CEE6ABD1C4E8FDC00A1ECB5 /* ChatListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE0BDEF1C4522BF00DCE357 /* ChatListCell.swift */; }; + 9CEE6ABE1C4E8FDC00A1ECB5 /* ChatListCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE0BDF21C4522C800DCE357 /* ChatListCellModel.swift */; }; + 9CEE6ABF1C4E8FDC00A1ECB5 /* ChatMovableDateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253011C4A61D00068F47C /* ChatMovableDateCell.swift */; }; + 9CEE6AC01C4E8FDC00A1ECB5 /* ChatMovableDateCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253031C4A61D50068F47C /* ChatMovableDateCellModel.swift */; }; + 9CEE6AC11C4E8FDC00A1ECB5 /* ChatOutgoingTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B252FD1C4A615E0068F47C /* ChatOutgoingTextCell.swift */; }; + 9CEE6AC31C4E8FDC00A1ECB5 /* FriendListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB691C1F5C28006AA9E5 /* FriendListCell.swift */; }; + 9CEE6AC41C4E8FDC00A1ECB5 /* FriendListCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB6B1C1F5C31006AA9E5 /* FriendListCellModel.swift */; }; + 9CEE6AC51C4E8FDC00A1ECB5 /* StaticTableAvatarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4861C10D122005AC8C6 /* StaticTableAvatarCell.swift */; }; + 9CEE6AC61C4E8FDC00A1ECB5 /* StaticTableAvatarCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4841C10D119005AC8C6 /* StaticTableAvatarCellModel.swift */; }; + 9CEE6AC71C4E8FDC00A1ECB5 /* StaticTableBaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C04BB6C1C17389200F58488 /* StaticTableBaseCell.swift */; }; + 9CEE6AC81C4E8FDC00A1ECB5 /* StaticTableBaseCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4781C0E478D005AC8C6 /* StaticTableBaseCellModel.swift */; }; + 9CEE6AC91C4E8FDC00A1ECB5 /* StaticTableButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4801C0FA0A4005AC8C6 /* StaticTableButtonCell.swift */; }; + 9CEE6ACA1C4E8FDC00A1ECB5 /* StaticTableButtonCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A47A1C0F6178005AC8C6 /* StaticTableButtonCellModel.swift */; }; + 9CEE6ACB1C4E8FDC00A1ECB5 /* StaticTableChatButtonsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116513211C2C74940066AF06 /* StaticTableChatButtonsCell.swift */; }; + 9CEE6ACC1C4E8FDC00A1ECB5 /* StaticTableChatButtonsCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116513231C2C749E0066AF06 /* StaticTableChatButtonsCellModel.swift */; }; + 9CEE6ACD1C4E8FDC00A1ECB5 /* StaticTableDefaultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115DFB891C177F5C00F18DB5 /* StaticTableDefaultCell.swift */; }; + 9CEE6ACE1C4E8FDC00A1ECB5 /* StaticTableDefaultCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115DFB951C177F6500F18DB5 /* StaticTableDefaultCellModel.swift */; }; + 9CEE6ACF1C4E8FDC00A1ECB5 /* StaticTableSelectableCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A47C1C0F632C005AC8C6 /* StaticTableSelectableCellModel.swift */; }; + 9CEE6AD01C4E8FDC00A1ECB5 /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B2534E1C4AD2550068F47C /* AudioPlayer.swift */; }; + 9CEE6AD11C4E8FDC00A1ECB5 /* AvatarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156A4881C15FB2B005AC8C6 /* AvatarManager.swift */; }; + 9CEE6AD21C4E8FDC00A1ECB5 /* ChatListTableManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C2EF3791C4E2DDE006E7AB1 /* ChatListTableManager.swift */; }; + 9CEE6AD31C4E8FDC00A1ECB5 /* FriendListDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDC091A1C34081900DC0D63 /* FriendListDataSource.swift */; }; + 9CEE6AD61C4E8FDC00A1ECB5 /* ProfileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDDB2051BD5376200B65D79 /* ProfileManager.swift */; }; + 9CEE6AD71C4E8FDC00A1ECB5 /* TabBarAbstractItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253651C4BAD3C0068F47C /* TabBarAbstractItem.swift */; }; + 9CEE6AD81C4E8FDC00A1ECB5 /* TabBarBadgeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253691C4BAD560068F47C /* TabBarBadgeItem.swift */; }; + 9CEE6AD91C4E8FDC00A1ECB5 /* TabBarProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B2536C1C4BAD5D0068F47C /* TabBarProfileItem.swift */; }; + 9CEE6ADA1C4E8FDC00A1ECB5 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1173F06D1BC5D9BA00B88B7B /* Theme.swift */; }; + 9CEE6ADB1C4E8FDC00A1ECB5 /* UserDefaultsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8D71BC922EB00B91107 /* UserDefaultsManager.swift */; }; + 9CEE6ADC1C4E8FDC00A1ECB5 /* KeyboardNotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112422651BDD2032004D7926 /* KeyboardNotificationController.swift */; }; + 9CEE6ADD1C4E8FDC00A1ECB5 /* PortraitNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C07151C1BCD2E5B003A27B5 /* PortraitNavigationController.swift */; }; + 9CEE6ADE1C4E8FDC00A1ECB5 /* LoginBaseController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8E61BC929C500B91107 /* LoginBaseController.swift */; }; + 9CEE6ADF1C4E8FDC00A1ECB5 /* LoginChoiceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8E81BC929F700B91107 /* LoginChoiceController.swift */; }; + 9CEE6AE01C4E8FDC00A1ECB5 /* LoginCreateAccountController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CBBC7C01BDFC62700099A5E /* LoginCreateAccountController.swift */; }; + 9CEE6AE11C4E8FDC00A1ECB5 /* LoginFormController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C07151E1BCD501D003A27B5 /* LoginFormController.swift */; }; + 9CEE6AE21C4E8FDC00A1ECB5 /* LoginLogoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8F21BC9880000B91107 /* LoginLogoController.swift */; }; + 9CEE6AE51C4E8FDC00A1ECB5 /* AddFriendController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E640751C2DBAE200D24C6D /* AddFriendController.swift */; }; + 9CEE6AE71C4E8FDC00A1ECB5 /* ChatListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE0BDE51C45229500DCE357 /* ChatListController.swift */; }; + 9CEE6AE81C4E8FDC00A1ECB5 /* ChatPrivateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A21EFA1C45BDB100E80A89 /* ChatPrivateController.swift */; }; + 9CEE6AEA1C4E8FDC00A1ECB5 /* FriendCardController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116513181C2C6C980066AF06 /* FriendCardController.swift */; }; + 9CEE6AEB1C4E8FDC00A1ECB5 /* FriendListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB471C1F4164006AA9E5 /* FriendListController.swift */; }; + 9CEE6AEC1C4E8FDC00A1ECB5 /* PrimaryIpadController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6AA11C4E679100A1ECB5 /* PrimaryIpadController.swift */; }; + 9CEE6AED1C4E8FDC00A1ECB5 /* ProfileMainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C9A07001BE793BA0003D6C7 /* ProfileMainController.swift */; }; + 9CEE6AEE1C4E8FDC00A1ECB5 /* QRScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113D56551C2F459E00B3D3E8 /* QRScannerController.swift */; }; + 9CEE6AEF1C4E8FDC00A1ECB5 /* QRViewerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB411C1E2075006AA9E5 /* QRViewerController.swift */; }; + 9CEE6AF11C4E8FDC00A1ECB5 /* StaticTableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110E425B1C00DC5E001A3CA2 /* StaticTableController.swift */; }; + 9CEE6AF21C4E8FDC00A1ECB5 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B2535C1C4BACDB0068F47C /* TabBarController.swift */; }; + 9CEE6AF31C4E8FDC00A1ECB5 /* TextEditController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1193AB351C1DEF2E006AA9E5 /* TextEditController.swift */; }; + 9CEE6AF41C4E8FDC00A1ECB5 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119AD1BD1BDED261000C5CB8 /* TextViewController.swift */; }; + 9CEE6AF61C4E8FDC00A1ECB5 /* CoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EDD1BC58DCC00F3DA5B /* CoordinatorProtocol.swift */; }; + 9CEE6AF71C4E8FDC00A1ECB5 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0ED91BC584D900F3DA5B /* AppCoordinator.swift */; }; + 9CEE6AF81C4E8FDC00A1ECB5 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EE81BC5A66D00F3DA5B /* LoginCoordinator.swift */; }; + 9CEE6AF91C4E8FDC00A1ECB5 /* ActiveSessionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EE61BC59B1400F3DA5B /* ActiveSessionCoordinator.swift */; }; + 9CEE6AFA1C4E8FDC00A1ECB5 /* CallCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1180FD9F1C384E29005F3EA1 /* CallCoordinator.swift */; }; + 9CEE6AFC1C4E8FDC00A1ECB5 /* ChatsTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EF21BC5A6AF00F3DA5B /* ChatsTabCoordinator.swift */; }; + 9CEE6AFD1C4E8FDC00A1ECB5 /* FriendsTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EEA1BC5A68600F3DA5B /* FriendsTabCoordinator.swift */; }; + 9CEE6AFE1C4E8FDC00A1ECB5 /* NotificationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C2EF3701C4D4D5C006E7AB1 /* NotificationCoordinator.swift */; }; + 9CEE6AFF1C4E8FDC00A1ECB5 /* ProfileTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EEC1BC5A69000F3DA5B /* ProfileTabCoordinator.swift */; }; + 9CEE6B001C4E8FDC00A1ECB5 /* ActiveSessionNavigationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110E49DF1BF925FD00D1FE6F /* ActiveSessionNavigationCoordinator.swift */; }; + 9CEE6B011C4E8FDC00A1ECB5 /* SettingsTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EF01BC5A6A500F3DA5B /* SettingsTabCoordinator.swift */; }; + 9CEE6B021C4E8FDC00A1ECB5 /* UserStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFCD0A1C2745230046BD94 /* UserStatus.swift */; }; + 9CEE6B031C4E8FDC00A1ECB5 /* UIImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8EF1BC982FF00B91107 /* UIImageExtension.swift */; }; + 9CEE6B041C4E8FDC00A1ECB5 /* NSDateFormatterExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1180FD9D1C38497A005F3EA1 /* NSDateFormatterExtension.swift */; }; + 9CEE6B051C4E8FDC00A1ECB5 /* OCTManagerConfigurationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B9C6821BD598FC0083C2A5 /* OCTManagerConfigurationExtension.swift */; }; + 9CEE6B071C4E8FDC00A1ECB5 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C7475C41BC698510098B1A4 /* StringExtension.swift */; }; + 9CEE6B091C4E8FDC00A1ECB5 /* UIColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119AD1CB1BDEDD9C000C5CB8 /* UIColorExtension.swift */; }; + 9CEE6B0A1C4E8FDC00A1ECB5 /* UIViewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8F51BC98F8300B91107 /* UIViewControllerExtension.swift */; }; + 9CEE6B0B1C4E8FDC00A1ECB5 /* ErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119AD1C91BDED6E9000C5CB8 /* ErrorHandling.swift */; }; + 9CEE6B0C1C4E8FDC00A1ECB5 /* HelperFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E640771C2DC15400D24C6D /* HelperFunctions.swift */; }; + 9CEE6B0D1C4E8FDC00A1ECB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FA0EE01BC591FC00F3DA5B /* Logger.swift */; }; + 9CEE6B0E1C4E8FDC00A1ECB5 /* BubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253131C4A79A50068F47C /* BubbleView.swift */; }; + 9CEE6B0F1C4E8FDC00A1ECB5 /* ChatInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A21EFC1C45BDF200E80A89 /* ChatInputView.swift */; }; + 9CEE6B101C4E8FDC00A1ECB5 /* ChatPrivateTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B253551C4AEA400068F47C /* ChatPrivateTitleView.swift */; }; + 9CEE6B111C4E8FDC00A1ECB5 /* FullscreenPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B9C6961BD80B080083C2A5 /* FullscreenPicker.swift */; }; + 9CEE6B121C4E8FDC00A1ECB5 /* ImageViewWithStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFCD0C1C27488E0046BD94 /* ImageViewWithStatus.swift */; }; + 9CEE6B131C4E8FDC00A1ECB5 /* NotificationWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116513251C2D636A0066AF06 /* NotificationWindow.swift */; }; + 9CEE6B141C4E8FDC00A1ECB5 /* QRScannerAimView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113D564C1C2F437B00B3D3E8 /* QRScannerAimView.swift */; }; + 9CEE6B151C4E8FDC00A1ECB5 /* StaticBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFCD101C2A02350046BD94 /* StaticBackgroundView.swift */; }; + 9CEE6B161C4E8FDC00A1ECB5 /* UserStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFCD0E1C27499D0046BD94 /* UserStatusView.swift */; }; + 9CEE6B171C4E8FDC00A1ECB5 /* ViewPassingGestures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E6406C1C2D6C3500D24C6D /* ViewPassingGestures.swift */; }; + 9CEE6B181C4E8FDC00A1ECB5 /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1164C8EC1BC9801200B91107 /* RoundedButton.swift */; }; + 9CEE6B191C4E8FDC00A1ECB5 /* ExtendedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CBBC7CC1BDFC6C600099A5E /* ExtendedTextField.swift */; }; + 9CEE6B1F1C510A1E00A1ECB5 /* NotificationObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6B1E1C510A1E00A1ECB5 /* NotificationObject.swift */; }; + 9CEE6B201C510A1E00A1ECB5 /* NotificationObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6B1E1C510A1E00A1ECB5 /* NotificationObject.swift */; }; + 9CEE6B461C5289E200A1ECB5 /* SettingsMainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6B451C5289E200A1ECB5 /* SettingsMainController.swift */; }; + 9CEE6B471C5289E200A1ECB5 /* SettingsMainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6B451C5289E200A1ECB5 /* SettingsMainController.swift */; }; + 9CEE6B4F1C528AAA00A1ECB5 /* SettingsAboutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6B4E1C528AAA00A1ECB5 /* SettingsAboutController.swift */; }; + 9CEE6B501C528AAA00A1ECB5 /* SettingsAboutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6B4E1C528AAA00A1ECB5 /* SettingsAboutController.swift */; }; + 9CEE6B521C528AB600A1ECB5 /* SettingsAdvancedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6B511C528AB600A1ECB5 /* SettingsAdvancedController.swift */; }; + 9CEE6B531C528AB600A1ECB5 /* SettingsAdvancedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CEE6B511C528AB600A1ECB5 /* SettingsAdvancedController.swift */; }; + AF2C929D279AB3F10094C08D /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF2C929C279AB3F10094C08D /* NotificationService.swift */; }; + AF2C92A1279AB3F10094C08D /* pushextension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = AF2C929A279AB3F10094C08D /* pushextension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + AFA023F4274C069100FBFCC0 /* ConnectionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA023F3274C069100FBFCC0 /* ConnectionStatus.swift */; }; + AFA024022753CC9000FBFCC0 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = AFA024012753CC9000FBFCC0 /* GoogleService-Info.plist */; }; + D4F896D05F7EAA7C321A2AA8 /* libPods-Antidote.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 034E57B3AE56E352BBAA0487 /* libPods-Antidote.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 111782AC1DC64391000C1721 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1164762719794D3300DB20B8 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1164762E19794D3300DB20B8; + remoteInfo = Antidote; + }; + 113F02EF1CAFC9830009ABE1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1173EFE41BC5CF5D00B88B7B /* Yaml.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8313C4291C8DC2CB00DEF215; + remoteInfo = "Yaml tvOS"; + }; + 113F02F11CAFC9830009ABE1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1173EFE41BC5CF5D00B88B7B /* Yaml.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8313C4321C8DC2CB00DEF215; + remoteInfo = "Tests tvOS"; + }; + 1173EFCF1BC5C6B300B88B7B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1173EFC81BC5C6B300B88B7B /* SnapKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = EEBCC9D819CC627D0083B827; + remoteInfo = "SnapKit iOS"; + }; + 1173EFD31BC5C6B300B88B7B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1173EFC81BC5C6B300B88B7B /* SnapKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = EEBCC9E219CC627E0083B827; + remoteInfo = "SnapKit iOS Tests"; + }; + 1173EFEB1BC5CF5D00B88B7B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1173EFE41BC5CF5D00B88B7B /* Yaml.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8E1D76211B258FEE0022C013; + remoteInfo = "Yaml OSX"; + }; + 1173EFED1BC5CF5D00B88B7B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1173EFE41BC5CF5D00B88B7B /* Yaml.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8E1D762C1B258FEE0022C013; + remoteInfo = "YamlTests OSX"; + }; + 1173EFEF1BC5CF5D00B88B7B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1173EFE41BC5CF5D00B88B7B /* Yaml.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = F32DF2581B73054C0011046A; + remoteInfo = "Yaml iOS"; + }; + 1173EFF11BC5CF5D00B88B7B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1173EFE41BC5CF5D00B88B7B /* Yaml.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = F32DF2611B73054C0011046A; + remoteInfo = "YamlTests iOS"; + }; + 1173F05B1BC5D94400B88B7B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1164762719794D3300DB20B8 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1164762E19794D3300DB20B8; + remoteInfo = Antidote; + }; + AF2C929F279AB3F10094C08D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1164762719794D3300DB20B8 /* Project object */; + proxyType = 1; + remoteGlobalIDString = AF2C9299279AB3F10094C08D; + remoteInfo = pushextension; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + AF2C92A2279AB3F10094C08D /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + AF2C92A1279AB3F10094C08D /* pushextension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 02404DE3D3BB1BF461C45EA0 /* Pods-ScreenshotsUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ScreenshotsUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ScreenshotsUITests/Pods-ScreenshotsUITests.debug.xcconfig"; sourceTree = ""; }; + 034E57B3AE56E352BBAA0487 /* libPods-Antidote.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Antidote.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1105B18C1EA09B1A0035B213 /* ChatFauxOfflineHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatFauxOfflineHeaderView.swift; sourceTree = ""; }; + 1109019A1D83417500BC5751 /* PinInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinInputView.swift; sourceTree = ""; }; + 110E078E1EB0756C00B2CA9D /* ResultsExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultsExtension.swift; sourceTree = ""; }; + 110E425B1C00DC5E001A3CA2 /* StaticTableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableController.swift; sourceTree = ""; }; + 110E49DF1BF925FD00D1FE6F /* ActiveSessionNavigationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveSessionNavigationCoordinator.swift; sourceTree = ""; }; + 111782771DC52BDB000C1721 /* OCTManagerMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCTManagerMock.swift; sourceTree = ""; }; + 1117827A1DC52C3A000C1721 /* OCTSubmanagerBootstrapMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCTSubmanagerBootstrapMock.swift; sourceTree = ""; }; + 1117827D1DC52CBA000C1721 /* OCTSubmanagerCallsMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCTSubmanagerCallsMock.swift; sourceTree = ""; }; + 1117828C1DC531AD000C1721 /* OCTSubmanagerChatsMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCTSubmanagerChatsMock.swift; sourceTree = ""; }; + 111782921DC53371000C1721 /* OCTSubmanagerFilesMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCTSubmanagerFilesMock.swift; sourceTree = ""; }; + 111782951DC53458000C1721 /* OCTSubmanagerFriendsMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCTSubmanagerFriendsMock.swift; sourceTree = ""; }; + 111782981DC53545000C1721 /* OCTSubmanagerObjectsMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCTSubmanagerObjectsMock.swift; sourceTree = ""; }; + 1117829B1DC5363C000C1721 /* OCTSubmanagerUserMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCTSubmanagerUserMock.swift; sourceTree = ""; }; + 1117829E1DC53AB4000C1721 /* ToxFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToxFactory.swift; sourceTree = ""; }; + 111782A71DC64391000C1721 /* ScreenshotsUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ScreenshotsUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 111782A91DC64391000C1721 /* ScreenshotsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotsUITests.swift; sourceTree = ""; }; + 111782AB1DC64391000C1721 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1117835E1DC64C89000C1721 /* ScreenshotUITests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ScreenshotUITests-Bridging-Header.h"; sourceTree = ""; }; + 112422651BDD2032004D7926 /* KeyboardNotificationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardNotificationController.swift; sourceTree = ""; }; + 112495361DE7ABFF00EF45C4 /* KeyboardObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardObserver.swift; sourceTree = ""; }; + 112950831D63AFCE00C9CE0F /* LoginGenericCreateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginGenericCreateController.swift; sourceTree = ""; }; + 112950861D63AFD800C9CE0F /* LoginCreatePasswordController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginCreatePasswordController.swift; sourceTree = ""; }; + 1129508A1D6851EB00C9CE0F /* LoginCreateAccountCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginCreateAccountCoordinator.swift; sourceTree = ""; }; + 112E4ED51C668C02004312CF /* CallIncomingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallIncomingController.swift; sourceTree = ""; }; + 112E4EDE1C675C58004312CF /* CallActiveController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallActiveController.swift; sourceTree = ""; }; + 112E4EE41C676553004312CF /* CallButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallButton.swift; sourceTree = ""; }; + 113187371DD290DD00E6FAA2 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = pl; path = "pl.lproj/import-profile.html"; sourceTree = ""; }; + 113187381DD290DE00E6FAA2 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; + 113187431DD63B6600E6FAA2 /* ChatOutgoingTextCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatOutgoingTextCellModel.swift; sourceTree = ""; }; + 1131D6C41CA9D64500B4531C /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 1131D6C61CA9D8BC00B4531C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = en; path = "en.lproj/import-profile.html"; sourceTree = ""; }; + 1131D6C91CA9DA1500B4531C /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = ru; path = "ru.lproj/import-profile.html"; sourceTree = ""; }; + 1136605B1CDE5D5E0092C27A /* ChatEditable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatEditable.swift; sourceTree = ""; }; + 113AD8491CA97A3000D981B5 /* iPadNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iPadNavigationView.swift; sourceTree = ""; }; + 113AD8561CA9C70F00D981B5 /* iPadFriendsButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iPadFriendsButton.swift; sourceTree = ""; }; + 113BBBA61EA88EAC00540E6C /* ChatTypingHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatTypingHeaderView.swift; sourceTree = ""; }; + 113D564C1C2F437B00B3D3E8 /* QRScannerAimView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScannerAimView.swift; sourceTree = ""; }; + 113D56551C2F459E00B3D3E8 /* QRScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScannerController.swift; sourceTree = ""; }; + 113E96451E4BB302000282FC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + 113E96471E4BB314000282FC /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + 113E96481E4BB318000282FC /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + 113E96491E4BB31F000282FC /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + 113E964A1E4BB322000282FC /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + 113E964B1E4BB327000282FC /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + 113E964C1E4BB32B000282FC /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + 113E964E1E4BB371000282FC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + 113E964F1E4BB37B000282FC /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + 113F03091CAFCCB90009ABE1 /* SnapshotBaseTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotBaseTest.swift; sourceTree = ""; }; + 113F030B1CAFCCDD0009ABE1 /* ChatIncomingCallCellSnapshotTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatIncomingCallCellSnapshotTest.swift; sourceTree = ""; }; + 113F030E1CAFCE7B0009ABE1 /* ChatIncomingFileCellSnapshotTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatIncomingFileCellSnapshotTest.swift; sourceTree = ""; }; + 113F03101CAFD1610009ABE1 /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; + 113F03131CAFD5EE0009ABE1 /* CellSnapshotTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellSnapshotTest.swift; sourceTree = ""; }; + 113F03161CAFD9370009ABE1 /* MockedChatProgressProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockedChatProgressProtocol.swift; sourceTree = ""; }; + 113F03181CAFDB0D0009ABE1 /* ChatIncomingTextCellSnapshotTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatIncomingTextCellSnapshotTest.swift; sourceTree = ""; }; + 113F031A1CAFDBF70009ABE1 /* ChatListCellSnapshotTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListCellSnapshotTest.swift; sourceTree = ""; }; + 113F031C1CAFE0DE0009ABE1 /* ChatMovableDateCellSnapshotTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMovableDateCellSnapshotTest.swift; sourceTree = ""; }; + 113F031E1CAFE2130009ABE1 /* ChatOutgoingCallCellSnapshotTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatOutgoingCallCellSnapshotTest.swift; sourceTree = ""; }; + 113F03201CAFE2B50009ABE1 /* ChatOutgoingFileCellSnapshotTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatOutgoingFileCellSnapshotTest.swift; sourceTree = ""; }; + 113F03221CAFE5190009ABE1 /* ChatOutgoingTextCellSnapshotTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatOutgoingTextCellSnapshotTest.swift; sourceTree = ""; }; + 113F03321CB2E9C20009ABE1 /* UIFontExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIFontExtension.swift; sourceTree = ""; }; + 113F03411CB31DD60009ABE1 /* AlertAudioPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertAudioPlayer.swift; sourceTree = ""; }; + 113F035C1CB458D10009ABE1 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = ar; path = "ar.lproj/import-profile.html"; sourceTree = ""; }; + 113F035D1CB458D10009ABE1 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; + 1143E4311DCE1A2500BE7250 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = nl; path = "nl.lproj/import-profile.html"; sourceTree = ""; }; + 1143E4321DCE1A2600BE7250 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + 1149626B1C5CE3DF001E5435 /* ChangeUserStatusController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeUserStatusController.swift; sourceTree = ""; }; + 114962741C5CEB0B001E5435 /* ProfileDetailsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileDetailsController.swift; sourceTree = ""; }; + 114962911C5E1BE0001E5435 /* ChangePasswordController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangePasswordController.swift; sourceTree = ""; }; + 114962941C64051A001E5435 /* CallBaseController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallBaseController.swift; sourceTree = ""; }; + 114D2D3F1E34D6E400662713 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = SOURCE_ROOT; }; + 1156A4781C0E478D005AC8C6 /* StaticTableBaseCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableBaseCellModel.swift; sourceTree = ""; }; + 1156A47A1C0F6178005AC8C6 /* StaticTableButtonCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableButtonCellModel.swift; sourceTree = ""; }; + 1156A47C1C0F632C005AC8C6 /* StaticTableSelectableCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableSelectableCellModel.swift; sourceTree = ""; }; + 1156A4801C0FA0A4005AC8C6 /* StaticTableButtonCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableButtonCell.swift; sourceTree = ""; }; + 1156A4841C10D119005AC8C6 /* StaticTableAvatarCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableAvatarCellModel.swift; sourceTree = ""; }; + 1156A4861C10D122005AC8C6 /* StaticTableAvatarCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableAvatarCell.swift; sourceTree = ""; }; + 1156A4881C15FB2B005AC8C6 /* AvatarManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarManager.swift; sourceTree = ""; }; + 115CE3E71EB06F54001C08A0 /* ChatBottomStatusViewManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatBottomStatusViewManager.swift; sourceTree = ""; }; + 115DFB891C177F5C00F18DB5 /* StaticTableDefaultCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableDefaultCell.swift; sourceTree = ""; }; + 115DFB951C177F6500F18DB5 /* StaticTableDefaultCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableDefaultCellModel.swift; sourceTree = ""; }; + 116046181D8D434B002287C8 /* ChangePinTimeoutController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangePinTimeoutController.swift; sourceTree = ""; }; + 1160B67D1D5F9993002DF75B /* KeychainManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; + 1160B6801D5FC7CC002DF75B /* KeychainManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainManagerTests.swift; sourceTree = ""; }; + 1160B6891D5FD8DE002DF75B /* LaunchPlaceholderController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaunchPlaceholderController.swift; sourceTree = ""; }; + 11628E311CA2FBD1008C097E /* FilePreviewControllerDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilePreviewControllerDataSource.swift; sourceTree = ""; }; + 11628E3D1CA30C13008C097E /* QuickLookPreviewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickLookPreviewController.swift; sourceTree = ""; }; + 11628E621CA57B14008C097E /* ChatOutgoingFileCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatOutgoingFileCell.swift; sourceTree = ""; }; + 11628E6B1CA57B19008C097E /* ChatOutgoingFileCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatOutgoingFileCellModel.swift; sourceTree = ""; }; + 11628E711CA580ED008C097E /* ChatGenericFileCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatGenericFileCell.swift; sourceTree = ""; }; + 11628E741CA5811C008C097E /* ChatGenericFileCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatGenericFileCellModel.swift; sourceTree = ""; }; + 11628E771CA5819B008C097E /* LoadingImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingImageView.swift; sourceTree = ""; }; + 11628E7A1CA5BF1F008C097E /* ChangeAutodownloadImagesController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeAutodownloadImagesController.swift; sourceTree = ""; }; + 1164762F19794D3300DB20B8 /* Antidote.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Antidote.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1164763219794D3300DB20B8 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 1164763419794D3300DB20B8 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 1164763619794D3300DB20B8 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 1164763A19794D3300DB20B8 /* Antidote-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Antidote-Info.plist"; sourceTree = ""; }; + 1164764419794D3300DB20B8 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 1164764B19794D3300DB20B8 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 1164C8D71BC922EB00B91107 /* UserDefaultsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsManager.swift; sourceTree = ""; }; + 1164C8E61BC929C500B91107 /* LoginBaseController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginBaseController.swift; sourceTree = ""; }; + 1164C8E81BC929F700B91107 /* LoginChoiceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginChoiceController.swift; sourceTree = ""; }; + 1164C8EC1BC9801200B91107 /* RoundedButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundedButton.swift; sourceTree = ""; }; + 1164C8EF1BC982FF00B91107 /* UIImageExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtension.swift; sourceTree = ""; }; + 1164C8F21BC9880000B91107 /* LoginLogoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginLogoController.swift; sourceTree = ""; }; + 1164C8F51BC98F8300B91107 /* UIViewControllerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewControllerExtension.swift; sourceTree = ""; }; + 116513181C2C6C980066AF06 /* FriendCardController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FriendCardController.swift; sourceTree = ""; }; + 116513211C2C74940066AF06 /* StaticTableChatButtonsCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableChatButtonsCell.swift; sourceTree = ""; }; + 116513231C2C749E0066AF06 /* StaticTableChatButtonsCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableChatButtonsCellModel.swift; sourceTree = ""; }; + 116513251C2D636A0066AF06 /* NotificationWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationWindow.swift; sourceTree = ""; }; + 1166349F1C6E8D0F0072C980 /* ChatIncomingCallCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatIncomingCallCell.swift; sourceTree = ""; }; + 116634A81C6E8D1A0072C980 /* ChatIncomingCallCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatIncomingCallCellModel.swift; sourceTree = ""; }; + 116634AB1C6F407D0072C980 /* ChatOutgoingCallCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatOutgoingCallCell.swift; sourceTree = ""; }; + 116634AE1C6F40820072C980 /* ChatOutgoingCallCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatOutgoingCallCellModel.swift; sourceTree = ""; }; + 116634B11C7087A80072C980 /* UIApplicationExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIApplicationExtension.swift; sourceTree = ""; }; + 116634CE1C70E46C0072C980 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; + 116634D81C70F1260072C980 /* CopyLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CopyLabel.swift; sourceTree = ""; }; + 116634DF1C7B8D770072C980 /* StaticTableInfoCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableInfoCell.swift; sourceTree = ""; }; + 116634E81C7B8D7C0072C980 /* StaticTableInfoCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableInfoCellModel.swift; sourceTree = ""; }; + 116634EB1C7B90F20072C980 /* FriendRequestController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FriendRequestController.swift; sourceTree = ""; }; + 116634EE1C7B94530072C980 /* StaticTableMultiChoiceButtonCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableMultiChoiceButtonCell.swift; sourceTree = ""; }; + 116634F11C7B945A0072C980 /* StaticTableMultiChoiceButtonCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableMultiChoiceButtonCellModel.swift; sourceTree = ""; }; + 116DCE311CAAF47100B693EC /* TopCoordinatorProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopCoordinatorProtocol.swift; sourceTree = ""; }; + 116DCE3A1CAAF85300B693EC /* NSURLExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSURLExtension.swift; sourceTree = ""; }; + 1173EFC81BC5C6B300B88B7B /* SnapKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SnapKit.xcodeproj; path = submodules/SnapKit/SnapKit.xcodeproj; sourceTree = ""; }; + 1173EFE41BC5CF5D00B88B7B /* Yaml.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Yaml.xcodeproj; path = submodules/YamlSwift/Yaml.xcodeproj; sourceTree = ""; }; + 1173F0561BC5D94400B88B7B /* AntidoteTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AntidoteTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 1173F05A1BC5D94400B88B7B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1173F06D1BC5D9BA00B88B7B /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; + 1173F06F1BC5D9CA00B88B7B /* ThemeTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeTest.swift; sourceTree = ""; }; + 1173F0711BC5D9DA00B88B7B /* default-theme.yaml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "default-theme.yaml"; sourceTree = ""; }; + 11763ADF1D9C54640035B4C9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = cs; path = "cs.lproj/import-profile.html"; sourceTree = ""; }; + 11763AEA1D9C54680035B4C9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; + 11770D691CBC120C00D34D6E /* FriendSelectController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FriendSelectController.swift; sourceTree = ""; }; + 11770D761CBC1C7A00D34D6E /* UIAlertControllerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIAlertControllerExtension.swift; sourceTree = ""; }; + 11771E2B1CA5C3F200EC259E /* AutomationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutomationCoordinator.swift; sourceTree = ""; }; + 11771E351CA5C6E900EC259E /* Reach.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reach.swift; sourceTree = ""; }; + 1180FD9D1C38497A005F3EA1 /* NSDateFormatterExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSDateFormatterExtension.swift; sourceTree = ""; }; + 1180FD9F1C384E29005F3EA1 /* CallCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallCoordinator.swift; sourceTree = ""; }; + 1183BCAB1CA1B398000CD310 /* ChatIncomingFileCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatIncomingFileCell.swift; sourceTree = ""; }; + 1183BCB41CA1B3AE000CD310 /* ChatIncomingFileCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatIncomingFileCellModel.swift; sourceTree = ""; }; + 1183BCBF1CA1DB05000CD310 /* ProgressCircleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressCircleView.swift; sourceTree = ""; }; + 1183BCD61CA1FC5B000CD310 /* ChatProgressProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatProgressProtocol.swift; sourceTree = ""; }; + 1183BCDC1CA1FD55000CD310 /* ChatProgressBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatProgressBridge.swift; sourceTree = ""; }; + 1193AB351C1DEF2E006AA9E5 /* TextEditController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextEditController.swift; sourceTree = ""; }; + 1193AB411C1E2075006AA9E5 /* QRViewerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRViewerController.swift; sourceTree = ""; }; + 1193AB471C1F4164006AA9E5 /* FriendListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FriendListController.swift; sourceTree = ""; }; + 1193AB651C1F5694006AA9E5 /* BaseCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseCell.swift; sourceTree = ""; }; + 1193AB671C1F569A006AA9E5 /* BaseCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseCellModel.swift; sourceTree = ""; }; + 1193AB691C1F5C28006AA9E5 /* FriendListCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FriendListCell.swift; sourceTree = ""; }; + 1193AB6B1C1F5C31006AA9E5 /* FriendListCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FriendListCellModel.swift; sourceTree = ""; }; + 119AD1BD1BDED261000C5CB8 /* TextViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextViewController.swift; sourceTree = ""; }; + 119AD1C91BDED6E9000C5CB8 /* ErrorHandling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorHandling.swift; sourceTree = ""; }; + 119AD1CB1BDEDD9C000C5CB8 /* UIColorExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColorExtension.swift; sourceTree = ""; }; + 11A21EFA1C45BDB100E80A89 /* ChatPrivateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatPrivateController.swift; sourceTree = ""; }; + 11A21EFC1C45BDF200E80A89 /* ChatInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInputView.swift; sourceTree = ""; }; + 11AEBE701DA1876F00D04B59 /* FAQController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FAQController.swift; sourceTree = ""; }; + 11B252F91C4A614D0068F47C /* ChatIncomingTextCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatIncomingTextCell.swift; sourceTree = ""; }; + 11B252FD1C4A615E0068F47C /* ChatOutgoingTextCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatOutgoingTextCell.swift; sourceTree = ""; }; + 11B253011C4A61D00068F47C /* ChatMovableDateCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMovableDateCell.swift; sourceTree = ""; }; + 11B253031C4A61D50068F47C /* ChatMovableDateCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMovableDateCellModel.swift; sourceTree = ""; }; + 11B253131C4A79A50068F47C /* BubbleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleView.swift; sourceTree = ""; }; + 11B253491C4AD2200068F47C /* isotoxin_Calltone.aac */ = {isa = PBXFileReference; lastKnownFileType = file; path = isotoxin_Calltone.aac; sourceTree = ""; }; + 11B2534A1C4AD23A0068F47C /* isotoxin_Hangup.aac */ = {isa = PBXFileReference; lastKnownFileType = file; path = isotoxin_Hangup.aac; sourceTree = ""; }; + 11B2534B1C4AD23A0068F47C /* isotoxin_NewMessage.aac */ = {isa = PBXFileReference; lastKnownFileType = file; path = isotoxin_NewMessage.aac; sourceTree = ""; }; + 11B2534C1C4AD23A0068F47C /* isotoxin_Ringtone.aac */ = {isa = PBXFileReference; lastKnownFileType = file; path = isotoxin_Ringtone.aac; sourceTree = ""; }; + 11B2534D1C4AD23A0068F47C /* isotoxin_RingtoneWhileCall.aac */ = {isa = PBXFileReference; lastKnownFileType = file; path = isotoxin_RingtoneWhileCall.aac; sourceTree = ""; }; + 11B2534E1C4AD2550068F47C /* AudioPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = ""; }; + 11B253551C4AEA400068F47C /* ChatPrivateTitleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatPrivateTitleView.swift; sourceTree = ""; }; + 11B2535C1C4BACDB0068F47C /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; + 11B253651C4BAD3C0068F47C /* TabBarAbstractItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarAbstractItem.swift; sourceTree = ""; }; + 11B253691C4BAD560068F47C /* TabBarBadgeItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarBadgeItem.swift; sourceTree = ""; }; + 11B2536C1C4BAD5D0068F47C /* TabBarProfileItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarProfileItem.swift; sourceTree = ""; }; + 11B253A31C4EA8040068F47C /* InterfaceIdiom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterfaceIdiom.swift; sourceTree = ""; }; + 11B253AD1C4EC47D0068F47C /* IncompressibleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IncompressibleView.swift; sourceTree = ""; }; + 11B253B61C503FA10068F47C /* NSTimerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSTimerExtension.swift; sourceTree = ""; }; + 11B253FA1C52C0A10068F47C /* StaticTableSwitchCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableSwitchCell.swift; sourceTree = ""; }; + 11B253FD1C52C0AA0068F47C /* StaticTableSwitchCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableSwitchCellModel.swift; sourceTree = ""; }; + 11B9C6821BD598FC0083C2A5 /* OCTManagerConfigurationExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCTManagerConfigurationExtension.swift; sourceTree = ""; }; + 11B9C6961BD80B080083C2A5 /* FullscreenPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FullscreenPicker.swift; sourceTree = ""; }; + 11C33AC31DC961DC008DBC49 /* LoginChoiceViewSnapshotTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginChoiceViewSnapshotTest.swift; sourceTree = ""; }; + 11CFCD0A1C2745230046BD94 /* UserStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserStatus.swift; sourceTree = ""; }; + 11CFCD0C1C27488E0046BD94 /* ImageViewWithStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageViewWithStatus.swift; sourceTree = ""; }; + 11CFCD0E1C27499D0046BD94 /* UserStatusView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserStatusView.swift; sourceTree = ""; }; + 11CFCD101C2A02350046BD94 /* StaticBackgroundView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticBackgroundView.swift; sourceTree = ""; }; + 11D15D741D53CDAA0042FD4A /* br */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = br; path = "br.lproj/import-profile.html"; sourceTree = ""; }; + 11D15D751D53CDAA0042FD4A /* br */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = br; path = br.lproj/Localizable.strings; sourceTree = ""; }; + 11D15D761D53CECC0042FD4A /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = lt; path = "lt.lproj/import-profile.html"; sourceTree = ""; }; + 11D15D771D53CECC0042FD4A /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = ""; }; + 11D15D7A1D53D26A0042FD4A /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = fr; path = "fr.lproj/import-profile.html"; sourceTree = ""; }; + 11D15D7B1D53D26A0042FD4A /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + 11D17CE81DD11F10006B2910 /* dummy-photo.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "dummy-photo.jpg"; sourceTree = ""; }; + 11DDEAFC1D5FD9FE0000E2BE /* LaunchPlaceholderBoard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchPlaceholderBoard.storyboard; sourceTree = ""; }; + 11E6406C1C2D6C3500D24C6D /* ViewPassingGestures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewPassingGestures.swift; sourceTree = ""; }; + 11E640751C2DBAE200D24C6D /* AddFriendController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFriendController.swift; sourceTree = ""; }; + 11E640771C2DC15400D24C6D /* HelperFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelperFunctions.swift; sourceTree = ""; }; + 11F08D161DE0610B00F80F5F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D1E1DE0611700F80F5F /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D1F1DE0611800F80F5F /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D201DE0611800F80F5F /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D211DE0611900F80F5F /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D221DE0611A00F80F5F /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D231DE0611B00F80F5F /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D241DE0611B00F80F5F /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D251DE0611C00F80F5F /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D261DE0611D00F80F5F /* br */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = br; path = br.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D281DE0612200F80F5F /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D291DE0612200F80F5F /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D2B1DE0612400F80F5F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F08D2C1DE0612700F80F5F /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; + 11F276AD1D3EBEF700C613AA /* ChatBaseTextCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatBaseTextCell.swift; sourceTree = ""; }; + 11F276BA1D3EBF3000C613AA /* ChatBaseTextCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatBaseTextCellModel.swift; sourceTree = ""; }; + 11F83B751CADC9260074FE11 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = zh; path = "zh.lproj/import-profile.html"; sourceTree = ""; }; + 11F83B7C1CADC9270074FE11 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/Localizable.strings; sourceTree = ""; }; + 11F83B7D1CADC9330074FE11 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = da; path = "da.lproj/import-profile.html"; sourceTree = ""; }; + 11F83B7E1CADC9330074FE11 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 11F83B821CADC9460074FE11 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = de; path = "de.lproj/import-profile.html"; sourceTree = ""; }; + 11F83B831CADC9460074FE11 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 11F83B841CADC9790074FE11 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = pt; path = "pt.lproj/import-profile.html"; sourceTree = ""; }; + 11F83B851CADC9790074FE11 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; + 11F83B861CADC9820074FE11 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = es; path = "es.lproj/import-profile.html"; sourceTree = ""; }; + 11F83B871CADC9820074FE11 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 11F83BD31CAFB0F30074FE11 /* AntidoteTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AntidoteTests-Bridging-Header.h"; sourceTree = ""; }; + 11F83BDA1CAFB2A20074FE11 /* SwiftSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftSupport.swift; sourceTree = ""; }; + 11FA0ED21BC5842800F3DA5B /* Antidote-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Antidote-Bridging-Header.h"; sourceTree = ""; }; + 11FA0ED31BC5842800F3DA5B /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 11FA0ED91BC584D900F3DA5B /* AppCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; + 11FA0EDD1BC58DCC00F3DA5B /* CoordinatorProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoordinatorProtocol.swift; sourceTree = ""; }; + 11FA0EE01BC591FC00F3DA5B /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 11FA0EE61BC59B1400F3DA5B /* ActiveSessionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveSessionCoordinator.swift; sourceTree = ""; }; + 11FA0EE81BC5A66D00F3DA5B /* LoginCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginCoordinator.swift; sourceTree = ""; }; + 11FA0EEA1BC5A68600F3DA5B /* FriendsTabCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FriendsTabCoordinator.swift; sourceTree = ""; }; + 11FA0EEC1BC5A69000F3DA5B /* ProfileTabCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileTabCoordinator.swift; sourceTree = ""; }; + 11FA0EF01BC5A6A500F3DA5B /* SettingsTabCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTabCoordinator.swift; sourceTree = ""; }; + 11FA0EF21BC5A6AF00F3DA5B /* ChatsTabCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatsTabCoordinator.swift; sourceTree = ""; }; + 11FC10561D32E54A00CE863E /* Results.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Results.swift; sourceTree = ""; }; + 11FC10591D32EED000CE863E /* ResultsChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultsChange.swift; sourceTree = ""; }; + 11FC105C1D32F29700CE863E /* OCTSubmanagerObjectsExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCTSubmanagerObjectsExtension.swift; sourceTree = ""; }; + 22DCF57E8F127B60BD096DD1 /* Pods-ScreenshotsUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ScreenshotsUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ScreenshotsUITests/Pods-ScreenshotsUITests.release.xcconfig"; sourceTree = ""; }; + 2D5B916CEDBEC1B69CD1EFA4 /* Pods-Antidote.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Antidote.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Antidote/Pods-Antidote.debug.xcconfig"; sourceTree = ""; }; + 43C533DEE94E80C981FEC348 /* Pods-AntidoteTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AntidoteTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AntidoteTests/Pods-AntidoteTests.debug.xcconfig"; sourceTree = ""; }; + 4E3D0F9427C9751300D5A068 /* LinearProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinearProgressBar.swift; sourceTree = ""; }; + 4E4B267E27C0DCAE00E07A66 /* Call.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Call.swift; sourceTree = ""; }; + 4E4B268427C0DCB500E07A66 /* CallManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = ""; }; + 4E4B268A27C0DCBC00E07A66 /* ProviderDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProviderDelegate.swift; sourceTree = ""; }; + 4E4EEA1827DCEC67008B0E77 /* LocationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; + 4E4EEA1E27DCECD3008B0E77 /* Bus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bus.swift; sourceTree = ""; }; + 6A1B3D525D11BDD4F3E9B831 /* libPods-ScreenshotsUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ScreenshotsUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 72586F4D098584EF43024224 /* libPods-AntidoteTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AntidoteTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9C04BB6C1C17389200F58488 /* StaticTableBaseCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTableBaseCell.swift; sourceTree = ""; }; + 9C07151C1BCD2E5B003A27B5 /* PortraitNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PortraitNavigationController.swift; sourceTree = ""; }; + 9C07151E1BCD501D003A27B5 /* LoginFormController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginFormController.swift; sourceTree = ""; }; + 9C19367D1D79CF7E005EA0B2 /* EnterPinController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterPinController.swift; sourceTree = ""; }; + 9C1FEB281D8C10EB008C2ADE /* ProfileSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileSettings.swift; sourceTree = ""; }; + 9C2EF3701C4D4D5C006E7AB1 /* NotificationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationCoordinator.swift; sourceTree = ""; }; + 9C2EF3791C4E2DDE006E7AB1 /* ChatListTableManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListTableManager.swift; sourceTree = ""; }; + 9C3271861D79C19C00347490 /* PinAuthorizationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinAuthorizationCoordinator.swift; sourceTree = ""; }; + 9C7475BF1BC698110098B1A4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 9C7475C41BC698510098B1A4 /* StringExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; }; + 9C89D83C1DDB36770040C67B /* Antidote.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Antidote.entitlements; sourceTree = ""; }; + 9C9A07001BE793BA0003D6C7 /* ProfileMainController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileMainController.swift; sourceTree = ""; }; + 9CB1F9501D58CA4000105858 /* RunningCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RunningCoordinator.swift; sourceTree = ""; }; + 9CBBC7C01BDFC62700099A5E /* LoginCreateAccountController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginCreateAccountController.swift; sourceTree = ""; }; + 9CBBC7CC1BDFC6C600099A5E /* ExtendedTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedTextField.swift; sourceTree = ""; }; + 9CDC091A1C34081900DC0D63 /* FriendListDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FriendListDataSource.swift; sourceTree = ""; }; + 9CDC09241C34083100DC0D63 /* FriendListDataSourceTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FriendListDataSourceTest.swift; sourceTree = ""; }; + 9CDC092A1C34102F00DC0D63 /* ExceptionHandling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExceptionHandling.h; sourceTree = ""; }; + 9CDC092B1C34102F00DC0D63 /* ExceptionHandling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExceptionHandling.m; sourceTree = ""; }; + 9CDC093D1C3415DE00DC0D63 /* Antidote-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Antidote-Prefix.pch"; sourceTree = ""; }; + 9CDDB2051BD5376200B65D79 /* ProfileManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileManager.swift; sourceTree = ""; }; + 9CDE31E51E489B2700333E0D /* ChatInputViewManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInputViewManager.swift; sourceTree = ""; }; + 9CE0BDE51C45229500DCE357 /* ChatListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListController.swift; sourceTree = ""; }; + 9CE0BDEF1C4522BF00DCE357 /* ChatListCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListCell.swift; sourceTree = ""; }; + 9CE0BDF21C4522C800DCE357 /* ChatListCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListCellModel.swift; sourceTree = ""; }; + 9CEA6A1E1D957EFC0045F000 /* antidote-acknowledgements.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "antidote-acknowledgements.html"; sourceTree = ""; }; + 9CEE6AA11C4E679100A1ECB5 /* PrimaryIpadController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimaryIpadController.swift; sourceTree = ""; }; + 9CEE6B1E1C510A1E00A1ECB5 /* NotificationObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationObject.swift; sourceTree = ""; }; + 9CEE6B451C5289E200A1ECB5 /* SettingsMainController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsMainController.swift; sourceTree = ""; }; + 9CEE6B4E1C528AAA00A1ECB5 /* SettingsAboutController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAboutController.swift; sourceTree = ""; }; + 9CEE6B511C528AB600A1ECB5 /* SettingsAdvancedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAdvancedController.swift; sourceTree = ""; }; + AF2C929A279AB3F10094C08D /* pushextension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = pushextension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + AF2C929C279AB3F10094C08D /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + AF2C929E279AB3F10094C08D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AF6AB23329156C5800019362 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; + AF6AB23429156C6000019362 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + AF6AB23A29256F0000019362 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + AF6AB23529156EDA00019362 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/InfoPlist.strings; sourceTree = ""; }; + AF6AB23629156EDA00019362 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = ca; path = "ca.lproj/import-profile.html"; sourceTree = ""; }; + AF6AB23A29257F0000019362 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = ""; }; + AF6AB23729156EDC00019362 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + AF6AB23829156F0000019362 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = ""; }; + AF6AB23929156F0000019362 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = ko; path = "ko.lproj/import-profile.html"; sourceTree = ""; }; + AF6AB23A29156F0000019362 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + AF6AB23B29156F0000019362 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + AF6AB23C29156F0A00019362 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; + AF6AB23D29156F0A00019362 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = nb; path = "nb.lproj/import-profile.html"; sourceTree = ""; }; + AF6AB23A29258F0000019362 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + AF6AB23F29156F0B00019362 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/AppStoreLocalizable.strings; sourceTree = ""; }; + AF6AB24029156F1C00019362 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoPlist.strings"; sourceTree = ""; }; + AF6AB24129156F1C00019362 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = "pt-BR"; path = "pt-BR.lproj/import-profile.html"; sourceTree = ""; }; + AF6AB24229156F1E00019362 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/AppStoreLocalizable.strings"; sourceTree = ""; }; + AF6AB23A29259F0000019362 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + AF6AB24129156F1C00019362 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el"; path = "el.lproj/Localizable.strings"; sourceTree = ""; }; + AFA023F3274C069100FBFCC0 /* ConnectionStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionStatus.swift; sourceTree = ""; }; + AFA024012753CC9000FBFCC0 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + B3C17AF8CE3B4AD1B68F2B5B /* Pods-Antidote.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Antidote.release.xcconfig"; path = "Pods/Target Support Files/Pods-Antidote/Pods-Antidote.release.xcconfig"; sourceTree = ""; }; + CEC32120B965BE40E0029DB1 /* Pods-AntidoteTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AntidoteTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AntidoteTests/Pods-AntidoteTests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 111782A41DC64391000C1721 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8B8E9C45924FB18EB2C8044E /* libPods-ScreenshotsUITests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1164762C19794D3300DB20B8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1164763519794D3300DB20B8 /* CoreGraphics.framework in Frameworks */, + 1164763719794D3300DB20B8 /* UIKit.framework in Frameworks */, + 1164763319794D3300DB20B8 /* Foundation.framework in Frameworks */, + D4F896D05F7EAA7C321A2AA8 /* libPods-Antidote.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1173F0531BC5D94400B88B7B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 96166DAAD67996D5A6819984 /* libPods-AntidoteTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AF2C9297279AB3F10094C08D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1117826C1DC52BBB000C1721 /* Mocks */ = { + isa = PBXGroup; + children = ( + 111782771DC52BDB000C1721 /* OCTManagerMock.swift */, + 1117827A1DC52C3A000C1721 /* OCTSubmanagerBootstrapMock.swift */, + 1117827D1DC52CBA000C1721 /* OCTSubmanagerCallsMock.swift */, + 1117828C1DC531AD000C1721 /* OCTSubmanagerChatsMock.swift */, + 111782921DC53371000C1721 /* OCTSubmanagerFilesMock.swift */, + 111782951DC53458000C1721 /* OCTSubmanagerFriendsMock.swift */, + 111782981DC53545000C1721 /* OCTSubmanagerObjectsMock.swift */, + 1117829B1DC5363C000C1721 /* OCTSubmanagerUserMock.swift */, + ); + name = Mocks; + sourceTree = ""; + }; + 111782A81DC64391000C1721 /* ScreenshotsUITests */ = { + isa = PBXGroup; + children = ( + 111782AB1DC64391000C1721 /* Info.plist */, + 111782A91DC64391000C1721 /* ScreenshotsUITests.swift */, + 1117835E1DC64C89000C1721 /* ScreenshotUITests-Bridging-Header.h */, + 114D2D3F1E34D6E400662713 /* SnapshotHelper.swift */, + ); + path = ScreenshotsUITests; + sourceTree = ""; + }; + 112950891D6851B600C9CE0F /* Login */ = { + isa = PBXGroup; + children = ( + 11FA0EE81BC5A66D00F3DA5B /* LoginCoordinator.swift */, + 1129508A1D6851EB00C9CE0F /* LoginCreateAccountCoordinator.swift */, + ); + name = Login; + sourceTree = ""; + }; + 113F02FB1CAFCBA00009ABE1 /* Cells */ = { + isa = PBXGroup; + children = ( + 113F03131CAFD5EE0009ABE1 /* CellSnapshotTest.swift */, + 113F030B1CAFCCDD0009ABE1 /* ChatIncomingCallCellSnapshotTest.swift */, + 113F030E1CAFCE7B0009ABE1 /* ChatIncomingFileCellSnapshotTest.swift */, + 113F03181CAFDB0D0009ABE1 /* ChatIncomingTextCellSnapshotTest.swift */, + 113F031A1CAFDBF70009ABE1 /* ChatListCellSnapshotTest.swift */, + 113F031C1CAFE0DE0009ABE1 /* ChatMovableDateCellSnapshotTest.swift */, + 113F031E1CAFE2130009ABE1 /* ChatOutgoingCallCellSnapshotTest.swift */, + 113F03201CAFE2B50009ABE1 /* ChatOutgoingFileCellSnapshotTest.swift */, + 113F03221CAFE5190009ABE1 /* ChatOutgoingTextCellSnapshotTest.swift */, + ); + name = Cells; + sourceTree = ""; + }; + 113F030D1CAFCCE20009ABE1 /* SnapshotTests */ = { + isa = PBXGroup; + children = ( + 113F03101CAFD1610009ABE1 /* icon.png */, + 113F03091CAFCCB90009ABE1 /* SnapshotBaseTest.swift */, + 113F02FB1CAFCBA00009ABE1 /* Cells */, + 11C33AB81DC961AB008DBC49 /* Login */, + 113F03151CAFD9200009ABE1 /* Mocks */, + ); + name = SnapshotTests; + sourceTree = ""; + }; + 113F03151CAFD9200009ABE1 /* Mocks */ = { + isa = PBXGroup; + children = ( + 113F03161CAFD9370009ABE1 /* MockedChatProgressProtocol.swift */, + ); + name = Mocks; + sourceTree = ""; + }; + 1164762619794D3300DB20B8 = { + isa = PBXGroup; + children = ( + 1164763819794D3300DB20B8 /* Antidote */, + 1173F0571BC5D94400B88B7B /* AntidoteTests */, + 111782A81DC64391000C1721 /* ScreenshotsUITests */, + AF2C929B279AB3F10094C08D /* pushextension */, + 1164763119794D3300DB20B8 /* Frameworks */, + 1164763019794D3300DB20B8 /* Products */, + 1173EFB21BC5C40100B88B7B /* Submodules */, + D5C4D8B82C26B8F47A461D93 /* Pods */, + ); + sourceTree = ""; + }; + 1164763019794D3300DB20B8 /* Products */ = { + isa = PBXGroup; + children = ( + 1164762F19794D3300DB20B8 /* Antidote.app */, + 1173F0561BC5D94400B88B7B /* AntidoteTests.xctest */, + 111782A71DC64391000C1721 /* ScreenshotsUITests.xctest */, + AF2C929A279AB3F10094C08D /* pushextension.appex */, + ); + name = Products; + sourceTree = ""; + }; + 1164763119794D3300DB20B8 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1164763219794D3300DB20B8 /* Foundation.framework */, + 1164763419794D3300DB20B8 /* CoreGraphics.framework */, + 1164763619794D3300DB20B8 /* UIKit.framework */, + 1164764B19794D3300DB20B8 /* XCTest.framework */, + 034E57B3AE56E352BBAA0487 /* libPods-Antidote.a */, + 72586F4D098584EF43024224 /* libPods-AntidoteTests.a */, + 6A1B3D525D11BDD4F3E9B831 /* libPods-ScreenshotsUITests.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1164763819794D3300DB20B8 /* Antidote */ = { + isa = PBXGroup; + children = ( + 4E4B267D27C0DC9800E07A66 /* CallManagement */, + 9C89D83C1DDB36770040C67B /* Antidote.entitlements */, + F902D28D1ADED27A0070A3F5 /* Application */, + 1193AB561C1F565B006AA9E5 /* Cells */, + 1173F06C1BC5D9B000B88B7B /* Classes */, + 1164C8E31BC9279500B91107 /* Controllers */, + 11FA0ED61BC584A300F3DA5B /* Coordinators */, + 11CFCD091C2744E10046BD94 /* Enums */, + 9C7475C11BC698240098B1A4 /* Extensions */, + 11FA0EDF1BC591EA00F3DA5B /* Functions */, + 1117826C1DC52BBB000C1721 /* Mocks */, + 11771E341CA5C6D100EC259E /* Other */, + 119AD2081BEC718C000C5CB8 /* Sounds */, + 1164C8EA1BC97FF600B91107 /* Views */, + ); + path = Antidote; + sourceTree = ""; + }; + 1164C8E31BC9279500B91107 /* Controllers */ = { + isa = PBXGroup; + children = ( + 112422651BDD2032004D7926 /* KeyboardNotificationController.swift */, + 1160B6891D5FD8DE002DF75B /* LaunchPlaceholderController.swift */, + 11DDEAFC1D5FD9FE0000E2BE /* LaunchPlaceholderBoard.storyboard */, + 9C07151C1BCD2E5B003A27B5 /* PortraitNavigationController.swift */, + 119AD1CD1BDEE4A2000C5CB8 /* Login */, + 9C9A06F51BE792E60003D6C7 /* Running */, + ); + name = Controllers; + sourceTree = ""; + }; + 1164C8EA1BC97FF600B91107 /* Views */ = { + isa = PBXGroup; + children = ( + 11B253131C4A79A50068F47C /* BubbleView.swift */, + 112E4EE41C676553004312CF /* CallButton.swift */, + 1105B18C1EA09B1A0035B213 /* ChatFauxOfflineHeaderView.swift */, + 11A21EFC1C45BDF200E80A89 /* ChatInputView.swift */, + 11B253551C4AEA400068F47C /* ChatPrivateTitleView.swift */, + 113BBBA61EA88EAC00540E6C /* ChatTypingHeaderView.swift */, + 116634D81C70F1260072C980 /* CopyLabel.swift */, + 9CBBC7CC1BDFC6C600099A5E /* ExtendedTextField.swift */, + 11B9C6961BD80B080083C2A5 /* FullscreenPicker.swift */, + 11CFCD0C1C27488E0046BD94 /* ImageViewWithStatus.swift */, + 11B253AD1C4EC47D0068F47C /* IncompressibleView.swift */, + 113AD8561CA9C70F00D981B5 /* iPadFriendsButton.swift */, + 113AD8491CA97A3000D981B5 /* iPadNavigationView.swift */, + 11628E771CA5819B008C097E /* LoadingImageView.swift */, + 116513251C2D636A0066AF06 /* NotificationWindow.swift */, + 1109019A1D83417500BC5751 /* PinInputView.swift */, + 1183BCBF1CA1DB05000CD310 /* ProgressCircleView.swift */, + 113D564C1C2F437B00B3D3E8 /* QRScannerAimView.swift */, + 1164C8EC1BC9801200B91107 /* RoundedButton.swift */, + 11CFCD101C2A02350046BD94 /* StaticBackgroundView.swift */, + 11CFCD0E1C27499D0046BD94 /* UserStatusView.swift */, + 11E6406C1C2D6C3500D24C6D /* ViewPassingGestures.swift */, + ); + name = Views; + sourceTree = ""; + }; + 1173EFB21BC5C40100B88B7B /* Submodules */ = { + isa = PBXGroup; + children = ( + 1173EFC81BC5C6B300B88B7B /* SnapKit.xcodeproj */, + 1173EFE41BC5CF5D00B88B7B /* Yaml.xcodeproj */, + ); + name = Submodules; + sourceTree = ""; + }; + 1173EFC91BC5C6B300B88B7B /* Products */ = { + isa = PBXGroup; + children = ( + 1173EFD01BC5C6B300B88B7B /* SnapKit.framework */, + 1173EFD41BC5C6B300B88B7B /* SnapKit Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 1173EFE51BC5CF5D00B88B7B /* Products */ = { + isa = PBXGroup; + children = ( + 1173EFEC1BC5CF5D00B88B7B /* Yaml.framework */, + 1173EFEE1BC5CF5D00B88B7B /* Tests.xctest */, + 1173EFF01BC5CF5D00B88B7B /* Yaml.framework */, + 1173EFF21BC5CF5D00B88B7B /* Tests.xctest */, + 113F02F01CAFC9830009ABE1 /* Yaml.framework */, + 113F02F21CAFC9830009ABE1 /* Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 1173F0571BC5D94400B88B7B /* AntidoteTests */ = { + isa = PBXGroup; + children = ( + 11F83BD31CAFB0F30074FE11 /* AntidoteTests-Bridging-Header.h */, + 1173F05A1BC5D94400B88B7B /* Info.plist */, + 9CDC09241C34083100DC0D63 /* FriendListDataSourceTest.swift */, + 1160B6801D5FC7CC002DF75B /* KeychainManagerTests.swift */, + 11F83BDA1CAFB2A20074FE11 /* SwiftSupport.swift */, + 1173F06F1BC5D9CA00B88B7B /* ThemeTest.swift */, + 113F030D1CAFCCE20009ABE1 /* SnapshotTests */, + ); + path = AntidoteTests; + sourceTree = ""; + }; + 1173F06C1BC5D9B000B88B7B /* Classes */ = { + isa = PBXGroup; + children = ( + 113F03411CB31DD60009ABE1 /* AlertAudioPlayer.swift */, + 11B2534E1C4AD2550068F47C /* AudioPlayer.swift */, + 1156A4881C15FB2B005AC8C6 /* AvatarManager.swift */, + 115CE3E71EB06F54001C08A0 /* ChatBottomStatusViewManager.swift */, + 9CDE31E51E489B2700333E0D /* ChatInputViewManager.swift */, + 9C2EF3791C4E2DDE006E7AB1 /* ChatListTableManager.swift */, + 11628E311CA2FBD1008C097E /* FilePreviewControllerDataSource.swift */, + 9CDC091A1C34081900DC0D63 /* FriendListDataSource.swift */, + 112495361DE7ABFF00EF45C4 /* KeyboardObserver.swift */, + 1160B67D1D5F9993002DF75B /* KeychainManager.swift */, + 9CEE6B1E1C510A1E00A1ECB5 /* NotificationObject.swift */, + 9CDDB2051BD5376200B65D79 /* ProfileManager.swift */, + 9C1FEB281D8C10EB008C2ADE /* ProfileSettings.swift */, + 11FC10561D32E54A00CE863E /* Results.swift */, + 11FC10591D32EED000CE863E /* ResultsChange.swift */, + 11B253651C4BAD3C0068F47C /* TabBarAbstractItem.swift */, + 11B253691C4BAD560068F47C /* TabBarBadgeItem.swift */, + 11B2536C1C4BAD5D0068F47C /* TabBarProfileItem.swift */, + 1173F06D1BC5D9BA00B88B7B /* Theme.swift */, + 1117829E1DC53AB4000C1721 /* ToxFactory.swift */, + 1164C8D71BC922EB00B91107 /* UserDefaultsManager.swift */, + 4E3D0F9427C9751300D5A068 /* LinearProgressBar.swift */, + ); + name = Classes; + sourceTree = ""; + }; + 11771E341CA5C6D100EC259E /* Other */ = { + isa = PBXGroup; + children = ( + AFA024012753CC9000FBFCC0 /* GoogleService-Info.plist */, + 11771E351CA5C6E900EC259E /* Reach.swift */, + ); + name = Other; + sourceTree = ""; + }; + 1193AB561C1F565B006AA9E5 /* Cells */ = { + isa = PBXGroup; + children = ( + 1193AB651C1F5694006AA9E5 /* BaseCell.swift */, + 1193AB671C1F569A006AA9E5 /* BaseCellModel.swift */, + 11F276AD1D3EBEF700C613AA /* ChatBaseTextCell.swift */, + 11F276BA1D3EBF3000C613AA /* ChatBaseTextCellModel.swift */, + 1136605B1CDE5D5E0092C27A /* ChatEditable.swift */, + 11628E711CA580ED008C097E /* ChatGenericFileCell.swift */, + 11628E741CA5811C008C097E /* ChatGenericFileCellModel.swift */, + 1166349F1C6E8D0F0072C980 /* ChatIncomingCallCell.swift */, + 116634A81C6E8D1A0072C980 /* ChatIncomingCallCellModel.swift */, + 1183BCAB1CA1B398000CD310 /* ChatIncomingFileCell.swift */, + 1183BCB41CA1B3AE000CD310 /* ChatIncomingFileCellModel.swift */, + 11B252F91C4A614D0068F47C /* ChatIncomingTextCell.swift */, + 9CE0BDEF1C4522BF00DCE357 /* ChatListCell.swift */, + 9CE0BDF21C4522C800DCE357 /* ChatListCellModel.swift */, + 11B253011C4A61D00068F47C /* ChatMovableDateCell.swift */, + 11B253031C4A61D50068F47C /* ChatMovableDateCellModel.swift */, + 116634AB1C6F407D0072C980 /* ChatOutgoingCallCell.swift */, + 116634AE1C6F40820072C980 /* ChatOutgoingCallCellModel.swift */, + 11628E621CA57B14008C097E /* ChatOutgoingFileCell.swift */, + 11628E6B1CA57B19008C097E /* ChatOutgoingFileCellModel.swift */, + 11B252FD1C4A615E0068F47C /* ChatOutgoingTextCell.swift */, + 113187431DD63B6600E6FAA2 /* ChatOutgoingTextCellModel.swift */, + 1183BCDC1CA1FD55000CD310 /* ChatProgressBridge.swift */, + 1183BCD61CA1FC5B000CD310 /* ChatProgressProtocol.swift */, + 1193AB691C1F5C28006AA9E5 /* FriendListCell.swift */, + 1193AB6B1C1F5C31006AA9E5 /* FriendListCellModel.swift */, + 1156A4861C10D122005AC8C6 /* StaticTableAvatarCell.swift */, + 1156A4841C10D119005AC8C6 /* StaticTableAvatarCellModel.swift */, + 9C04BB6C1C17389200F58488 /* StaticTableBaseCell.swift */, + 1156A4781C0E478D005AC8C6 /* StaticTableBaseCellModel.swift */, + 1156A4801C0FA0A4005AC8C6 /* StaticTableButtonCell.swift */, + 1156A47A1C0F6178005AC8C6 /* StaticTableButtonCellModel.swift */, + 116513211C2C74940066AF06 /* StaticTableChatButtonsCell.swift */, + 116513231C2C749E0066AF06 /* StaticTableChatButtonsCellModel.swift */, + 115DFB891C177F5C00F18DB5 /* StaticTableDefaultCell.swift */, + 115DFB951C177F6500F18DB5 /* StaticTableDefaultCellModel.swift */, + 116634DF1C7B8D770072C980 /* StaticTableInfoCell.swift */, + 116634E81C7B8D7C0072C980 /* StaticTableInfoCellModel.swift */, + 116634EE1C7B94530072C980 /* StaticTableMultiChoiceButtonCell.swift */, + 116634F11C7B945A0072C980 /* StaticTableMultiChoiceButtonCellModel.swift */, + 1156A47C1C0F632C005AC8C6 /* StaticTableSelectableCellModel.swift */, + 11B253FA1C52C0A10068F47C /* StaticTableSwitchCell.swift */, + 11B253FD1C52C0AA0068F47C /* StaticTableSwitchCellModel.swift */, + ); + name = Cells; + sourceTree = ""; + }; + 119AD1CD1BDEE4A2000C5CB8 /* Login */ = { + isa = PBXGroup; + children = ( + 1164C8E61BC929C500B91107 /* LoginBaseController.swift */, + 1164C8E81BC929F700B91107 /* LoginChoiceController.swift */, + 9CBBC7C01BDFC62700099A5E /* LoginCreateAccountController.swift */, + 112950861D63AFD800C9CE0F /* LoginCreatePasswordController.swift */, + 9C07151E1BCD501D003A27B5 /* LoginFormController.swift */, + 112950831D63AFCE00C9CE0F /* LoginGenericCreateController.swift */, + 1164C8F21BC9880000B91107 /* LoginLogoController.swift */, + ); + name = Login; + sourceTree = ""; + }; + 119AD2081BEC718C000C5CB8 /* Sounds */ = { + isa = PBXGroup; + children = ( + 11B253491C4AD2200068F47C /* isotoxin_Calltone.aac */, + 11B2534A1C4AD23A0068F47C /* isotoxin_Hangup.aac */, + 11B2534B1C4AD23A0068F47C /* isotoxin_NewMessage.aac */, + 11B2534C1C4AD23A0068F47C /* isotoxin_Ringtone.aac */, + 11B2534D1C4AD23A0068F47C /* isotoxin_RingtoneWhileCall.aac */, + ); + name = Sounds; + sourceTree = ""; + }; + 11C33AB81DC961AB008DBC49 /* Login */ = { + isa = PBXGroup; + children = ( + 11C33AC31DC961DC008DBC49 /* LoginChoiceViewSnapshotTest.swift */, + ); + name = Login; + sourceTree = ""; + }; + 11CFCD091C2744E10046BD94 /* Enums */ = { + isa = PBXGroup; + children = ( + 11B253A31C4EA8040068F47C /* InterfaceIdiom.swift */, + AFA023F3274C069100FBFCC0 /* ConnectionStatus.swift */, + 11CFCD0A1C2745230046BD94 /* UserStatus.swift */, + ); + name = Enums; + sourceTree = ""; + }; + 11FA0ED61BC584A300F3DA5B /* Coordinators */ = { + isa = PBXGroup; + children = ( + 11FA0ED91BC584D900F3DA5B /* AppCoordinator.swift */, + 11FA0EDD1BC58DCC00F3DA5B /* CoordinatorProtocol.swift */, + 116DCE311CAAF47100B693EC /* TopCoordinatorProtocol.swift */, + 112950891D6851B600C9CE0F /* Login */, + 11FA0EE31BC59A9B00F3DA5B /* Running */, + ); + name = Coordinators; + sourceTree = ""; + }; + 11FA0EDF1BC591EA00F3DA5B /* Functions */ = { + isa = PBXGroup; + children = ( + 119AD1C91BDED6E9000C5CB8 /* ErrorHandling.swift */, + 9CDC092A1C34102F00DC0D63 /* ExceptionHandling.h */, + 9CDC092B1C34102F00DC0D63 /* ExceptionHandling.m */, + 11E640771C2DC15400D24C6D /* HelperFunctions.swift */, + 11FA0EE01BC591FC00F3DA5B /* Logger.swift */, + ); + name = Functions; + sourceTree = ""; + }; + 11FA0EE31BC59A9B00F3DA5B /* Running */ = { + isa = PBXGroup; + children = ( + 11FA0EE61BC59B1400F3DA5B /* ActiveSessionCoordinator.swift */, + 110E49DF1BF925FD00D1FE6F /* ActiveSessionNavigationCoordinator.swift */, + 11771E2B1CA5C3F200EC259E /* AutomationCoordinator.swift */, + 1180FD9F1C384E29005F3EA1 /* CallCoordinator.swift */, + 11FA0EF21BC5A6AF00F3DA5B /* ChatsTabCoordinator.swift */, + 11FA0EEA1BC5A68600F3DA5B /* FriendsTabCoordinator.swift */, + 9C2EF3701C4D4D5C006E7AB1 /* NotificationCoordinator.swift */, + 9C3271861D79C19C00347490 /* PinAuthorizationCoordinator.swift */, + 11FA0EEC1BC5A69000F3DA5B /* ProfileTabCoordinator.swift */, + 9CB1F9501D58CA4000105858 /* RunningCoordinator.swift */, + 11FA0EF01BC5A6A500F3DA5B /* SettingsTabCoordinator.swift */, + ); + name = Running; + sourceTree = ""; + }; + 4E4B267D27C0DC9800E07A66 /* CallManagement */ = { + isa = PBXGroup; + children = ( + 4E4B268A27C0DCBC00E07A66 /* ProviderDelegate.swift */, + 4E4B268427C0DCB500E07A66 /* CallManager.swift */, + 4E4B267E27C0DCAE00E07A66 /* Call.swift */, + ); + path = CallManagement; + sourceTree = ""; + }; + 9C7475C11BC698240098B1A4 /* Extensions */ = { + isa = PBXGroup; + children = ( + 1180FD9D1C38497A005F3EA1 /* NSDateFormatterExtension.swift */, + 11B253B61C503FA10068F47C /* NSTimerExtension.swift */, + 116DCE3A1CAAF85300B693EC /* NSURLExtension.swift */, + 11B9C6821BD598FC0083C2A5 /* OCTManagerConfigurationExtension.swift */, + 11FC105C1D32F29700CE863E /* OCTSubmanagerObjectsExtension.swift */, + 110E078E1EB0756C00B2CA9D /* ResultsExtension.swift */, + 9C7475C41BC698510098B1A4 /* StringExtension.swift */, + 11770D761CBC1C7A00D34D6E /* UIAlertControllerExtension.swift */, + 116634B11C7087A80072C980 /* UIApplicationExtension.swift */, + 119AD1CB1BDEDD9C000C5CB8 /* UIColorExtension.swift */, + 113F03321CB2E9C20009ABE1 /* UIFontExtension.swift */, + 1164C8EF1BC982FF00B91107 /* UIImageExtension.swift */, + 1164C8F51BC98F8300B91107 /* UIViewControllerExtension.swift */, + ); + name = Extensions; + sourceTree = ""; + }; + 9C9A06F51BE792E60003D6C7 /* Running */ = { + isa = PBXGroup; + children = ( + 11E640751C2DBAE200D24C6D /* AddFriendController.swift */, + 112E4EDE1C675C58004312CF /* CallActiveController.swift */, + 114962941C64051A001E5435 /* CallBaseController.swift */, + 112E4ED51C668C02004312CF /* CallIncomingController.swift */, + 11628E7A1CA5BF1F008C097E /* ChangeAutodownloadImagesController.swift */, + 114962911C5E1BE0001E5435 /* ChangePasswordController.swift */, + 116046181D8D434B002287C8 /* ChangePinTimeoutController.swift */, + 1149626B1C5CE3DF001E5435 /* ChangeUserStatusController.swift */, + 9CE0BDE51C45229500DCE357 /* ChatListController.swift */, + 11A21EFA1C45BDB100E80A89 /* ChatPrivateController.swift */, + 9C19367D1D79CF7E005EA0B2 /* EnterPinController.swift */, + 11AEBE701DA1876F00D04B59 /* FAQController.swift */, + 116513181C2C6C980066AF06 /* FriendCardController.swift */, + 1193AB471C1F4164006AA9E5 /* FriendListController.swift */, + 116634EB1C7B90F20072C980 /* FriendRequestController.swift */, + 11770D691CBC120C00D34D6E /* FriendSelectController.swift */, + 9CEE6AA11C4E679100A1ECB5 /* PrimaryIpadController.swift */, + 114962741C5CEB0B001E5435 /* ProfileDetailsController.swift */, + 9C9A07001BE793BA0003D6C7 /* ProfileMainController.swift */, + 113D56551C2F459E00B3D3E8 /* QRScannerController.swift */, + 1193AB411C1E2075006AA9E5 /* QRViewerController.swift */, + 11628E3D1CA30C13008C097E /* QuickLookPreviewController.swift */, + 9CEE6B4E1C528AAA00A1ECB5 /* SettingsAboutController.swift */, + 9CEE6B511C528AB600A1ECB5 /* SettingsAdvancedController.swift */, + 9CEE6B451C5289E200A1ECB5 /* SettingsMainController.swift */, + 110E425B1C00DC5E001A3CA2 /* StaticTableController.swift */, + 11B2535C1C4BACDB0068F47C /* TabBarController.swift */, + 1193AB351C1DEF2E006AA9E5 /* TextEditController.swift */, + 119AD1BD1BDED261000C5CB8 /* TextViewController.swift */, + ); + name = Running; + sourceTree = ""; + }; + AF2C929B279AB3F10094C08D /* pushextension */ = { + isa = PBXGroup; + children = ( + AF2C929C279AB3F10094C08D /* NotificationService.swift */, + AF2C929E279AB3F10094C08D /* Info.plist */, + ); + path = pushextension; + sourceTree = ""; + }; + D5C4D8B82C26B8F47A461D93 /* Pods */ = { + isa = PBXGroup; + children = ( + 2D5B916CEDBEC1B69CD1EFA4 /* Pods-Antidote.debug.xcconfig */, + B3C17AF8CE3B4AD1B68F2B5B /* Pods-Antidote.release.xcconfig */, + 43C533DEE94E80C981FEC348 /* Pods-AntidoteTests.debug.xcconfig */, + CEC32120B965BE40E0029DB1 /* Pods-AntidoteTests.release.xcconfig */, + 02404DE3D3BB1BF461C45EA0 /* Pods-ScreenshotsUITests.debug.xcconfig */, + 22DCF57E8F127B60BD096DD1 /* Pods-ScreenshotsUITests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + F902D28D1ADED27A0070A3F5 /* Application */ = { + isa = PBXGroup; + children = ( + 4E4EEA1E27DCECD3008B0E77 /* Bus.swift */, + 4E4EEA1827DCEC67008B0E77 /* LocationManager.swift */, + 11FA0ED21BC5842800F3DA5B /* Antidote-Bridging-Header.h */, + 9CDC093D1C3415DE00DC0D63 /* Antidote-Prefix.pch */, + 1164763A19794D3300DB20B8 /* Antidote-Info.plist */, + 11F08D171DE0610B00F80F5F /* InfoPlist.strings */, + 11FA0ED31BC5842800F3DA5B /* AppDelegate.swift */, + 1164764419794D3300DB20B8 /* Images.xcassets */, + 11D17CE81DD11F10006B2910 /* dummy-photo.jpg */, + 9CEA6A1E1D957EFC0045F000 /* antidote-acknowledgements.html */, + 1131D6C71CA9D8BC00B4531C /* import-profile.html */, + 1173F0711BC5D9DA00B88B7B /* default-theme.yaml */, + 9C7475C01BC698110098B1A4 /* Localizable.strings */, + 113E96461E4BB302000282FC /* AppStoreLocalizable.strings */, + 116634CE1C70E46C0072C980 /* Launch Screen.storyboard */, + ); + name = Application; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 111782A61DC64391000C1721 /* ScreenshotsUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 111782BA1DC64391000C1721 /* Build configuration list for PBXNativeTarget "ScreenshotsUITests" */; + buildPhases = ( + 6544AEF82D801AA2208FA2C0 /* [CP] Check Pods Manifest.lock */, + 111782A31DC64391000C1721 /* Sources */, + 111782A41DC64391000C1721 /* Frameworks */, + 111782A51DC64391000C1721 /* Resources */, + 008EF89EF3FF486967EBDAF6 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 111782AD1DC64391000C1721 /* PBXTargetDependency */, + ); + name = ScreenshotsUITests; + productName = ScreenshotsUITests; + productReference = 111782A71DC64391000C1721 /* ScreenshotsUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; + 1164762E19794D3300DB20B8 /* Antidote */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1164765B19794D3300DB20B8 /* Build configuration list for PBXNativeTarget "Antidote" */; + buildPhases = ( + 0E40AAC10D854CCE59CF1B7F /* [CP] Check Pods Manifest.lock */, + 1164762B19794D3300DB20B8 /* Sources */, + 1164762C19794D3300DB20B8 /* Frameworks */, + 1164762D19794D3300DB20B8 /* Resources */, + F334EF7D7E56B63481570925 /* [CP] Copy Pods Resources */, + AF2C92A2279AB3F10094C08D /* Embed App Extensions */, + ); + buildRules = ( + ); + dependencies = ( + AF2C92A0279AB3F10094C08D /* PBXTargetDependency */, + ); + name = Antidote; + productName = Antidote; + productReference = 1164762F19794D3300DB20B8 /* Antidote.app */; + productType = "com.apple.product-type.application"; + }; + 1173F0551BC5D94400B88B7B /* AntidoteTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1173F05D1BC5D94400B88B7B /* Build configuration list for PBXNativeTarget "AntidoteTests" */; + buildPhases = ( + 0684EDB5CA4B84C1AF4AF8A4 /* [CP] Check Pods Manifest.lock */, + 1173F0521BC5D94400B88B7B /* Sources */, + 1173F0531BC5D94400B88B7B /* Frameworks */, + 1173F0541BC5D94400B88B7B /* Resources */, + B98298617DAB39F4AF6E0CCC /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 1173F05C1BC5D94400B88B7B /* PBXTargetDependency */, + ); + name = AntidoteTests; + productName = AntidoteTests; + productReference = 1173F0561BC5D94400B88B7B /* AntidoteTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + AF2C9299279AB3F10094C08D /* pushextension */ = { + isa = PBXNativeTarget; + buildConfigurationList = AF2C92A5279AB3F10094C08D /* Build configuration list for PBXNativeTarget "pushextension" */; + buildPhases = ( + AF2C9296279AB3F10094C08D /* Sources */, + AF2C9297279AB3F10094C08D /* Frameworks */, + AF2C9298279AB3F10094C08D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = pushextension; + productName = pushextension; + productReference = AF2C929A279AB3F10094C08D /* pushextension.appex */; + productType = "com.apple.product-type.app-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 1164762719794D3300DB20B8 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1250; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = zoxcore; + TargetAttributes = { + 111782A61DC64391000C1721 = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0820; + TestTargetID = 1164762E19794D3300DB20B8; + }; + 1164762E19794D3300DB20B8 = { + DevelopmentTeam = Y46L589C5C; + LastSwiftMigration = 0930; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Keychain = { + enabled = 1; + }; + }; + }; + 1173F0551BC5D94400B88B7B = { + CreatedOnToolsVersion = 7.0; + LastSwiftMigration = 0930; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.BackgroundModes = { + enabled = 1; + }; + }; + TestTargetID = 1164762E19794D3300DB20B8; + }; + AF2C9299279AB3F10094C08D = { + CreatedOnToolsVersion = 12.5; + DevelopmentTeam = Y46L589C5C; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 1164762A19794D3300DB20B8 /* Build configuration list for PBXProject "Antidote" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ru, + zh, + da, + de, + pt, + es, + ar, + br, + lt, + fr, + cs, + nl, + pl, + Base, + it, + ca, + ko, + nb, + "pt-BR", + el, + ); + mainGroup = 1164762619794D3300DB20B8; + productRefGroup = 1164763019794D3300DB20B8 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 1173EFC91BC5C6B300B88B7B /* Products */; + ProjectRef = 1173EFC81BC5C6B300B88B7B /* SnapKit.xcodeproj */; + }, + { + ProductGroup = 1173EFE51BC5CF5D00B88B7B /* Products */; + ProjectRef = 1173EFE41BC5CF5D00B88B7B /* Yaml.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 1164762E19794D3300DB20B8 /* Antidote */, + 1173F0551BC5D94400B88B7B /* AntidoteTests */, + 111782A61DC64391000C1721 /* ScreenshotsUITests */, + AF2C9299279AB3F10094C08D /* pushextension */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 113F02F01CAFC9830009ABE1 /* Yaml.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Yaml.framework; + remoteRef = 113F02EF1CAFC9830009ABE1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 113F02F21CAFC9830009ABE1 /* Tests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = Tests.xctest; + remoteRef = 113F02F11CAFC9830009ABE1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 1173EFD01BC5C6B300B88B7B /* SnapKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = SnapKit.framework; + remoteRef = 1173EFCF1BC5C6B300B88B7B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 1173EFD41BC5C6B300B88B7B /* SnapKit Tests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = "SnapKit Tests.xctest"; + remoteRef = 1173EFD31BC5C6B300B88B7B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 1173EFEC1BC5CF5D00B88B7B /* Yaml.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Yaml.framework; + remoteRef = 1173EFEB1BC5CF5D00B88B7B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 1173EFEE1BC5CF5D00B88B7B /* Tests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = Tests.xctest; + remoteRef = 1173EFED1BC5CF5D00B88B7B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 1173EFF01BC5CF5D00B88B7B /* Yaml.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Yaml.framework; + remoteRef = 1173EFEF1BC5CF5D00B88B7B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 1173EFF21BC5CF5D00B88B7B /* Tests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = Tests.xctest; + remoteRef = 1173EFF11BC5CF5D00B88B7B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 111782A51DC64391000C1721 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 115C3BD61DC6D2B900903A47 /* Localizable.strings in Resources */, + 11F08D151DE0610B00F80F5F /* InfoPlist.strings in Resources */, + 11D17CDD1DD11E58006B2910 /* Images.xcassets in Resources */, + 1143E4331DCE1A5600BE7250 /* import-profile.html in Resources */, + 111783101DC647BD000C1721 /* LaunchPlaceholderBoard.storyboard in Resources */, + 113E96441E4BB302000282FC /* AppStoreLocalizable.strings in Resources */, + 11D17CEA1DD11F10006B2910 /* dummy-photo.jpg in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1164762D19794D3300DB20B8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 11D17CE91DD11F10006B2910 /* dummy-photo.jpg in Resources */, + 11F08D131DE0610B00F80F5F /* InfoPlist.strings in Resources */, + 113E96421E4BB302000282FC /* AppStoreLocalizable.strings in Resources */, + 11B253541C4AD9CF0068F47C /* isotoxin_RingtoneWhileCall.aac in Resources */, + 11B253501C4AD9CF0068F47C /* isotoxin_Calltone.aac in Resources */, + 11DDEAFD1D5FD9FE0000E2BE /* LaunchPlaceholderBoard.storyboard in Resources */, + 11B253531C4AD9CF0068F47C /* isotoxin_Ringtone.aac in Resources */, + 9C7475BE1BC698110098B1A4 /* Localizable.strings in Resources */, + 9CEA6A1F1D957EFC0045F000 /* antidote-acknowledgements.html in Resources */, + AFA024022753CC9000FBFCC0 /* GoogleService-Info.plist in Resources */, + 1173F0721BC5D9DA00B88B7B /* default-theme.yaml in Resources */, + 116634CF1C70E46C0072C980 /* Launch Screen.storyboard in Resources */, + 1131D6C51CA9D8BC00B4531C /* import-profile.html in Resources */, + 11B253521C4AD9CF0068F47C /* isotoxin_NewMessage.aac in Resources */, + 1164764519794D3300DB20B8 /* Images.xcassets in Resources */, + 11B253511C4AD9CF0068F47C /* isotoxin_Hangup.aac in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1173F0541BC5D94400B88B7B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 113F03111CAFD1610009ABE1 /* icon.png in Resources */, + 11F08D141DE0610B00F80F5F /* InfoPlist.strings in Resources */, + 113E96431E4BB302000282FC /* AppStoreLocalizable.strings in Resources */, + 113F03081CAFCC9D0009ABE1 /* default-theme.yaml in Resources */, + 9CEA6A201D957EFC0045F000 /* antidote-acknowledgements.html in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AF2C9298279AB3F10094C08D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 008EF89EF3FF486967EBDAF6 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ScreenshotsUITests/Pods-ScreenshotsUITests-resources.sh", + "${PODS_ROOT}/JGProgressHUD/JGProgressHUD/JGProgressHUD/JGProgressHUD Resources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/objcTox/objcTox.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/JGProgressHUD Resources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/objcTox.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ScreenshotsUITests/Pods-ScreenshotsUITests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 0684EDB5CA4B84C1AF4AF8A4 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-AntidoteTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 0E40AAC10D854CCE59CF1B7F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Antidote-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 6544AEF82D801AA2208FA2C0 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ScreenshotsUITests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B98298617DAB39F4AF6E0CCC /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-AntidoteTests/Pods-AntidoteTests-resources.sh", + "${PODS_ROOT}/JGProgressHUD/JGProgressHUD/JGProgressHUD/JGProgressHUD Resources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/objcTox/objcTox.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/JGProgressHUD Resources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/objcTox.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AntidoteTests/Pods-AntidoteTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + F334EF7D7E56B63481570925 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Antidote/Pods-Antidote-resources.sh", + "${PODS_ROOT}/JGProgressHUD/JGProgressHUD/JGProgressHUD/JGProgressHUD Resources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/objcTox/objcTox.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/JGProgressHUD Resources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/objcTox.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Antidote/Pods-Antidote-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 111782A31DC64391000C1721 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 111782F41DC64796000C1721 /* StaticTableDefaultCellModel.swift in Sources */, + 111783041DC6479E000C1721 /* ProfileManager.swift in Sources */, + 111783411DC64810000C1721 /* ExtendedTextField.swift in Sources */, + 111782BB1DC643A3000C1721 /* OCTManagerMock.swift in Sources */, + 1117830E1DC647BD000C1721 /* KeyboardNotificationController.swift in Sources */, + 1117830B1DC6479E000C1721 /* Theme.swift in Sources */, + 1117833F1DC64810000C1721 /* ChatPrivateTitleView.swift in Sources */, + 111783391DC647F8000C1721 /* HelperFunctions.swift in Sources */, + 1117834D1DC64810000C1721 /* StaticBackgroundView.swift in Sources */, + 113BBBA91EA88EAC00540E6C /* ChatTypingHeaderView.swift in Sources */, + 111782FD1DC6479E000C1721 /* AudioPlayer.swift in Sources */, + 111782F31DC64796000C1721 /* StaticTableDefaultCell.swift in Sources */, + 111782D41DC64796000C1721 /* ChatBaseTextCell.swift in Sources */, + 111783441DC64810000C1721 /* IncompressibleView.swift in Sources */, + 1117833B1DC647FF000C1721 /* Reach.swift in Sources */, + 1117832F1DC647E4000C1721 /* OCTSubmanagerObjectsExtension.swift in Sources */, + 111782C11DC643A3000C1721 /* OCTSubmanagerFriendsMock.swift in Sources */, + 111782FF1DC6479E000C1721 /* ChatListTableManager.swift in Sources */, + 1117832D1DC647E4000C1721 /* NSURLExtension.swift in Sources */, + 111783321DC647E4000C1721 /* UIApplicationExtension.swift in Sources */, + 111782EB1DC64796000C1721 /* StaticTableAvatarCell.swift in Sources */, + 111782F61DC64796000C1721 /* StaticTableInfoCellModel.swift in Sources */, + 111782F21DC64796000C1721 /* StaticTableChatButtonsCellModel.swift in Sources */, + 111783011DC6479E000C1721 /* FriendListDataSource.swift in Sources */, + 114D2D401E34D6E400662713 /* SnapshotHelper.swift in Sources */, + 111783291DC647DF000C1721 /* InterfaceIdiom.swift in Sources */, + 111782DC1DC64796000C1721 /* ChatIncomingFileCellModel.swift in Sources */, + 111782D91DC64796000C1721 /* ChatIncomingCallCell.swift in Sources */, + 1117832C1DC647E4000C1721 /* NSTimerExtension.swift in Sources */, + 111783461DC64810000C1721 /* iPadNavigationView.swift in Sources */, + 111783251DC647D9000C1721 /* PinAuthorizationCoordinator.swift in Sources */, + 111782E71DC64796000C1721 /* ChatProgressBridge.swift in Sources */, + 111783811DC64D75000C1721 /* SettingsMainController.swift in Sources */, + 1117834F1DC64810000C1721 /* ViewPassingGestures.swift in Sources */, + 111783281DC647D9000C1721 /* SettingsTabCoordinator.swift in Sources */, + 1117831C1DC647D4000C1721 /* LoginCoordinator.swift in Sources */, + 111782D81DC64796000C1721 /* ChatGenericFileCellModel.swift in Sources */, + 1117834B1DC64810000C1721 /* QRScannerAimView.swift in Sources */, + 111783071DC6479E000C1721 /* ResultsChange.swift in Sources */, + 1117830F1DC647BD000C1721 /* LaunchPlaceholderController.swift in Sources */, + 111783221DC647D9000C1721 /* ChatsTabCoordinator.swift in Sources */, + 115CE3EA1EB06F54001C08A0 /* ChatBottomStatusViewManager.swift in Sources */, + 111783701DC64D75000C1721 /* ChangePinTimeoutController.swift in Sources */, + 111783491DC64810000C1721 /* PinInputView.swift in Sources */, + 111783201DC647D9000C1721 /* AutomationCoordinator.swift in Sources */, + 111783271DC647D9000C1721 /* RunningCoordinator.swift in Sources */, + 111782EF1DC64796000C1721 /* StaticTableButtonCell.swift in Sources */, + 1117834E1DC64810000C1721 /* UserStatusView.swift in Sources */, + 111783231DC647D9000C1721 /* FriendsTabCoordinator.swift in Sources */, + 111782E61DC64796000C1721 /* ChatOutgoingTextCell.swift in Sources */, + 1117830C1DC6479E000C1721 /* ToxFactory.swift in Sources */, + 1117831D1DC647D4000C1721 /* LoginCreateAccountCoordinator.swift in Sources */, + 111782C01DC643A3000C1721 /* OCTSubmanagerFilesMock.swift in Sources */, + 111783791DC64D75000C1721 /* FriendSelectController.swift in Sources */, + 111783691DC64D50000C1721 /* QuickLookPreviewController.swift in Sources */, + 1117832B1DC647E4000C1721 /* NSDateFormatterExtension.swift in Sources */, + 111782FE1DC6479E000C1721 /* AvatarManager.swift in Sources */, + 111783841DC64D75000C1721 /* TextEditController.swift in Sources */, + 111783471DC64810000C1721 /* LoadingImageView.swift in Sources */, + 111783021DC6479E000C1721 /* KeychainManager.swift in Sources */, + 111783771DC64D75000C1721 /* FriendListController.swift in Sources */, + 1124953F1DE7AC0400EF45C4 /* KeyboardObserver.swift in Sources */, + 111783261DC647D9000C1721 /* ProfileTabCoordinator.swift in Sources */, + 1117836D1DC64D75000C1721 /* CallActiveController.swift in Sources */, + 111782EA1DC64796000C1721 /* FriendListCellModel.swift in Sources */, + 111782D71DC64796000C1721 /* ChatGenericFileCell.swift in Sources */, + 111783381DC647F8000C1721 /* ExceptionHandling.m in Sources */, + 1117837D1DC64D75000C1721 /* QRScannerController.swift in Sources */, + 1105B18F1EA09B1A0035B213 /* ChatFauxOfflineHeaderView.swift in Sources */, + 111783091DC6479E000C1721 /* TabBarBadgeItem.swift in Sources */, + 1117836B1DC64D68000C1721 /* CallBaseController.swift in Sources */, + 111782DA1DC64796000C1721 /* ChatIncomingCallCellModel.swift in Sources */, + 110E07911EB0756C00B2CA9D /* ResultsExtension.swift in Sources */, + 111783451DC64810000C1721 /* iPadFriendsButton.swift in Sources */, + 111782DE1DC64796000C1721 /* ChatListCell.swift in Sources */, + 111782FB1DC64796000C1721 /* StaticTableSwitchCellModel.swift in Sources */, + 111783711DC64D75000C1721 /* ChangeUserStatusController.swift in Sources */, + 1117832E1DC647E4000C1721 /* OCTManagerConfigurationExtension.swift in Sources */, + 111783151DC647C1000C1721 /* LoginCreatePasswordController.swift in Sources */, + 1117836F1DC64D75000C1721 /* ChangePasswordController.swift in Sources */, + 1117834C1DC64810000C1721 /* RoundedButton.swift in Sources */, + 111783341DC647E4000C1721 /* UIFontExtension.swift in Sources */, + 1117834A1DC64810000C1721 /* ProgressCircleView.swift in Sources */, + 111783061DC6479E000C1721 /* Results.swift in Sources */, + 113187461DD63B6600E6FAA2 /* ChatOutgoingTextCellModel.swift in Sources */, + 111783421DC64810000C1721 /* FullscreenPicker.swift in Sources */, + 111783751DC64D75000C1721 /* FAQController.swift in Sources */, + 111782F81DC64796000C1721 /* StaticTableMultiChoiceButtonCellModel.swift in Sources */, + 111782E91DC64796000C1721 /* FriendListCell.swift in Sources */, + 1117833A1DC647F8000C1721 /* Logger.swift in Sources */, + 1117831E1DC647D9000C1721 /* ActiveSessionCoordinator.swift in Sources */, + 111782F71DC64796000C1721 /* StaticTableMultiChoiceButtonCell.swift in Sources */, + 111782D21DC64796000C1721 /* BaseCell.swift in Sources */, + 111783171DC647C1000C1721 /* LoginGenericCreateController.swift in Sources */, + 11687F2E1DC64F0D0029B93F /* AppDelegate.swift in Sources */, + 111783311DC647E4000C1721 /* UIAlertControllerExtension.swift in Sources */, + 111783431DC64810000C1721 /* ImageViewWithStatus.swift in Sources */, + 111782BE1DC643A3000C1721 /* OCTSubmanagerChatsMock.swift in Sources */, + 1117836C1DC64D75000C1721 /* AddFriendController.swift in Sources */, + 111782E11DC64796000C1721 /* ChatMovableDateCellModel.swift in Sources */, + 111782F51DC64796000C1721 /* StaticTableInfoCell.swift in Sources */, + 111782D61DC64796000C1721 /* ChatEditable.swift in Sources */, + 1117831F1DC647D9000C1721 /* ActiveSessionNavigationCoordinator.swift in Sources */, + 111783131DC647C1000C1721 /* LoginChoiceController.swift in Sources */, + 111782ED1DC64796000C1721 /* StaticTableBaseCell.swift in Sources */, + 111783141DC647C1000C1721 /* LoginCreateAccountController.swift in Sources */, + 1117836A1DC64D5A000C1721 /* CallIncomingController.swift in Sources */, + 111782EE1DC64796000C1721 /* StaticTableBaseCellModel.swift in Sources */, + 9CDE31E81E489B2700333E0D /* ChatInputViewManager.swift in Sources */, + 1117837A1DC64D75000C1721 /* PrimaryIpadController.swift in Sources */, + 111783731DC64D75000C1721 /* ChatPrivateController.swift in Sources */, + 111783121DC647C1000C1721 /* LoginBaseController.swift in Sources */, + 111782E01DC64796000C1721 /* ChatMovableDateCell.swift in Sources */, + 111782DD1DC64796000C1721 /* ChatIncomingTextCell.swift in Sources */, + 111783831DC64D75000C1721 /* TabBarController.swift in Sources */, + 111783211DC647D9000C1721 /* CallCoordinator.swift in Sources */, + 111783161DC647C1000C1721 /* LoginFormController.swift in Sources */, + 111782E21DC64796000C1721 /* ChatOutgoingCallCell.swift in Sources */, + 111783851DC64D75000C1721 /* TextViewController.swift in Sources */, + 111783821DC64D75000C1721 /* StaticTableController.swift in Sources */, + 111782E31DC64796000C1721 /* ChatOutgoingCallCellModel.swift in Sources */, + 111782DB1DC64796000C1721 /* ChatIncomingFileCell.swift in Sources */, + 111782FC1DC6479E000C1721 /* AlertAudioPlayer.swift in Sources */, + 111783191DC647CF000C1721 /* AppCoordinator.swift in Sources */, + 1117830A1DC6479E000C1721 /* TabBarProfileItem.swift in Sources */, + 111782F91DC64796000C1721 /* StaticTableSelectableCellModel.swift in Sources */, + 111783741DC64D75000C1721 /* EnterPinController.swift in Sources */, + 1117833C1DC64810000C1721 /* BubbleView.swift in Sources */, + 1117831A1DC647CF000C1721 /* CoordinatorProtocol.swift in Sources */, + 1117837E1DC64D75000C1721 /* QRViewerController.swift in Sources */, + 1117836E1DC64D75000C1721 /* ChangeAutodownloadImagesController.swift in Sources */, + 111782BC1DC643A3000C1721 /* OCTSubmanagerBootstrapMock.swift in Sources */, + 111782D51DC64796000C1721 /* ChatBaseTextCellModel.swift in Sources */, + 111783241DC647D9000C1721 /* NotificationCoordinator.swift in Sources */, + 111782DF1DC64796000C1721 /* ChatListCellModel.swift in Sources */, + 111783111DC647BD000C1721 /* PortraitNavigationController.swift in Sources */, + 111782F01DC64796000C1721 /* StaticTableButtonCellModel.swift in Sources */, + 111783031DC6479E000C1721 /* NotificationObject.swift in Sources */, + 111783761DC64D75000C1721 /* FriendCardController.swift in Sources */, + 111783051DC6479E000C1721 /* ProfileSettings.swift in Sources */, + 111782AA1DC64391000C1721 /* ScreenshotsUITests.swift in Sources */, + 111783781DC64D75000C1721 /* FriendRequestController.swift in Sources */, + 111782E51DC64796000C1721 /* ChatOutgoingFileCellModel.swift in Sources */, + 111782BD1DC643A3000C1721 /* OCTSubmanagerCallsMock.swift in Sources */, + 111783371DC647F8000C1721 /* ErrorHandling.swift in Sources */, + 1117832A1DC647DF000C1721 /* UserStatus.swift in Sources */, + 111783001DC6479E000C1721 /* FilePreviewControllerDataSource.swift in Sources */, + 111783301DC647E4000C1721 /* StringExtension.swift in Sources */, + 111783331DC647E4000C1721 /* UIColorExtension.swift in Sources */, + 1117831B1DC647CF000C1721 /* TopCoordinatorProtocol.swift in Sources */, + 111783361DC647E4000C1721 /* UIViewControllerExtension.swift in Sources */, + 111783721DC64D75000C1721 /* ChatListController.swift in Sources */, + 111783801DC64D75000C1721 /* SettingsAdvancedController.swift in Sources */, + 1117833D1DC64810000C1721 /* CallButton.swift in Sources */, + 111783481DC64810000C1721 /* NotificationWindow.swift in Sources */, + 111782FA1DC64796000C1721 /* StaticTableSwitchCell.swift in Sources */, + 111783401DC64810000C1721 /* CopyLabel.swift in Sources */, + 111782C31DC643A3000C1721 /* OCTSubmanagerUserMock.swift in Sources */, + 111782E81DC64796000C1721 /* ChatProgressProtocol.swift in Sources */, + 111782E41DC64796000C1721 /* ChatOutgoingFileCell.swift in Sources */, + 111782F11DC64796000C1721 /* StaticTableChatButtonsCell.swift in Sources */, + 111783351DC647E4000C1721 /* UIImageExtension.swift in Sources */, + 1117837B1DC64D75000C1721 /* ProfileDetailsController.swift in Sources */, + 1117837C1DC64D75000C1721 /* ProfileMainController.swift in Sources */, + 111783181DC647C1000C1721 /* LoginLogoController.swift in Sources */, + 1117830D1DC6479E000C1721 /* UserDefaultsManager.swift in Sources */, + 111783081DC6479E000C1721 /* TabBarAbstractItem.swift in Sources */, + 111782C21DC643A3000C1721 /* OCTSubmanagerObjectsMock.swift in Sources */, + 111782EC1DC64796000C1721 /* StaticTableAvatarCellModel.swift in Sources */, + 1117837F1DC64D75000C1721 /* SettingsAboutController.swift in Sources */, + 111782D31DC64796000C1721 /* BaseCellModel.swift in Sources */, + 1117833E1DC64810000C1721 /* ChatInputView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1164762B19794D3300DB20B8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 11628E721CA580ED008C097E /* ChatGenericFileCell.swift in Sources */, + 116634AF1C6F40820072C980 /* ChatOutgoingCallCellModel.swift in Sources */, + 116634B21C7087A80072C980 /* UIApplicationExtension.swift in Sources */, + 113187441DD63B6600E6FAA2 /* ChatOutgoingTextCellModel.swift in Sources */, + 1117828D1DC531AD000C1721 /* OCTSubmanagerChatsMock.swift in Sources */, + 11A21EFD1C45BDF200E80A89 /* ChatInputView.swift in Sources */, + 1183BCB51CA1B3AE000CD310 /* ChatIncomingFileCellModel.swift in Sources */, + 110E425C1C00DC5E001A3CA2 /* StaticTableController.swift in Sources */, + 11FC105A1D32EED000CE863E /* ResultsChange.swift in Sources */, + 114962751C5CEB0B001E5435 /* ProfileDetailsController.swift in Sources */, + 11FC105D1D32F29700CE863E /* OCTSubmanagerObjectsExtension.swift in Sources */, + 11628E321CA2FBD1008C097E /* FilePreviewControllerDataSource.swift in Sources */, + 113D564D1C2F437B00B3D3E8 /* QRScannerAimView.swift in Sources */, + 1193AB361C1DEF2E006AA9E5 /* TextEditController.swift in Sources */, + 11B252FA1C4A614D0068F47C /* ChatIncomingTextCell.swift in Sources */, + 4E4B268527C0DCB500E07A66 /* CallManager.swift in Sources */, + 116513241C2C749E0066AF06 /* StaticTableChatButtonsCellModel.swift in Sources */, + 116634A01C6E8D0F0072C980 /* ChatIncomingCallCell.swift in Sources */, + 112E4EE51C676553004312CF /* CallButton.swift in Sources */, + 11771E361CA5C6E900EC259E /* Reach.swift in Sources */, + 112950871D63AFD800C9CE0F /* LoginCreatePasswordController.swift in Sources */, + 9CEE6B1F1C510A1E00A1ECB5 /* NotificationObject.swift in Sources */, + 1156A4871C10D122005AC8C6 /* StaticTableAvatarCell.swift in Sources */, + 11E640761C2DBAE200D24C6D /* AddFriendController.swift in Sources */, + 9CDC092C1C34102F00DC0D63 /* ExceptionHandling.m in Sources */, + 9CE0BDF31C4522C800DCE357 /* ChatListCellModel.swift in Sources */, + 11CFCD111C2A02350046BD94 /* StaticBackgroundView.swift in Sources */, + 111782961DC53458000C1721 /* OCTSubmanagerFriendsMock.swift in Sources */, + 111782991DC53545000C1721 /* OCTSubmanagerObjectsMock.swift in Sources */, + 1183BCAC1CA1B398000CD310 /* ChatIncomingFileCell.swift in Sources */, + 112E4ED61C668C02004312CF /* CallIncomingController.swift in Sources */, + 4E3D0F9527C9751300D5A068 /* LinearProgressBar.swift in Sources */, + 116634E91C7B8D7C0072C980 /* StaticTableInfoCellModel.swift in Sources */, + 11CFCD0D1C27488E0046BD94 /* ImageViewWithStatus.swift in Sources */, + 11B2535D1C4BACDB0068F47C /* TabBarController.swift in Sources */, + 9CB1F9511D58CA4000105858 /* RunningCoordinator.swift in Sources */, + 4E4EEA1927DCEC67008B0E77 /* LocationManager.swift in Sources */, + 11628E6C1CA57B19008C097E /* ChatOutgoingFileCellModel.swift in Sources */, + 9CBBC7C11BDFC62700099A5E /* LoginCreateAccountController.swift in Sources */, + 116DCE3B1CAAF85300B693EC /* NSURLExtension.swift in Sources */, + 1183BCDD1CA1FD55000CD310 /* ChatProgressBridge.swift in Sources */, + 11E640781C2DC15400D24C6D /* HelperFunctions.swift in Sources */, + 11FA0EEB1BC5A68600F3DA5B /* FriendsTabCoordinator.swift in Sources */, + 1164C8F01BC982FF00B91107 /* UIImageExtension.swift in Sources */, + 4E4B267F27C0DCAE00E07A66 /* Call.swift in Sources */, + 9CEE6B461C5289E200A1ECB5 /* SettingsMainController.swift in Sources */, + 9CE0BDF01C4522BF00DCE357 /* ChatListCell.swift in Sources */, + 116513261C2D636A0066AF06 /* NotificationWindow.swift in Sources */, + 1117829C1DC5363C000C1721 /* OCTSubmanagerUserMock.swift in Sources */, + 11B253AE1C4EC47D0068F47C /* IncompressibleView.swift in Sources */, + 11F276BB1D3EBF3000C613AA /* ChatBaseTextCellModel.swift in Sources */, + 11628E3E1CA30C13008C097E /* QuickLookPreviewController.swift in Sources */, + 1105B18D1EA09B1A0035B213 /* ChatFauxOfflineHeaderView.swift in Sources */, + 1164C8ED1BC9801200B91107 /* RoundedButton.swift in Sources */, + 11FA0EF11BC5A6A500F3DA5B /* SettingsTabCoordinator.swift in Sources */, + 1156A4891C15FB2B005AC8C6 /* AvatarManager.swift in Sources */, + 119AD1CA1BDED6E9000C5CB8 /* ErrorHandling.swift in Sources */, + 9C2EF3711C4D4D5C006E7AB1 /* NotificationCoordinator.swift in Sources */, + 1156A4811C0FA0A4005AC8C6 /* StaticTableButtonCell.swift in Sources */, + 11B2536D1C4BAD5D0068F47C /* TabBarProfileItem.swift in Sources */, + 11B253A41C4EA8040068F47C /* InterfaceIdiom.swift in Sources */, + 11FA0EE91BC5A66D00F3DA5B /* LoginCoordinator.swift in Sources */, + 111782781DC52BDB000C1721 /* OCTManagerMock.swift in Sources */, + 11AEBE711DA1876F00D04B59 /* FAQController.swift in Sources */, + 11B253141C4A79A50068F47C /* BubbleView.swift in Sources */, + 11B252FE1C4A615E0068F47C /* ChatOutgoingTextCell.swift in Sources */, + 112950841D63AFCE00C9CE0F /* LoginGenericCreateController.swift in Sources */, + 116634EC1C7B90F20072C980 /* FriendRequestController.swift in Sources */, + 11CFCD0B1C2745230046BD94 /* UserStatus.swift in Sources */, + 11B253021C4A61D00068F47C /* ChatMovableDateCell.swift in Sources */, + 11B253041C4A61D50068F47C /* ChatMovableDateCellModel.swift in Sources */, + 1164C8F61BC98F8300B91107 /* UIViewControllerExtension.swift in Sources */, + 11FA0EF31BC5A6AF00F3DA5B /* ChatsTabCoordinator.swift in Sources */, + 11B253561C4AEA400068F47C /* ChatPrivateTitleView.swift in Sources */, + 1173F06E1BC5D9BA00B88B7B /* Theme.swift in Sources */, + 4E4EEA1F27DCECD3008B0E77 /* Bus.swift in Sources */, + 1193AB481C1F4164006AA9E5 /* FriendListController.swift in Sources */, + 11CFCD0F1C27499D0046BD94 /* UserStatusView.swift in Sources */, + 9CEE6B4F1C528AAA00A1ECB5 /* SettingsAboutController.swift in Sources */, + 113F03331CB2E9C20009ABE1 /* UIFontExtension.swift in Sources */, + 11F276AE1D3EBEF700C613AA /* ChatBaseTextCell.swift in Sources */, + 4E4B268B27C0DCBC00E07A66 /* ProviderDelegate.swift in Sources */, + 1193AB6A1C1F5C28006AA9E5 /* FriendListCell.swift in Sources */, + 9CBBC7CD1BDFC6C600099A5E /* ExtendedTextField.swift in Sources */, + 11B9C6971BD80B080083C2A5 /* FullscreenPicker.swift in Sources */, + 9C07151D1BCD2E5B003A27B5 /* PortraitNavigationController.swift in Sources */, + 11FA0EED1BC5A69000F3DA5B /* ProfileTabCoordinator.swift in Sources */, + 1156A4851C10D119005AC8C6 /* StaticTableAvatarCellModel.swift in Sources */, + 116634AC1C6F407D0072C980 /* ChatOutgoingCallCell.swift in Sources */, + 113AD8571CA9C70F00D981B5 /* iPadFriendsButton.swift in Sources */, + 1156A4791C0E478D005AC8C6 /* StaticTableBaseCellModel.swift in Sources */, + 9CEE6AA21C4E679100A1ECB5 /* PrimaryIpadController.swift in Sources */, + 112422661BDD2032004D7926 /* KeyboardNotificationController.swift in Sources */, + 11B2536A1C4BAD560068F47C /* TabBarBadgeItem.swift in Sources */, + 113D56561C2F459E00B3D3E8 /* QRScannerController.swift in Sources */, + 116513191C2C6C980066AF06 /* FriendCardController.swift in Sources */, + 9C1FEB291D8C10EB008C2ADE /* ProfileSettings.swift in Sources */, + 9C07151F1BCD501D003A27B5 /* LoginFormController.swift in Sources */, + 11FA0EE71BC59B1400F3DA5B /* ActiveSessionCoordinator.swift in Sources */, + 1164C8F31BC9880000B91107 /* LoginLogoController.swift in Sources */, + 11B253FE1C52C0AA0068F47C /* StaticTableSwitchCellModel.swift in Sources */, + 119AD1CC1BDEDD9C000C5CB8 /* UIColorExtension.swift in Sources */, + 1117829F1DC53AB4000C1721 /* ToxFactory.swift in Sources */, + 9CDE31E61E489B2700333E0D /* ChatInputViewManager.swift in Sources */, + 115DFB8A1C177F5C00F18DB5 /* StaticTableDefaultCell.swift in Sources */, + 9C7475C51BC698510098B1A4 /* StringExtension.swift in Sources */, + 1193AB661C1F5694006AA9E5 /* BaseCell.swift in Sources */, + 1156A47D1C0F632C005AC8C6 /* StaticTableSelectableCellModel.swift in Sources */, + 111782931DC53371000C1721 /* OCTSubmanagerFilesMock.swift in Sources */, + 114962951C64051A001E5435 /* CallBaseController.swift in Sources */, + 115DFB961C177F6500F18DB5 /* StaticTableDefaultCellModel.swift in Sources */, + 1160B68B1D5FD8DE002DF75B /* LaunchPlaceholderController.swift in Sources */, + 1183BCD71CA1FC5B000CD310 /* ChatProgressProtocol.swift in Sources */, + 1117827B1DC52C3A000C1721 /* OCTSubmanagerBootstrapMock.swift in Sources */, + 1193AB681C1F569A006AA9E5 /* BaseCellModel.swift in Sources */, + 11771E2C1CA5C3F200EC259E /* AutomationCoordinator.swift in Sources */, + 11FC10571D32E54A00CE863E /* Results.swift in Sources */, + 11FA0EDA1BC584D900F3DA5B /* AppCoordinator.swift in Sources */, + 9C19367E1D79CF7E005EA0B2 /* EnterPinController.swift in Sources */, + 11770D6A1CBC120C00D34D6E /* FriendSelectController.swift in Sources */, + 1193AB421C1E2075006AA9E5 /* QRViewerController.swift in Sources */, + 9CEE6B521C528AB600A1ECB5 /* SettingsAdvancedController.swift in Sources */, + AFA023F4274C069100FBFCC0 /* ConnectionStatus.swift in Sources */, + 116634E01C7B8D770072C980 /* StaticTableInfoCell.swift in Sources */, + 119AD1BE1BDED261000C5CB8 /* TextViewController.swift in Sources */, + 1156A47B1C0F6178005AC8C6 /* StaticTableButtonCellModel.swift in Sources */, + 11B9C6831BD598FC0083C2A5 /* OCTManagerConfigurationExtension.swift in Sources */, + 110E49E01BF925FD00D1FE6F /* ActiveSessionNavigationCoordinator.swift in Sources */, + 1164C8D81BC922EB00B91107 /* UserDefaultsManager.swift in Sources */, + 11770D771CBC1C7A00D34D6E /* UIAlertControllerExtension.swift in Sources */, + 9C3271871D79C19C00347490 /* PinAuthorizationCoordinator.swift in Sources */, + 1109019B1D83417500BC5751 /* PinInputView.swift in Sources */, + 1180FDA01C384E29005F3EA1 /* CallCoordinator.swift in Sources */, + 110E078F1EB0756C00B2CA9D /* ResultsExtension.swift in Sources */, + 11FA0ED41BC5842800F3DA5B /* AppDelegate.swift in Sources */, + 11B253FB1C52C0A10068F47C /* StaticTableSwitchCell.swift in Sources */, + 113F03421CB31DD60009ABE1 /* AlertAudioPlayer.swift in Sources */, + 116634D91C70F1260072C980 /* CopyLabel.swift in Sources */, + 113BBBA71EA88EAC00540E6C /* ChatTypingHeaderView.swift in Sources */, + 1193AB6C1C1F5C31006AA9E5 /* FriendListCellModel.swift in Sources */, + 11B253661C4BAD3C0068F47C /* TabBarAbstractItem.swift in Sources */, + 113AD84A1CA97A3000D981B5 /* iPadNavigationView.swift in Sources */, + 116634F21C7B945A0072C980 /* StaticTableMultiChoiceButtonCellModel.swift in Sources */, + 11B2534F1C4AD2550068F47C /* AudioPlayer.swift in Sources */, + 9C04BB6D1C17389200F58488 /* StaticTableBaseCell.swift in Sources */, + 1129508B1D6851EB00C9CE0F /* LoginCreateAccountCoordinator.swift in Sources */, + 11B253B71C503FA10068F47C /* NSTimerExtension.swift in Sources */, + 9CE0BDE61C45229500DCE357 /* ChatListController.swift in Sources */, + 11E6406D1C2D6C3500D24C6D /* ViewPassingGestures.swift in Sources */, + 1136605C1CDE5D5E0092C27A /* ChatEditable.swift in Sources */, + 11628E7B1CA5BF1F008C097E /* ChangeAutodownloadImagesController.swift in Sources */, + 116513221C2C74940066AF06 /* StaticTableChatButtonsCell.swift in Sources */, + 9C9A07011BE793BA0003D6C7 /* ProfileMainController.swift in Sources */, + 11628E631CA57B14008C097E /* ChatOutgoingFileCell.swift in Sources */, + 112495371DE7ABFF00EF45C4 /* KeyboardObserver.swift in Sources */, + 116DCE321CAAF47100B693EC /* TopCoordinatorProtocol.swift in Sources */, + 1180FD9E1C38497A005F3EA1 /* NSDateFormatterExtension.swift in Sources */, + 9CDDB2061BD5376200B65D79 /* ProfileManager.swift in Sources */, + 1160B67E1D5F9993002DF75B /* KeychainManager.swift in Sources */, + 116046191D8D434B002287C8 /* ChangePinTimeoutController.swift in Sources */, + 114962921C5E1BE0001E5435 /* ChangePasswordController.swift in Sources */, + 9C2EF37A1C4E2DDE006E7AB1 /* ChatListTableManager.swift in Sources */, + 112E4EDF1C675C58004312CF /* CallActiveController.swift in Sources */, + 11628E751CA5811C008C097E /* ChatGenericFileCellModel.swift in Sources */, + 11A21EFB1C45BDB100E80A89 /* ChatPrivateController.swift in Sources */, + 1164C8E91BC929F700B91107 /* LoginChoiceController.swift in Sources */, + 1117827E1DC52CBA000C1721 /* OCTSubmanagerCallsMock.swift in Sources */, + 1149626C1C5CE3DF001E5435 /* ChangeUserStatusController.swift in Sources */, + 115CE3E81EB06F54001C08A0 /* ChatBottomStatusViewManager.swift in Sources */, + 116634EF1C7B94530072C980 /* StaticTableMultiChoiceButtonCell.swift in Sources */, + 116634A91C6E8D1A0072C980 /* ChatIncomingCallCellModel.swift in Sources */, + 11FA0EDE1BC58DCC00F3DA5B /* CoordinatorProtocol.swift in Sources */, + 11FA0EE11BC591FC00F3DA5B /* Logger.swift in Sources */, + 1164C8E71BC929C500B91107 /* LoginBaseController.swift in Sources */, + 9CDC091B1C34081900DC0D63 /* FriendListDataSource.swift in Sources */, + 1183BCC01CA1DB05000CD310 /* ProgressCircleView.swift in Sources */, + 11628E781CA5819B008C097E /* LoadingImageView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1173F0521BC5D94400B88B7B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9CEE6ACC1C4E8FDC00A1ECB5 /* StaticTableChatButtonsCellModel.swift in Sources */, + 9CEE6AFC1C4E8FDC00A1ECB5 /* ChatsTabCoordinator.swift in Sources */, + 1183BCC71CA1DB09000CD310 /* ProgressCircleView.swift in Sources */, + 9CEE6AD91C4E8FDC00A1ECB5 /* TabBarProfileItem.swift in Sources */, + 112E4EE61C676553004312CF /* CallButton.swift in Sources */, + 113AD8581CA9C70F00D981B5 /* iPadFriendsButton.swift in Sources */, + 9CEE6B181C4E8FDC00A1ECB5 /* RoundedButton.swift in Sources */, + 11B253A51C4EA8040068F47C /* InterfaceIdiom.swift in Sources */, + 9C3271881D79C19C00347490 /* PinAuthorizationCoordinator.swift in Sources */, + 116634EA1C7B8D7C0072C980 /* StaticTableInfoCellModel.swift in Sources */, + 9CEE6B201C510A1E00A1ECB5 /* NotificationObject.swift in Sources */, + 113F03341CB2E9C20009ABE1 /* UIFontExtension.swift in Sources */, + 112E4EE01C675C58004312CF /* CallActiveController.swift in Sources */, + 9CEE6B161C4E8FDC00A1ECB5 /* UserStatusView.swift in Sources */, + 9CEE6AE21C4E8FDC00A1ECB5 /* LoginLogoController.swift in Sources */, + 9CEE6B191C4E8FDC00A1ECB5 /* ExtendedTextField.swift in Sources */, + 111782A01DC53AB4000C1721 /* ToxFactory.swift in Sources */, + 9CEE6ABA1C4E8FDC00A1ECB5 /* BaseCellModel.swift in Sources */, + 1117829A1DC53545000C1721 /* OCTSubmanagerObjectsMock.swift in Sources */, + 9CEE6B0C1C4E8FDC00A1ECB5 /* HelperFunctions.swift in Sources */, + 113F03231CAFE5190009ABE1 /* ChatOutgoingTextCellSnapshotTest.swift in Sources */, + 116634E11C7B8D770072C980 /* StaticTableInfoCell.swift in Sources */, + 11771E2D1CA5C3F200EC259E /* AutomationCoordinator.swift in Sources */, + 11628E331CA2FBD1008C097E /* FilePreviewControllerDataSource.swift in Sources */, + 9CEE6B531C528AB600A1ECB5 /* SettingsAdvancedController.swift in Sources */, + 113F031F1CAFE2130009ABE1 /* ChatOutgoingCallCellSnapshotTest.swift in Sources */, + 116634A11C6E8D0F0072C980 /* ChatIncomingCallCell.swift in Sources */, + 116DCE3C1CAAF85300B693EC /* NSURLExtension.swift in Sources */, + 9CEE6AEB1C4E8FDC00A1ECB5 /* FriendListController.swift in Sources */, + 9CEE6AC81C4E8FDC00A1ECB5 /* StaticTableBaseCellModel.swift in Sources */, + 1183BCDE1CA1FD55000CD310 /* ChatProgressBridge.swift in Sources */, + 9CEE6AD71C4E8FDC00A1ECB5 /* TabBarAbstractItem.swift in Sources */, + 9CEE6ABB1C4E8FDC00A1ECB5 /* ChatIncomingTextCell.swift in Sources */, + 11AEBE721DA1876F00D04B59 /* FAQController.swift in Sources */, + 9CEE6AC51C4E8FDC00A1ECB5 /* StaticTableAvatarCell.swift in Sources */, + 9CEE6AB81C4E8FDC00A1ECB5 /* AppDelegate.swift in Sources */, + 9CEE6AE11C4E8FDC00A1ECB5 /* LoginFormController.swift in Sources */, + 1117827C1DC52C3A000C1721 /* OCTSubmanagerBootstrapMock.swift in Sources */, + 11FC10581D32E54A00CE863E /* Results.swift in Sources */, + 11771E371CA5C6E900EC259E /* Reach.swift in Sources */, + 9CEE6ACE1C4E8FDC00A1ECB5 /* StaticTableDefaultCellModel.swift in Sources */, + 9CEE6B091C4E8FDC00A1ECB5 /* UIColorExtension.swift in Sources */, + 9CEE6B501C528AAA00A1ECB5 /* SettingsAboutController.swift in Sources */, + 11628E6D1CA57B19008C097E /* ChatOutgoingFileCellModel.swift in Sources */, + 9CEE6AC61C4E8FDC00A1ECB5 /* StaticTableAvatarCellModel.swift in Sources */, + 114962931C5E1BE0001E5435 /* ChangePasswordController.swift in Sources */, + 11628E641CA57B14008C097E /* ChatOutgoingFileCell.swift in Sources */, + 1183BCAD1CA1B398000CD310 /* ChatIncomingFileCell.swift in Sources */, + 113187451DD63B6600E6FAA2 /* ChatOutgoingTextCellModel.swift in Sources */, + 9CEE6AF21C4E8FDC00A1ECB5 /* TabBarController.swift in Sources */, + 9CEE6B051C4E8FDC00A1ECB5 /* OCTManagerConfigurationExtension.swift in Sources */, + 9CEE6AE01C4E8FDC00A1ECB5 /* LoginCreateAccountController.swift in Sources */, + 1117828E1DC531AD000C1721 /* OCTSubmanagerChatsMock.swift in Sources */, + 1109019C1D83417500BC5751 /* PinInputView.swift in Sources */, + 9CEE6AC91C4E8FDC00A1ECB5 /* StaticTableButtonCell.swift in Sources */, + 11FC105E1D32F29700CE863E /* OCTSubmanagerObjectsExtension.swift in Sources */, + 113F03431CB31DD60009ABE1 /* AlertAudioPlayer.swift in Sources */, + 116DCE331CAAF47100B693EC /* TopCoordinatorProtocol.swift in Sources */, + 9CEE6ABD1C4E8FDC00A1ECB5 /* ChatListCell.swift in Sources */, + 9CEE6B021C4E8FDC00A1ECB5 /* UserStatus.swift in Sources */, + 9CEE6B0D1C4E8FDC00A1ECB5 /* Logger.swift in Sources */, + 9C19367F1D79CF7E005EA0B2 /* EnterPinController.swift in Sources */, + 9CEE6B001C4E8FDC00A1ECB5 /* ActiveSessionNavigationCoordinator.swift in Sources */, + 9CEE6ADA1C4E8FDC00A1ECB5 /* Theme.swift in Sources */, + 9CEE6B0E1C4E8FDC00A1ECB5 /* BubbleView.swift in Sources */, + 113F03211CAFE2B50009ABE1 /* ChatOutgoingFileCellSnapshotTest.swift in Sources */, + 9CEE6B0A1C4E8FDC00A1ECB5 /* UIViewControllerExtension.swift in Sources */, + 11628E7C1CA5BF1F008C097E /* ChangeAutodownloadImagesController.swift in Sources */, + 1160461A1D8D434B002287C8 /* ChangePinTimeoutController.swift in Sources */, + 116634ED1C7B90F20072C980 /* FriendRequestController.swift in Sources */, + 11B3F8701CE095D2001927D8 /* ChatEditable.swift in Sources */, + 9CDC092D1C34102F00DC0D63 /* ExceptionHandling.m in Sources */, + 11B253FC1C52C0A10068F47C /* StaticTableSwitchCell.swift in Sources */, + 11628E3F1CA30C13008C097E /* QuickLookPreviewController.swift in Sources */, + 1117829D1DC5363C000C1721 /* OCTSubmanagerUserMock.swift in Sources */, + 113F030A1CAFCCB90009ABE1 /* SnapshotBaseTest.swift in Sources */, + 9CEE6AF41C4E8FDC00A1ECB5 /* TextViewController.swift in Sources */, + 113F03171CAFD9370009ABE1 /* MockedChatProgressProtocol.swift in Sources */, + 113F030F1CAFCE7B0009ABE1 /* ChatIncomingFileCellSnapshotTest.swift in Sources */, + 113F031D1CAFE0DE0009ABE1 /* ChatMovableDateCellSnapshotTest.swift in Sources */, + 1117827F1DC52CBA000C1721 /* OCTSubmanagerCallsMock.swift in Sources */, + 9CEE6B171C4E8FDC00A1ECB5 /* ViewPassingGestures.swift in Sources */, + 9CEE6B0B1C4E8FDC00A1ECB5 /* ErrorHandling.swift in Sources */, + 116634B01C6F40820072C980 /* ChatOutgoingCallCellModel.swift in Sources */, + 9CEE6B101C4E8FDC00A1ECB5 /* ChatPrivateTitleView.swift in Sources */, + 9CEE6AC31C4E8FDC00A1ECB5 /* FriendListCell.swift in Sources */, + 9CDC09251C34083100DC0D63 /* FriendListDataSourceTest.swift in Sources */, + 9CEE6AF71C4E8FDC00A1ECB5 /* AppCoordinator.swift in Sources */, + 111782941DC53371000C1721 /* OCTSubmanagerFilesMock.swift in Sources */, + 113F03141CAFD5EE0009ABE1 /* CellSnapshotTest.swift in Sources */, + 9CDE31E71E489B2700333E0D /* ChatInputViewManager.swift in Sources */, + 112E4ED71C668C02004312CF /* CallIncomingController.swift in Sources */, + 9CEE6ADC1C4E8FDC00A1ECB5 /* KeyboardNotificationController.swift in Sources */, + 11628E761CA5811C008C097E /* ChatGenericFileCellModel.swift in Sources */, + 9CEE6ADE1C4E8FDC00A1ECB5 /* LoginBaseController.swift in Sources */, + 9CEE6AF91C4E8FDC00A1ECB5 /* ActiveSessionCoordinator.swift in Sources */, + 11F83BDB1CAFB2A20074FE11 /* SwiftSupport.swift in Sources */, + 9CEE6AC41C4E8FDC00A1ECB5 /* FriendListCellModel.swift in Sources */, + 9CEE6B131C4E8FDC00A1ECB5 /* NotificationWindow.swift in Sources */, + 1160B68C1D5FD8DE002DF75B /* LaunchPlaceholderController.swift in Sources */, + 113F030C1CAFCCDD0009ABE1 /* ChatIncomingCallCellSnapshotTest.swift in Sources */, + 113F03191CAFDB0D0009ABE1 /* ChatIncomingTextCellSnapshotTest.swift in Sources */, + 113BBBA81EA88EAC00540E6C /* ChatTypingHeaderView.swift in Sources */, + 9CEE6ACB1C4E8FDC00A1ECB5 /* StaticTableChatButtonsCell.swift in Sources */, + 9CEE6B471C5289E200A1ECB5 /* SettingsMainController.swift in Sources */, + 9CEE6AEC1C4E8FDC00A1ECB5 /* PrimaryIpadController.swift in Sources */, + 9CEE6AF81C4E8FDC00A1ECB5 /* LoginCoordinator.swift in Sources */, + 1129508C1D6851EB00C9CE0F /* LoginCreateAccountCoordinator.swift in Sources */, + 9CEE6AD31C4E8FDC00A1ECB5 /* FriendListDataSource.swift in Sources */, + 11FC105B1D32EED000CE863E /* ResultsChange.swift in Sources */, + 9CEE6AE81C4E8FDC00A1ECB5 /* ChatPrivateController.swift in Sources */, + 9CEE6ACD1C4E8FDC00A1ECB5 /* StaticTableDefaultCell.swift in Sources */, + 1105B18E1EA09B1A0035B213 /* ChatFauxOfflineHeaderView.swift in Sources */, + 9CEE6AB91C4E8FDC00A1ECB5 /* BaseCell.swift in Sources */, + 1149626D1C5CE3DF001E5435 /* ChangeUserStatusController.swift in Sources */, + 9CEE6AD11C4E8FDC00A1ECB5 /* AvatarManager.swift in Sources */, + 9C1FEB2A1D8C10EB008C2ADE /* ProfileSettings.swift in Sources */, + 114962961C64051A001E5435 /* CallBaseController.swift in Sources */, + 11F276BC1D3EBF3000C613AA /* ChatBaseTextCellModel.swift in Sources */, + 9CEE6B071C4E8FDC00A1ECB5 /* StringExtension.swift in Sources */, + 113AD84B1CA97A3000D981B5 /* iPadNavigationView.swift in Sources */, + 9CEE6AEE1C4E8FDC00A1ECB5 /* QRScannerController.swift in Sources */, + 1183BCD81CA1FC5B000CD310 /* ChatProgressProtocol.swift in Sources */, + 9CEE6ADD1C4E8FDC00A1ECB5 /* PortraitNavigationController.swift in Sources */, + 9CEE6AE71C4E8FDC00A1ECB5 /* ChatListController.swift in Sources */, + 1173F0701BC5D9CA00B88B7B /* ThemeTest.swift in Sources */, + 9CEE6AF11C4E8FDC00A1ECB5 /* StaticTableController.swift in Sources */, + 11B253FF1C52C0AA0068F47C /* StaticTableSwitchCellModel.swift in Sources */, + 9CEE6ABE1C4E8FDC00A1ECB5 /* ChatListCellModel.swift in Sources */, + 9CEE6AEF1C4E8FDC00A1ECB5 /* QRViewerController.swift in Sources */, + 1160B6821D5FC7CC002DF75B /* KeychainManagerTests.swift in Sources */, + 9CEE6AC71C4E8FDC00A1ECB5 /* StaticTableBaseCell.swift in Sources */, + 116634AA1C6E8D1A0072C980 /* ChatIncomingCallCellModel.swift in Sources */, + 116634AD1C6F407D0072C980 /* ChatOutgoingCallCell.swift in Sources */, + 9CEE6B121C4E8FDC00A1ECB5 /* ImageViewWithStatus.swift in Sources */, + 11628E731CA580ED008C097E /* ChatGenericFileCell.swift in Sources */, + 9CEE6ADB1C4E8FDC00A1ECB5 /* UserDefaultsManager.swift in Sources */, + 9CEE6AF61C4E8FDC00A1ECB5 /* CoordinatorProtocol.swift in Sources */, + 116634B31C7087A80072C980 /* UIApplicationExtension.swift in Sources */, + 9CEE6ACA1C4E8FDC00A1ECB5 /* StaticTableButtonCellModel.swift in Sources */, + 112950881D63AFD800C9CE0F /* LoginCreatePasswordController.swift in Sources */, + 9CEE6AEA1C4E8FDC00A1ECB5 /* FriendCardController.swift in Sources */, + 9CEE6AFA1C4E8FDC00A1ECB5 /* CallCoordinator.swift in Sources */, + 111782971DC53458000C1721 /* OCTSubmanagerFriendsMock.swift in Sources */, + 11C33AC41DC961DC008DBC49 /* LoginChoiceViewSnapshotTest.swift in Sources */, + 9CEE6AE51C4E8FDC00A1ECB5 /* AddFriendController.swift in Sources */, + 9CEE6AFE1C4E8FDC00A1ECB5 /* NotificationCoordinator.swift in Sources */, + 9CEE6AD21C4E8FDC00A1ECB5 /* ChatListTableManager.swift in Sources */, + 9CEE6B011C4E8FDC00A1ECB5 /* SettingsTabCoordinator.swift in Sources */, + 114962761C5CEB0B001E5435 /* ProfileDetailsController.swift in Sources */, + 9CEE6B151C4E8FDC00A1ECB5 /* StaticBackgroundView.swift in Sources */, + 9CEE6AFD1C4E8FDC00A1ECB5 /* FriendsTabCoordinator.swift in Sources */, + 9CEE6ACF1C4E8FDC00A1ECB5 /* StaticTableSelectableCellModel.swift in Sources */, + 9CEE6B141C4E8FDC00A1ECB5 /* QRScannerAimView.swift in Sources */, + 110E07901EB0756C00B2CA9D /* ResultsExtension.swift in Sources */, + 9CEE6AD81C4E8FDC00A1ECB5 /* TabBarBadgeItem.swift in Sources */, + 9CEE6B031C4E8FDC00A1ECB5 /* UIImageExtension.swift in Sources */, + 9CEE6B111C4E8FDC00A1ECB5 /* FullscreenPicker.swift in Sources */, + 1183BCB61CA1B3AE000CD310 /* ChatIncomingFileCellModel.swift in Sources */, + 112950851D63AFCE00C9CE0F /* LoginGenericCreateController.swift in Sources */, + 9CEE6AD61C4E8FDC00A1ECB5 /* ProfileManager.swift in Sources */, + 111782791DC52BDB000C1721 /* OCTManagerMock.swift in Sources */, + 9CEE6AC01C4E8FDC00A1ECB5 /* ChatMovableDateCellModel.swift in Sources */, + 116634DA1C70F1260072C980 /* CopyLabel.swift in Sources */, + 9CEE6AF31C4E8FDC00A1ECB5 /* TextEditController.swift in Sources */, + 11770D781CBC1C7A00D34D6E /* UIAlertControllerExtension.swift in Sources */, + 9CEE6B041C4E8FDC00A1ECB5 /* NSDateFormatterExtension.swift in Sources */, + 1160B67F1D5F9993002DF75B /* KeychainManager.swift in Sources */, + 11F276AF1D3EBEF700C613AA /* ChatBaseTextCell.swift in Sources */, + 9CEE6B0F1C4E8FDC00A1ECB5 /* ChatInputView.swift in Sources */, + 116634F01C7B94530072C980 /* StaticTableMultiChoiceButtonCell.swift in Sources */, + 115CE3E91EB06F54001C08A0 /* ChatBottomStatusViewManager.swift in Sources */, + 9CEE6AED1C4E8FDC00A1ECB5 /* ProfileMainController.swift in Sources */, + 11B253AF1C4EC47D0068F47C /* IncompressibleView.swift in Sources */, + 1124953E1DE7AC0300EF45C4 /* KeyboardObserver.swift in Sources */, + 9CEE6AC11C4E8FDC00A1ECB5 /* ChatOutgoingTextCell.swift in Sources */, + 9CEE6ABF1C4E8FDC00A1ECB5 /* ChatMovableDateCell.swift in Sources */, + 116634F31C7B945A0072C980 /* StaticTableMultiChoiceButtonCellModel.swift in Sources */, + 113F031B1CAFDBF70009ABE1 /* ChatListCellSnapshotTest.swift in Sources */, + 9CEE6AFF1C4E8FDC00A1ECB5 /* ProfileTabCoordinator.swift in Sources */, + 11770D6B1CBC120C00D34D6E /* FriendSelectController.swift in Sources */, + 9CEE6ADF1C4E8FDC00A1ECB5 /* LoginChoiceController.swift in Sources */, + 11628E791CA5819B008C097E /* LoadingImageView.swift in Sources */, + 11B253B81C503FA10068F47C /* NSTimerExtension.swift in Sources */, + 9CEE6AD01C4E8FDC00A1ECB5 /* AudioPlayer.swift in Sources */, + 9CB1F9521D58CA4000105858 /* RunningCoordinator.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AF2C9296279AB3F10094C08D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AF2C929D279AB3F10094C08D /* NotificationService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 111782AD1DC64391000C1721 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1164762E19794D3300DB20B8 /* Antidote */; + targetProxy = 111782AC1DC64391000C1721 /* PBXContainerItemProxy */; + }; + 1173F05C1BC5D94400B88B7B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1164762E19794D3300DB20B8 /* Antidote */; + targetProxy = 1173F05B1BC5D94400B88B7B /* PBXContainerItemProxy */; + }; + AF2C92A0279AB3F10094C08D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AF2C9299279AB3F10094C08D /* pushextension */; + targetProxy = AF2C929F279AB3F10094C08D /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 1131D6C71CA9D8BC00B4531C /* import-profile.html */ = { + isa = PBXVariantGroup; + children = ( + 1131D6C61CA9D8BC00B4531C /* en */, + 1131D6C91CA9DA1500B4531C /* ru */, + 11F83B751CADC9260074FE11 /* zh */, + 11F83B7D1CADC9330074FE11 /* da */, + 11F83B821CADC9460074FE11 /* de */, + 11F83B841CADC9790074FE11 /* pt */, + 11F83B861CADC9820074FE11 /* es */, + 113F035C1CB458D10009ABE1 /* ar */, + 11D15D741D53CDAA0042FD4A /* br */, + 11D15D761D53CECC0042FD4A /* lt */, + 11D15D7A1D53D26A0042FD4A /* fr */, + 11763ADF1D9C54640035B4C9 /* cs */, + 1143E4311DCE1A2500BE7250 /* nl */, + 113187371DD290DD00E6FAA2 /* pl */, + AF6AB23629156EDA00019362 /* ca */, + AF6AB23929156F0000019362 /* ko */, + AF6AB23D29156F0A00019362 /* nb */, + AF6AB24129156F1C00019362 /* pt-BR */, + ); + name = "import-profile.html"; + sourceTree = ""; + }; + 113E96461E4BB302000282FC /* AppStoreLocalizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 113E96451E4BB302000282FC /* en */, + 113E96471E4BB314000282FC /* ru */, + 113E96481E4BB318000282FC /* zh */, + 113E96491E4BB31F000282FC /* da */, + 113E964A1E4BB322000282FC /* de */, + 113E964B1E4BB327000282FC /* pt */, + 113E964C1E4BB32B000282FC /* es */, + 113E964E1E4BB371000282FC /* fr */, + 113E964F1E4BB37B000282FC /* nl */, + AF6AB23429156C6000019362 /* it */, + AF6AB23729156EDC00019362 /* ca */, + AF6AB23B29156F0000019362 /* ko */, + AF6AB23F29156F0B00019362 /* nb */, + AF6AB24229156F1E00019362 /* pt-BR */, + ); + name = AppStoreLocalizable.strings; + sourceTree = ""; + }; + 11F08D171DE0610B00F80F5F /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 11F08D161DE0610B00F80F5F /* en */, + 11F08D1E1DE0611700F80F5F /* ru */, + 11F08D1F1DE0611800F80F5F /* zh */, + 11F08D201DE0611800F80F5F /* da */, + 11F08D211DE0611900F80F5F /* de */, + 11F08D221DE0611A00F80F5F /* pt */, + 11F08D231DE0611B00F80F5F /* es */, + 11F08D241DE0611B00F80F5F /* ar */, + 11F08D251DE0611C00F80F5F /* lt */, + 11F08D261DE0611D00F80F5F /* br */, + 11F08D281DE0612200F80F5F /* fr */, + 11F08D291DE0612200F80F5F /* cs */, + 11F08D2B1DE0612400F80F5F /* nl */, + 11F08D2C1DE0612700F80F5F /* pl */, + AF6AB23329156C5800019362 /* it */, + AF6AB23529156EDA00019362 /* ca */, + AF6AB23829156F0000019362 /* ko */, + AF6AB23C29156F0A00019362 /* nb */, + AF6AB24029156F1C00019362 /* pt-BR */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 9C7475C01BC698110098B1A4 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 9C7475BF1BC698110098B1A4 /* en */, + 1131D6C41CA9D64500B4531C /* ru */, + 11F83B7C1CADC9270074FE11 /* zh */, + 11F83B7E1CADC9330074FE11 /* da */, + 11F83B831CADC9460074FE11 /* de */, + 11F83B851CADC9790074FE11 /* pt */, + 11F83B871CADC9820074FE11 /* es */, + 113F035D1CB458D10009ABE1 /* ar */, + 11D15D751D53CDAA0042FD4A /* br */, + 11D15D771D53CECC0042FD4A /* lt */, + 11D15D7B1D53D26A0042FD4A /* fr */, + 11763AEA1D9C54680035B4C9 /* cs */, + 1143E4321DCE1A2600BE7250 /* nl */, + 113187381DD290DE00E6FAA2 /* pl */, + AF6AB23A29156F0000019362 /* ko */, + AF6AB23A29256F0000019362 /* it */, + AF6AB23A29257F0000019362 /* ca */, + AF6AB23A29258F0000019362 /* nb */, + AF6AB23A29259F0000019362 /* pt-BR */, + AF6AB24129156F1C00019362 /* el */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 111782AE1DC64391000C1721 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 02404DE3D3BB1BF461C45EA0 /* Pods-ScreenshotsUITests.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = ScreenshotsUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-l\"CocoaLumberjack\"", + "-l\"JGProgressHUD\"", + "-l\"LNNotificationsUI\"", + "-l\"RBBAnimation\"", + "-l\"Realm\"", + "-l\"SDCAlertView\"", + "-l\"SDCAutoLayout\"", + "-l\"TPCircularBuffer\"", + "-l\"UITextView+Placeholder\"", + "-l\"c++\"", + "-l\"libopus\"", + "-l\"libsodium\"", + "-l\"objcTox\"", + "-l\"realm-ios\"", + "-l\"toxcore\"", + "-framework", + "\"AudioToolbox\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"QuartzCore\"", + "-framework", + "\"UIKit\"", + "-framework", + "\"vpx\"", + "-read_only_relocs", + suppress, + ); + PRODUCT_BUNDLE_IDENTIFIER = org.zoxcore.ScreenshotsUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "ScreenshotsUITests/ScreenshotUITests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + TEST_TARGET_NAME = Antidote; + }; + name = Debug; + }; + 111782AF1DC64391000C1721 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 22DCF57E8F127B60BD096DD1 /* Pods-ScreenshotsUITests.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = ScreenshotsUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-l\"CocoaLumberjack\"", + "-l\"JGProgressHUD\"", + "-l\"LNNotificationsUI\"", + "-l\"RBBAnimation\"", + "-l\"Realm\"", + "-l\"SDCAlertView\"", + "-l\"SDCAutoLayout\"", + "-l\"TPCircularBuffer\"", + "-l\"UITextView+Placeholder\"", + "-l\"c++\"", + "-l\"libopus\"", + "-l\"libsodium\"", + "-l\"objcTox\"", + "-l\"realm-ios\"", + "-l\"toxcore\"", + "-framework", + "\"AudioToolbox\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"QuartzCore\"", + "-framework", + "\"UIKit\"", + "-framework", + "\"vpx\"", + "-read_only_relocs", + suppress, + ); + PRODUCT_BUNDLE_IDENTIFIER = org.zoxcore.ScreenshotsUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "ScreenshotsUITests/ScreenshotUITests-Bridging-Header.h"; + SWIFT_VERSION = 4.0; + TEST_TARGET_NAME = Antidote; + }; + name = Release; + }; + 1164765919794D3300DB20B8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREFIX_HEADER = "Antidote/Antidote-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + ONLY_ACTIVE_ARCH = YES; + PROVISIONING_PROFILE = ""; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 1164765A19794D3300DB20B8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREFIX_HEADER = "Antidote/Antidote-Prefix.pch"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 1164765C19794D3300DB20B8 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2D5B916CEDBEC1B69CD1EFA4 /* Pods-Antidote.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CLANG_STATIC_ANALYZER_MODE = deep; + CODE_SIGN_ENTITLEMENTS = Antidote/Antidote.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 142800; + DEVELOPMENT_TEAM = Y46L589C5C; + ENABLE_BITCODE = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Antidote/Antidote-Prefix.pch"; + GCC_WARN_SIGN_COMPARE = YES; + INFOPLIST_FILE = "Antidote/Antidote-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.4.28; + PRODUCT_BUNDLE_IDENTIFIER = "org.zoxcore.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + RUN_CLANG_STATIC_ANALYZER = YES; + SWIFT_OBJC_BRIDGING_HEADER = "Antidote/Antidote-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = 1; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 1164765D19794D3300DB20B8 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B3C17AF8CE3B4AD1B68F2B5B /* Pods-Antidote.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CLANG_STATIC_ANALYZER_MODE = deep; + CODE_SIGN_ENTITLEMENTS = Antidote/Antidote.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 142800; + DEVELOPMENT_TEAM = Y46L589C5C; + ENABLE_BITCODE = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Antidote/Antidote-Prefix.pch"; + GCC_WARN_SIGN_COMPARE = YES; + INFOPLIST_FILE = "Antidote/Antidote-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.4.28; + PRODUCT_BUNDLE_IDENTIFIER = "org.zoxcore.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + RUN_CLANG_STATIC_ANALYZER = YES; + SWIFT_OBJC_BRIDGING_HEADER = "Antidote/Antidote-Bridging-Header.h"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = 1; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; + 1173F05E1BC5D94400B88B7B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 43C533DEE94E80C981FEC348 /* Pods-AntidoteTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = AntidoteTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = zoxcore.AntidoteTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "AntidoteTests/AntidoteTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Antidote.app/Antidote"; + }; + name = Debug; + }; + 1173F05F1BC5D94400B88B7B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CEC32120B965BE40E0029DB1 /* Pods-AntidoteTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = AntidoteTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = zoxcore.AntidoteTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "AntidoteTests/AntidoteTests-Bridging-Header.h"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Antidote.app/Antidote"; + }; + name = Release; + }; + AF2C92A3279AB3F10094C08D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES_NONAGGRESSIVE; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = Y46L589C5C; + INFOPLIST_FILE = pushextension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.zoxcore.Antidote.pushextension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AF2C92A4279AB3F10094C08D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES_NONAGGRESSIVE; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = Y46L589C5C; + INFOPLIST_FILE = pushextension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.zoxcore.Antidote.pushextension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 111782BA1DC64391000C1721 /* Build configuration list for PBXNativeTarget "ScreenshotsUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 111782AE1DC64391000C1721 /* Debug */, + 111782AF1DC64391000C1721 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1164762A19794D3300DB20B8 /* Build configuration list for PBXProject "Antidote" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1164765919794D3300DB20B8 /* Debug */, + 1164765A19794D3300DB20B8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1164765B19794D3300DB20B8 /* Build configuration list for PBXNativeTarget "Antidote" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1164765C19794D3300DB20B8 /* Debug */, + 1164765D19794D3300DB20B8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1173F05D1BC5D94400B88B7B /* Build configuration list for PBXNativeTarget "AntidoteTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1173F05E1BC5D94400B88B7B /* Debug */, + 1173F05F1BC5D94400B88B7B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AF2C92A5279AB3F10094C08D /* Build configuration list for PBXNativeTarget "pushextension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AF2C92A3279AB3F10094C08D /* Debug */, + AF2C92A4279AB3F10094C08D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 1164762719794D3300DB20B8 /* Project object */; +} diff --git a/Antidote/ActiveSessionCoordinator.swift b/Antidote/ActiveSessionCoordinator.swift new file mode 100644 index 0000000..78ee43c --- /dev/null +++ b/Antidote/ActiveSessionCoordinator.swift @@ -0,0 +1,634 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +protocol ActiveSessionCoordinatorDelegate: class { + func activeSessionCoordinatorDidLogout(_ coordinator: ActiveSessionCoordinator, importToxProfileFromURL: URL?) + func activeSessionCoordinatorDeleteProfile(_ coordinator: ActiveSessionCoordinator) + func activeSessionCoordinatorRecreateCoordinatorsStack(_ coordinator: ActiveSessionCoordinator, options: CoordinatorOptions) + func activeSessionCoordinatorDidStartCall(_ coordinator: ActiveSessionCoordinator) + func activeSessionCoordinatorDidFinishCall(_ coordinator: ActiveSessionCoordinator) +} + +private struct Options { + static let ToShowKey = "ToShowKey" + static let StoredOptions = "StoredOptions" + + enum Coordinator { + case none + case settings + } +} + +private struct IpadObjects { + let splitController: UISplitViewController + + let primaryController: PrimaryIpadController + + let keyboardObserver = KeyboardObserver() +} + +private struct IphoneObjects { + enum TabCoordinator: Int { + case friends = 0 + case chats = 1 + case settings = 2 + case profile = 3 + + static func allValues() -> [TabCoordinator]{ + return [friends, chats, settings, profile] + } + } + + let chatsCoordinator: ChatsTabCoordinator + + let tabBarController: TabBarController + + let friendsTabBarItem: TabBarBadgeItem + let chatsTabBarItem: TabBarBadgeItem + let profileTabBarItem: TabBarProfileItem +} + +class ActiveSessionCoordinator: NSObject { + weak var delegate: ActiveSessionCoordinatorDelegate? + + fileprivate let theme: Theme + fileprivate let window: UIWindow + + // Tox manager is stored here + var toxManager: OCTManager! + + fileprivate let friendsCoordinator: FriendsTabCoordinator + fileprivate let settingsCoordinator: SettingsTabCoordinator + fileprivate let profileCoordinator: ProfileTabCoordinator + + fileprivate let notificationCoordinator: NotificationCoordinator + fileprivate let automationCoordinator: AutomationCoordinator + var callCoordinator: CallCoordinator! + + /** + One of following properties will be non-empty, depending on running device. + */ + fileprivate var iPhone: IphoneObjects! + fileprivate var iPad: IpadObjects! + + init(theme: Theme, window: UIWindow, toxManager: OCTManager) { + self.theme = theme + self.window = window + self.toxManager = toxManager + + self.friendsCoordinator = FriendsTabCoordinator(theme: theme, toxManager: toxManager) + self.settingsCoordinator = SettingsTabCoordinator(theme: theme) + self.profileCoordinator = ProfileTabCoordinator(theme: theme, toxManager: toxManager) + self.notificationCoordinator = NotificationCoordinator(theme: theme, submanagerObjects: toxManager.objects) + self.automationCoordinator = AutomationCoordinator(submanagerObjects: toxManager.objects, submanagerFiles: toxManager.files) + + super.init() + + // order matters + createDeviceSpecificObjects() + createCallCoordinator() + + toxManager.user.delegate = self + + friendsCoordinator.delegate = self + settingsCoordinator.delegate = self + profileCoordinator.delegate = self + notificationCoordinator.delegate = self + + NotificationCenter.default.addObserver(self, selector: #selector(ActiveSessionCoordinator.applicationWillTerminate), name: NSNotification.Name.UIApplicationWillTerminate, object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + @objc func applicationWillTerminate() { + toxManager = nil + + // Giving tox some time to close all connections. + let until = Date(timeIntervalSinceNow:1.0) + RunLoop.current.run(until: until) + } +} + +extension ActiveSessionCoordinator: TopCoordinatorProtocol { + func startWithOptions(_ options: CoordinatorOptions?) { + switch InterfaceIdiom.current() { + case .iPhone: + iPhone.tabBarController.selectedIndex = IphoneObjects.TabCoordinator.chats.rawValue + iPhone.chatsCoordinator.startWithOptions(nil) + + window.rootViewController = iPhone.tabBarController + case .iPad: + primaryIpadControllerShowFriends(iPad.primaryController) + + window.rootViewController = iPad.splitController + } + + var settingsOptions: CoordinatorOptions? + + let toShow = options?[Options.ToShowKey] as? Options.Coordinator ?? .none + switch toShow { + case .none: + break + case .settings: + settingsOptions = options?[Options.StoredOptions] as? CoordinatorOptions + } + + friendsCoordinator.startWithOptions(nil) + settingsCoordinator.startWithOptions(settingsOptions) + profileCoordinator.startWithOptions(nil) + notificationCoordinator.startWithOptions(nil) + automationCoordinator.startWithOptions(nil) + callCoordinator.startWithOptions(nil) + + toxManager.bootstrap.addPredefinedNodes() + toxManager.bootstrap.bootstrap() + + updateUserAvatar() + updateUserName() + + switch toShow { + case .none: + break + case .settings: + showSettings() + } + } + func handleLocalNotification(_ notification: UILocalNotification) { + notificationCoordinator.handleLocalNotification(notification) + } + + func handleInboxURL(_ url: URL) { + let fileName = url.lastPathComponent + let filePath = url.path + let isToxFile = url.isToxURL() + + let style: UIAlertControllerStyle + + switch InterfaceIdiom.current() { + case .iPhone: + style = .actionSheet + case .iPad: + style = .alert + } + + let alert = UIAlertController(title: nil, message: fileName, preferredStyle: style) + + if isToxFile { + alert.addAction(UIAlertAction(title: String(localized: "create_profile"), style: .default) { [unowned self] _ -> Void in + self.logout(importToxProfileFromURL: url) + }) + } + + alert.addAction(UIAlertAction(title: String(localized: "file_send_to_contact"), style: .default) { [unowned self] _ -> Void in + self.sendFileToChats(filePath, fileName: fileName) + }) + + alert.addAction(UIAlertAction(title: String(localized: "alert_cancel"), style: .cancel, handler: nil)) + + switch InterfaceIdiom.current() { + case .iPhone: + iPhone.tabBarController.present(alert, animated: true, completion: nil) + case .iPad: + iPad.splitController.present(alert, animated: true, completion: nil) + } + } +} + +extension ActiveSessionCoordinator: OCTSubmanagerUserDelegate { + func submanagerUser(_ submanager: OCTSubmanagerUser, connectionStatusUpdate connectionStatus: OCTToxConnectionStatus) { + updateUserStatusView() + + let show = (connectionStatus == .none) + notificationCoordinator.toggleConnectingView(show: show, animated: true) + } +} + +extension ActiveSessionCoordinator: NotificationCoordinatorDelegate { + func notificationCoordinator(_ coordinator: NotificationCoordinator, showChat chat: OCTChat) { + showChat(chat) + } + + func notificationCoordinatorShowFriendRequest(_ coordinator: NotificationCoordinator, showRequest request: OCTFriendRequest) { + showFriendRequest(request) + } + + func notificationCoordinatorAnswerIncomingCall(_ coordinator: NotificationCoordinator, userInfo: String) { + callCoordinator.answerIncomingCallWithUserInfo(userInfo) + } + + func notificationCoordinator(_ coordinator: NotificationCoordinator, updateFriendsBadge badge: Int) { + let text: String? = (badge > 0) ? "\(badge)" : nil + + switch InterfaceIdiom.current() { + case .iPhone: + iPhone.friendsTabBarItem.badgeText = text + case .iPad: + iPad.primaryController.friendsBadgeText = text + break + } + } + + func notificationCoordinator(_ coordinator: NotificationCoordinator, updateChatsBadge badge: Int) { + switch InterfaceIdiom.current() { + case .iPhone: + iPhone.chatsTabBarItem.badgeText = (badge > 0) ? "\(badge)" : nil + case .iPad: + // none + break + } + } +} + +extension ActiveSessionCoordinator: CallCoordinatorDelegate { + func callCoordinator(_ coordinator: CallCoordinator, notifyAboutBackgroundCallFrom caller: String, userInfo: String) { + notificationCoordinator.showCallNotificationWithCaller(caller, userInfo: userInfo) + } + + func callCoordinatorDidStartCall(_ coordinator: CallCoordinator) { + delegate?.activeSessionCoordinatorDidStartCall(self) + } + + func callCoordinatorDidFinishCall(_ coordinator: CallCoordinator) { + delegate?.activeSessionCoordinatorDidFinishCall(self) + } +} + +extension ActiveSessionCoordinator: FriendsTabCoordinatorDelegate { + func friendsTabCoordinatorOpenChat(_ coordinator: FriendsTabCoordinator, forFriend friend: OCTFriend) { + let chat = toxManager.chats.getOrCreateChat(with: friend) + + showChat(chat!) + } + + func friendsTabCoordinatorCall(_ coordinator: FriendsTabCoordinator, toFriend friend: OCTFriend) { + let chat = toxManager.chats.getOrCreateChat(with: friend)! + + callCoordinator.callToChat(chat, enableVideo: false) + } + + func friendsTabCoordinatorVideoCall(_ coordinator: FriendsTabCoordinator, toFriend friend: OCTFriend) { + let chat = toxManager.chats.getOrCreateChat(with: friend)! + + callCoordinator.callToChat(chat, enableVideo: true) + } +} + +extension ActiveSessionCoordinator: ChatsTabCoordinatorDelegate { + func chatsTabCoordinator(_ coordinator: ChatsTabCoordinator, chatWillAppear chat: OCTChat) { + notificationCoordinator.banNotificationsForChat(chat) + } + + func chatsTabCoordinator(_ coordinator: ChatsTabCoordinator, chatWillDisapper chat: OCTChat) { + notificationCoordinator.unbanNotificationsForChat(chat) + } + + func chatsTabCoordinator(_ coordinator: ChatsTabCoordinator, callToChat chat: OCTChat, enableVideo: Bool) { + callCoordinator.callToChat(chat, enableVideo: enableVideo) + } +} + +extension ActiveSessionCoordinator: SettingsTabCoordinatorDelegate { + func settingsTabCoordinatorRecreateCoordinatorsStack(_ coordinator: SettingsTabCoordinator, options settingsOptions: CoordinatorOptions) { + delegate?.activeSessionCoordinatorRecreateCoordinatorsStack(self, options: [ + Options.ToShowKey: Options.Coordinator.settings, + Options.StoredOptions: settingsOptions, + ]) + } +} + +extension ActiveSessionCoordinator: ProfileTabCoordinatorDelegate { + func profileTabCoordinatorDelegateLogout(_ coordinator: ProfileTabCoordinator) { + logout() + } + + func profileTabCoordinatorDelegateDeleteProfile(_ coordinator: ProfileTabCoordinator) { + delegate?.activeSessionCoordinatorDeleteProfile(self) + } + + func profileTabCoordinatorDelegateDidChangeUserStatus(_ coordinator: ProfileTabCoordinator) { + updateUserStatusView() + } + + func profileTabCoordinatorDelegateDidChangeAvatar(_ coordinator: ProfileTabCoordinator) { + updateUserAvatar() + } + + func profileTabCoordinatorDelegateDidChangeUserName(_ coordinator: ProfileTabCoordinator) { + updateUserName() + } +} + +extension ActiveSessionCoordinator: PrimaryIpadControllerDelegate { + func primaryIpadController(_ controller: PrimaryIpadController, didSelectChat chat: OCTChat) { + showChat(chat) + } + + func primaryIpadControllerShowFriends(_ controller: PrimaryIpadController) { + iPad.splitController.showDetailViewController(friendsCoordinator.navigationController, sender: nil) + } + + func primaryIpadControllerShowSettings(_ controller: PrimaryIpadController) { + iPad.splitController.showDetailViewController(settingsCoordinator.navigationController, sender: nil) + } + + func primaryIpadControllerShowProfile(_ controller: PrimaryIpadController) { + iPad.splitController.showDetailViewController(profileCoordinator.navigationController, sender: nil) + } +} + +extension ActiveSessionCoordinator: ChatPrivateControllerDelegate { + func chatPrivateControllerWillAppear(_ controller: ChatPrivateController) { + notificationCoordinator.banNotificationsForChat(controller.chat) + } + + func chatPrivateControllerWillDisappear(_ controller: ChatPrivateController) { + notificationCoordinator.unbanNotificationsForChat(controller.chat) + } + + func chatPrivateControllerCallToChat(_ controller: ChatPrivateController, enableVideo: Bool) { + callCoordinator.callToChat(controller.chat, enableVideo: enableVideo) + } + + func chatPrivateControllerShowQuickLookController( + _ controller: ChatPrivateController, + dataSource: QuickLookPreviewControllerDataSource, + selectedIndex: Int) + { + let controller = QuickLookPreviewController() + controller.dataSource = dataSource + controller.dataSourceStorage = dataSource + controller.currentPreviewItemIndex = selectedIndex + + iPad.splitController.present(controller, animated: true, completion: nil) + } +} + +extension ActiveSessionCoordinator: FriendSelectControllerDelegate { + func friendSelectController(_ controller: FriendSelectController, didSelectFriend friend: OCTFriend) { + rootViewController().dismiss(animated: true) { [unowned self] in + guard let filePath = controller.userInfo as? String else { + return + } + + let chat = self.toxManager.chats.getOrCreateChat(with: friend) + self.sendFile(filePath, toChat: chat!) + } + } + + func friendSelectControllerCancel(_ controller: FriendSelectController) { + rootViewController().dismiss(animated: true, completion: nil) + + guard let filePath = controller.userInfo as? String else { + return + } + _ = try? FileManager.default.removeItem(atPath: filePath) + } +} + +private extension ActiveSessionCoordinator { + func createDeviceSpecificObjects() { + switch InterfaceIdiom.current() { + case .iPhone: + let chatsCoordinator = ChatsTabCoordinator(theme: theme, submanagerObjects: toxManager.objects, submanagerChats: toxManager.chats, submanagerFiles: toxManager.files) + chatsCoordinator.delegate = self + + let tabBarControllers = IphoneObjects.TabCoordinator.allValues().map { object -> UINavigationController in + switch object { + case .friends: + return friendsCoordinator.navigationController + case .chats: + return chatsCoordinator.navigationController + case .settings: + return settingsCoordinator.navigationController + case .profile: + return profileCoordinator.navigationController + } + } + + let tabBarItems = createTabBarItems() + + let friendsTabBarItem = tabBarItems[IphoneObjects.TabCoordinator.friends.rawValue] as! TabBarBadgeItem + let chatsTabBarItem = tabBarItems[IphoneObjects.TabCoordinator.chats.rawValue] as! TabBarBadgeItem + let profileTabBarItem = tabBarItems[IphoneObjects.TabCoordinator.profile.rawValue] as! TabBarProfileItem + + let tabBarController = TabBarController(theme: theme, controllers: tabBarControllers, tabBarItems: tabBarItems) + + iPhone = IphoneObjects( + chatsCoordinator: chatsCoordinator, + tabBarController: tabBarController, + friendsTabBarItem: friendsTabBarItem, + chatsTabBarItem: chatsTabBarItem, + profileTabBarItem: profileTabBarItem) + + case .iPad: + let splitController = UISplitViewController() + splitController.preferredDisplayMode = .allVisible + + let primaryController = PrimaryIpadController(theme: theme, submanagerChats: toxManager.chats, submanagerObjects: toxManager.objects) + primaryController.delegate = self + splitController.viewControllers = [UINavigationController(rootViewController: primaryController)] + + iPad = IpadObjects(splitController: splitController, primaryController: primaryController) + } + } + + func createCallCoordinator() { + let presentingController: UIViewController + + switch InterfaceIdiom.current() { + case .iPhone: + presentingController = iPhone.tabBarController + case .iPad: + presentingController = iPad.splitController + } + + self.callCoordinator = CallCoordinator( + theme: theme, + presentingController: presentingController, + submanagerCalls: toxManager.calls, + submanagerObjects: toxManager.objects) + callCoordinator.delegate = self + } + + func createTabBarItems() -> [TabBarAbstractItem] { + return IphoneObjects.TabCoordinator.allValues().map { + switch $0 { + case .friends: + let item = TabBarBadgeItem(theme: theme) + item.image = UIImage(named: "tab-bar-friends") + item.text = String(localized: "contacts_title") + item.badgeAccessibilityEnding = String(localized: "contact_requests_section") + return item + case .chats: + let item = TabBarBadgeItem(theme: theme) + item.image = UIImage(named: "tab-bar-chats") + item.text = String(localized: "chats_title") + item.badgeAccessibilityEnding = String(localized: "accessibility_chats_ending") + return item + case .settings: + let item = TabBarBadgeItem(theme: theme) + item.image = UIImage(named: "tab-bar-settings") + item.text = String(localized: "settings_title") + return item + case .profile: + return TabBarProfileItem(theme: theme) + } + } + } + + func showFriendRequest(_ request: OCTFriendRequest) { + switch InterfaceIdiom.current() { + case .iPhone: + iPhone.tabBarController.selectedIndex = IphoneObjects.TabCoordinator.friends.rawValue + case .iPad: + primaryIpadControllerShowFriends(iPad.primaryController) + } + + friendsCoordinator.showRequest(request, animated: false) + } + + /** + Returns active chat controller if it is visible, nil otherwise. + */ + func activeChatController() -> ChatPrivateController? { + switch InterfaceIdiom.current() { + case .iPhone: + if iPhone.tabBarController.selectedIndex != IphoneObjects.TabCoordinator.chats.rawValue { + return nil + } + + return iPhone.chatsCoordinator.activeChatController() + case .iPad: + return iPadDetailController() as? ChatPrivateController + } + } + + func showChat(_ chat: OCTChat) { + switch InterfaceIdiom.current() { + case .iPhone: + if iPhone.tabBarController.selectedIndex != IphoneObjects.TabCoordinator.chats.rawValue { + iPhone.tabBarController.selectedIndex = IphoneObjects.TabCoordinator.chats.rawValue + } + + iPhone.chatsCoordinator.showChat(chat, animated: false) + case .iPad: + if let chatVC = iPadDetailController() as? ChatPrivateController { + if chatVC.chat == chat { + // controller is already visible + return + } + } + + let controller = ChatPrivateController( + theme: theme, + chat: chat, + submanagerChats: toxManager.chats, + submanagerObjects: toxManager.objects, + submanagerFiles: toxManager.files, + delegate: self, + showKeyboardOnAppear: iPad.keyboardObserver.keyboardVisible) + let navigation = UINavigationController(rootViewController: controller) + + iPad.splitController.showDetailViewController(navigation, sender: nil) + } + } + + func showSettings() { + switch InterfaceIdiom.current() { + case .iPhone: + iPhone.tabBarController.selectedIndex = IphoneObjects.TabCoordinator.settings.rawValue + case .iPad: + primaryIpadControllerShowFriends(iPad.primaryController) + } + } + + func updateUserStatusView() { + let status = UserStatus(connectionStatus: toxManager.user.connectionStatus, userStatus: toxManager.user.userStatus) + let connectionstatus = ConnectionStatus(connectionStatus: toxManager.user.connectionStatus) + + switch InterfaceIdiom.current() { + case .iPhone: + iPhone.profileTabBarItem.userStatus = status + iPhone.profileTabBarItem.connectionStatus = connectionstatus + case .iPad: + iPad.primaryController.userStatus = status + } + } + + func updateUserAvatar() { + var avatar: UIImage? + + if let avatarData = toxManager.user.userAvatar() { + avatar = UIImage(data: avatarData) + } + + switch InterfaceIdiom.current() { + case .iPhone: + iPhone.profileTabBarItem.userImage = avatar + case .iPad: + iPad.primaryController.userAvatar = avatar + } + } + + func updateUserName() { + switch InterfaceIdiom.current() { + case .iPhone: + // nop + break + case .iPad: + iPad.primaryController.userName = toxManager.user.userName() + } + } + + func iPadDetailController() -> UIViewController? { + guard iPad.splitController.viewControllers.count == 2 else { + return nil + } + + let controller = iPad.splitController.viewControllers[1] + + if let navigation = controller as? UINavigationController { + return navigation.topViewController + } + + return controller + } + + func logout(importToxProfileFromURL profileURL: URL? = nil) { + delegate?.activeSessionCoordinatorDidLogout(self, importToxProfileFromURL: profileURL) + } + + func rootViewController() -> UIViewController { + switch InterfaceIdiom.current() { + case .iPhone: + return iPhone.tabBarController + case .iPad: + return iPad.splitController + } + } + + func sendFileToChats(_ filePath: String, fileName: String) { + let controller = FriendSelectController(theme: theme, submanagerObjects: toxManager.objects) + controller.delegate = self + controller.title = String(localized: "file_send_to_contact") + controller.userInfo = filePath as AnyObject? + + let navigation = UINavigationController(rootViewController: controller) + + rootViewController().present(navigation, animated: true, completion: nil) + } + + func sendFile(_ filePath: String, toChat chat: OCTChat) { + showChat(chat) + + toxManager.files.sendFile(atPath: filePath, moveToUploads: true, to: chat, failureBlock: { (error: Error) in + handleErrorWithType(.sendFileToFriend, error: error as NSError) + + }) + } +} diff --git a/Antidote/ActiveSessionNavigationCoordinator.swift b/Antidote/ActiveSessionNavigationCoordinator.swift new file mode 100644 index 0000000..7903112 --- /dev/null +++ b/Antidote/ActiveSessionNavigationCoordinator.swift @@ -0,0 +1,24 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +class ActiveSessionNavigationCoordinator { + let theme: Theme + let navigationController: UINavigationController + + init(theme: Theme) { + self.theme = theme + self.navigationController = UINavigationController() + } + + init(theme: Theme, navigationController: UINavigationController) { + self.theme = theme + self.navigationController = navigationController + } + + func startWithOptions(_ options: CoordinatorOptions?) { + preconditionFailure("This method must be overridden") + } +} diff --git a/Antidote/AddFriendController.swift b/Antidote/AddFriendController.swift new file mode 100644 index 0000000..3a77bd5 --- /dev/null +++ b/Antidote/AddFriendController.swift @@ -0,0 +1,245 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let TextViewTopOffset = 5.0 + static let TextViewXOffset = 5.0 + static let QrCodeBottomSpacerDeltaHeight = 70.0 + + static let SendAlertTextViewBottomOffset = -10.0 + static let SendAlertTextViewXOffset = 5.0 + static let SendAlertTextViewHeight = 70.0 +} + +protocol AddFriendControllerDelegate: class { + func addFriendControllerScanQRCode( + _ controller: AddFriendController, + validateCodeHandler: @escaping (String) -> Bool, + didScanHander: @escaping (String) -> Void) + + func addFriendControllerDidFinish(_ controller: AddFriendController) +} + +class AddFriendController: UIViewController { + weak var delegate: AddFriendControllerDelegate? + + fileprivate let theme: Theme + fileprivate weak var submanagerFriends: OCTSubmanagerFriends! + + fileprivate var textView: UITextView! + + fileprivate var orTopSpacer: UIView! + fileprivate var qrCodeBottomSpacer: UIView! + + fileprivate var orLabel: UILabel! + fileprivate var qrCodeButton: UIButton! + + fileprivate var cachedMessage: String? + + init(theme: Theme, submanagerFriends: OCTSubmanagerFriends) { + self.theme = theme + self.submanagerFriends = submanagerFriends + + super.init(nibName: nil, bundle: nil) + + addNavigationButtons() + + edgesForExtendedLayout = UIRectEdge() + title = String(localized: "add_contact_title") + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + createViews() + installConstraints() + + updateSendButton() + } +} + +extension AddFriendController { + @objc func qrCodeButtonPressed() { + func prepareString(_ string: String) -> String { + var string = string + + string = string.uppercased().trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + + if string.hasPrefix("TOX:") { + return String(string[string.index(string.startIndex, offsetBy: 4) ..< string.endIndex]) + } + + return string + } + + delegate?.addFriendControllerScanQRCode(self, validateCodeHandler: { + return isAddressString(prepareString($0)) + + }, didScanHander: { [unowned self] in + self.textView.text = prepareString($0) + self.updateSendButton() + }) + } + + @objc func sendButtonPressed() { + textView.resignFirstResponder() + + let messageView = UITextView() + messageView.text = cachedMessage + let placeholderstring = NSAttributedString.init(string: String(localized: "add_contact_default_message_text")) + //messageView.attributedPlaceholder = placeholderstring + messageView.font = UIFont.systemFont(ofSize: 17.0) + messageView.layer.cornerRadius = 5.0 + messageView.layer.masksToBounds = true + + let alert = SDCAlertController( + title: String(localized: "add_contact_default_message_title"), + message: nil, + preferredStyle: .alert)! + + alert.contentView.addSubview(messageView) + messageView.snp.makeConstraints { + $0.top.equalTo(alert.contentView) + $0.bottom.equalTo(alert.contentView).offset(Constants.SendAlertTextViewBottomOffset); + $0.leading.equalTo(alert.contentView).offset(Constants.SendAlertTextViewXOffset); + $0.trailing.equalTo(alert.contentView).offset(-Constants.SendAlertTextViewXOffset); + $0.height.equalTo(Constants.SendAlertTextViewHeight); + } + + alert.addAction(SDCAlertAction(title: String(localized: "alert_cancel"), style: .default, handler: nil)) + alert.addAction(SDCAlertAction(title: String(localized: "add_contact_send"), style: .recommended) { [unowned self] action in + self.cachedMessage = messageView.text + + let message = messageView.text.isEmpty ? "Antidote is Tox" : messageView.text + + do { + try self.submanagerFriends.sendFriendRequest(toAddress: self.textView.text, message: message) + } + catch let error as NSError { + handleErrorWithType(.toxAddFriend, error: error) + return + } + + self.delegate?.addFriendControllerDidFinish(self) + }) + + alert.present(completion: nil) + } +} + +extension AddFriendController: UITextViewDelegate { + func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + if text == "\n" { + updateSendButton() + textView.resignFirstResponder() + return false + } + + let resultText = (textView.text! as NSString).replacingCharacters(in: range, with: text) + let maxLength = Int(kOCTToxAddressLength) + + if resultText.lengthOfBytes(using: String.Encoding.utf8) > maxLength { + textView.text = resultText.substringToByteLength(maxLength, encoding: String.Encoding.utf8) + updateSendButton() + return false + } + + updateSendButton() + return true + } + + func textViewDidChange(_ textView: UITextView) { + updateSendButton() + } +} + +private extension AddFriendController { + func addNavigationButtons() { + navigationItem.rightBarButtonItem = UIBarButtonItem( + title: String(localized: "add_contact_send"), + style: .done, + target: self, + action: #selector(AddFriendController.sendButtonPressed)) + } + + func createViews() { + textView = UITextView() + let placeholderstring = NSAttributedString.init(string: String(localized: "add_contact_tox_id_placeholder")) + //textView.attributedPlaceholder = (placeholderstring) + textView.delegate = self + textView.isScrollEnabled = false + textView.font = UIFont.systemFont(ofSize: 17) + textView.textColor = theme.colorForType(.NormalText) + textView.backgroundColor = .clear + textView.returnKeyType = .done + textView.layer.cornerRadius = 5.0 + textView.layer.borderWidth = 0.5 + textView.layer.borderColor = theme.colorForType(.SeparatorsAndBorders).cgColor + textView.layer.masksToBounds = true + view.addSubview(textView) + + orTopSpacer = createSpacer() + qrCodeBottomSpacer = createSpacer() + + orLabel = UILabel() + orLabel.text = String(localized: "add_contact_or_label") + orLabel.textColor = theme.colorForType(.NormalText) + orLabel.backgroundColor = .clear + view.addSubview(orLabel) + + qrCodeButton = UIButton(type: .system) + qrCodeButton.setTitle(String(localized: "add_contact_use_qr"), for: UIControlState()) + qrCodeButton.titleLabel!.font = UIFont.antidoteFontWithSize(16.0, weight: .bold) + qrCodeButton.addTarget(self, action: #selector(AddFriendController.qrCodeButtonPressed), for: .touchUpInside) + view.addSubview(qrCodeButton) + } + + func createSpacer() -> UIView { + let spacer = UIView() + spacer.backgroundColor = .clear + view.addSubview(spacer) + + return spacer + } + + func installConstraints() { + textView.snp.makeConstraints { + $0.top.equalTo(view).offset(Constants.TextViewTopOffset) + $0.leading.equalTo(view).offset(Constants.TextViewXOffset) + $0.trailing.equalTo(view).offset(-Constants.TextViewXOffset) + $0.bottom.equalTo(view.snp.centerY) + } + + orTopSpacer.snp.makeConstraints { + $0.top.equalTo(textView.snp.bottom) + } + + orLabel.snp.makeConstraints { + $0.top.equalTo(orTopSpacer.snp.bottom) + $0.centerX.equalTo(view) + } + + qrCodeButton.snp.makeConstraints { + $0.top.equalTo(orLabel.snp.bottom) + $0.centerX.equalTo(view) + } + + qrCodeBottomSpacer.snp.makeConstraints { + $0.top.equalTo(qrCodeButton.snp.bottom) + $0.bottom.equalTo(view) + $0.height.equalTo(orTopSpacer) + } + } + + func updateSendButton() { + navigationItem.rightBarButtonItem!.isEnabled = isAddressString(textView.text) + } +} diff --git a/Antidote/AlertAudioPlayer.swift b/Antidote/AlertAudioPlayer.swift new file mode 100644 index 0000000..11e3115 --- /dev/null +++ b/Antidote/AlertAudioPlayer.swift @@ -0,0 +1,50 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import AVFoundation + +class AlertAudioPlayer { + enum Sound: String { + case NewMessage = "isotoxin_NewMessage" + } + + var playOnlyIfApplicationIsActive = true + + fileprivate var sounds: [Sound: SystemSoundID]! + + init() { + sounds = [ + .NewMessage: createSystemSoundForSound(.NewMessage), + ] + } + + deinit { + for (_, systemSound) in sounds { + AudioServicesDisposeSystemSoundID(systemSound) + } + } + + func playSound(_ sound: Sound) { + if playOnlyIfApplicationIsActive && !UIApplication.isActive { + return + } + + guard let systemSound = sounds[sound] else { + return + } + + AudioServicesPlayAlertSound(systemSound) + } +} + +private extension AlertAudioPlayer { + func createSystemSoundForSound(_ sound: Sound) -> SystemSoundID { + let url = Bundle.main.url(forResource: sound.rawValue, withExtension: "aac")! + + var sound: SystemSoundID = 0 + AudioServicesCreateSystemSoundID(url as CFURL, &sound) + return sound + } +} diff --git a/Antidote/Antidote-Bridging-Header.h b/Antidote/Antidote-Bridging-Header.h new file mode 100644 index 0000000..c350f0e --- /dev/null +++ b/Antidote/Antidote-Bridging-Header.h @@ -0,0 +1,43 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#undef LOG_INFO +#undef LOG_DEBUG +#import "DDLog.h" +#import "DDASLLogger.h" +#import "DDTTYLogger.h" + +#import +#import +#import +#import + +#import "ExceptionHandling.h" diff --git a/Antidote/Antidote-Info.plist b/Antidote/Antidote-Info.plist new file mode 100644 index 0000000..137bf7e --- /dev/null +++ b/Antidote/Antidote-Info.plist @@ -0,0 +1,89 @@ + + + + + + NSLocationWhenInUseUsageDescription + Used to share your location with your contacts + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleDocumentTypes + + + CFBundleTypeIconFiles + + CFBundleTypeName + Data + LSHandlerRank + Default + LSItemContentTypes + + public.data + + + + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + ITSAppUsesNonExemptEncryption + + LSApplicationCategoryType + + LSRequiresIPhoneOS + + LSSupportsOpeningDocumentsInPlace + + NSCameraUsageDescription + You can use video calls, send photos and videos, scan QR codes. + NSMicrophoneUsageDescription + You can use audio and video calls. + NSPhotoLibraryUsageDescription + You can send photos and videos. + UIBackgroundModes + + audio + fetch + location + remote-notification + voip + + UILaunchStoryboardName + Launch Screen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown + + UISupportsDocumentBrowser + + UIUserInterfaceStyle + Light + + diff --git a/Antidote/Antidote-Prefix.pch b/Antidote/Antidote-Prefix.pch new file mode 100644 index 0000000..de2f8e8 --- /dev/null +++ b/Antidote/Antidote-Prefix.pch @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +#ifndef Antidote_Prefix_pch +#define Antidote_Prefix_pch + +// Include any system framework and library headers here that should be included in all compilation units. +// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. + +#endif /* Antidote_Prefix_pch */ diff --git a/Antidote/Antidote.entitlements b/Antidote/Antidote.entitlements new file mode 100644 index 0000000..5265c39 --- /dev/null +++ b/Antidote/Antidote.entitlements @@ -0,0 +1,12 @@ + + + + + aps-environment + development + keychain-access-groups + + $(AppIdentifierPrefix)org.zoxcore.Antidote + + + diff --git a/Antidote/AppCoordinator.swift b/Antidote/AppCoordinator.swift new file mode 100644 index 0000000..985f1d4 --- /dev/null +++ b/Antidote/AppCoordinator.swift @@ -0,0 +1,163 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +class AppCoordinator { + fileprivate let window: UIWindow + var activeCoordinator: TopCoordinatorProtocol! + fileprivate var theme: Theme + + init(window: UIWindow) { + self.window = window + + let filepath = Bundle.main.path(forResource: "default-theme", ofType: "yaml")! + let yamlString = try! NSString(contentsOfFile:filepath, encoding:String.Encoding.utf8.rawValue) as String + + theme = try! Theme(yamlString: yamlString) + applyTheme(theme) + } +} + +// MARK: CoordinatorProtocol +extension AppCoordinator: TopCoordinatorProtocol { + func startWithOptions(_ options: CoordinatorOptions?) { + let storyboard = UIStoryboard(name: "LaunchPlaceholderBoard", bundle: Bundle.main) + window.rootViewController = storyboard.instantiateViewController(withIdentifier: "LaunchPlaceholderController") + + recreateActiveCoordinator(options: options) + } + + func handleLocalNotification(_ notification: UILocalNotification) { + activeCoordinator.handleLocalNotification(notification) + } + + func handleInboxURL(_ url: URL) { + activeCoordinator.handleInboxURL(url) + } +} + +extension AppCoordinator: RunningCoordinatorDelegate { + func runningCoordinatorDidLogout(_ coordinator: RunningCoordinator, importToxProfileFromURL: URL?) { + KeychainManager().deleteActiveAccountData() + + recreateActiveCoordinator() + + if let url = importToxProfileFromURL, + let coordinator = activeCoordinator as? LoginCoordinator { + coordinator.handleInboxURL(url) + } + } + + func runningCoordinatorDeleteProfile(_ coordinator: RunningCoordinator) { + let userDefaults = UserDefaultsManager() + let profileManager = ProfileManager() + + let name = userDefaults.lastActiveProfile! + + do { + try profileManager.deleteProfileWithName(name) + + KeychainManager().deleteActiveAccountData() + userDefaults.lastActiveProfile = nil + + recreateActiveCoordinator() + } + catch let error as NSError { + handleErrorWithType(.deleteProfile, error: error) + } + } + + func runningCoordinatorRecreateCoordinatorsStack(_ coordinator: RunningCoordinator, options: CoordinatorOptions) { + recreateActiveCoordinator(options: options, skipAuthorizationChallenge: true) + } +} + +extension AppCoordinator: LoginCoordinatorDelegate { + func loginCoordinatorDidLogin(_ coordinator: LoginCoordinator, manager: OCTManager, password: String) { + KeychainManager().toxPasswordForActiveAccount = password + + recreateActiveCoordinator(manager: manager, skipAuthorizationChallenge: true) + } +} + +// MARK: Private +private extension AppCoordinator { + func applyTheme(_ theme: Theme) { + let linkTextColor = theme.colorForType(.LinkText) + + UIButton.appearance().tintColor = linkTextColor + UISwitch.appearance().onTintColor = linkTextColor + UINavigationBar.appearance().tintColor = linkTextColor + } + + func recreateActiveCoordinator(options: CoordinatorOptions? = nil, + manager: OCTManager? = nil, + skipAuthorizationChallenge: Bool = false) { + if let password = KeychainManager().toxPasswordForActiveAccount { + let successBlock: (OCTManager) -> Void = { [unowned self] manager -> Void in + self.activeCoordinator = self.createRunningCoordinatorWithManager(manager, + options: options, + skipAuthorizationChallenge: skipAuthorizationChallenge) + } + + if let manager = manager { + successBlock(manager) + } + else { + let deleteActiveAccountAndRetry: () -> Void = { [unowned self] in + KeychainManager().deleteActiveAccountData() + self.recreateActiveCoordinator(options: options, + manager: manager, + skipAuthorizationChallenge: skipAuthorizationChallenge) + } + + guard let profileName = UserDefaultsManager().lastActiveProfile else { + deleteActiveAccountAndRetry() + return + } + + let path = ProfileManager().pathForProfileWithName(profileName) + + guard let configuration = OCTManagerConfiguration.configurationWithBaseDirectory(path) else { + deleteActiveAccountAndRetry() + return + } + + ToxFactory.createToxWithConfiguration(configuration, + encryptPassword: password, + successBlock: successBlock, + failureBlock: { _ in + log("Cannot create tox with configuration \(configuration)") + deleteActiveAccountAndRetry() + }) + } + } + else { + activeCoordinator = createLoginCoordinator(options) + } + } + + func createRunningCoordinatorWithManager(_ manager: OCTManager, + options: CoordinatorOptions?, + skipAuthorizationChallenge: Bool) -> RunningCoordinator { + let coordinator = RunningCoordinator(theme: theme, + window: window, + toxManager: manager, + skipAuthorizationChallenge: skipAuthorizationChallenge) + coordinator.delegate = self + coordinator.startWithOptions(options) + + return coordinator + } + + func createLoginCoordinator(_ options: CoordinatorOptions?) -> LoginCoordinator { + let coordinator = LoginCoordinator(theme: theme, window: window) + coordinator.delegate = self + coordinator.startWithOptions(options) + + return coordinator + } +} + diff --git a/Antidote/AppDelegate.swift b/Antidote/AppDelegate.swift new file mode 100644 index 0000000..78c7770 --- /dev/null +++ b/Antidote/AppDelegate.swift @@ -0,0 +1,321 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import Firebase +import os + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + let gcmMessageIDKey = "gcm.message_id" + var coordinator: AppCoordinator! + let callManager = CallManager() + lazy var providerDelegate: ProviderDelegate = ProviderDelegate(callManager: self.callManager) + var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid + var gps_was_stopped_by_forground: Bool = false + static var lastStartGpsTS: Int64 = 0 + static var location_sharing_contact_pubkey: String = "-1" + + class var shared: AppDelegate { + return UIApplication.shared.delegate as! AppDelegate + } + + func displayIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) { + providerDelegate.reportIncomingCall(uuid: uuid, handle: handle, hasVideo: hasVideo, completion: completion) + } + + func endIncomingCalls() { + providerDelegate.endIncomingCalls() + } + + func applicationWillEnterForeground(_ application: UIApplication) { + os_log("AppDelegate:applicationWillEnterForeground") + UIApplication.shared.endBackgroundTask(self.backgroundTask) + self.backgroundTask = UIBackgroundTaskInvalid + + gps_was_stopped_by_forground = true + let gps = LocationManager.shared + if !gps.isHasAccess() { + os_log("AppDelegate:applicationWillEnterForeground:gps:no_access") + } else if gps.state == .Monitoring { + os_log("AppDelegate:applicationWillEnterForeground:gps:STOP") + gps.stopMonitoring() + } + + os_log("AppDelegate:applicationWillEnterForeground:DidEnterBackground:2:END") + } + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + window = UIWindow(frame:UIScreen.main.bounds) + + print("didFinishLaunchingWithOptions") + os_log("AppDelegate:didFinishLaunchingWithOptions:start") + + if ProcessInfo.processInfo.arguments.contains("UI_TESTING") { + // Speeding up animations for UI tests. + window!.layer.speed = 1000 + } + + configureLoggingStuff() + + coordinator = AppCoordinator(window: window!) + coordinator.startWithOptions(nil) + + if let notification = launchOptions?[UIApplicationLaunchOptionsKey.localNotification] as? UILocalNotification { + coordinator.handleLocalNotification(notification) + } + + window?.backgroundColor = UIColor.white + window?.makeKeyAndVisible() + + FirebaseApp.configure() + + Messaging.messaging().delegate = self + + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self + + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] + UNUserNotificationCenter.current().requestAuthorization( + options: authOptions, + completionHandler: { _, _ in } + ) + } else { + let settings: UIUserNotificationSettings = + UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) + application.registerUserNotificationSettings(settings) + } + + application.registerForRemoteNotifications() + // HINT: try to go online every 47 minutes + let bgfetchInterval: TimeInterval = 47 * 60 + application.setMinimumBackgroundFetchInterval(bgfetchInterval); + os_log("AppDelegate:didFinishLaunchingWithOptions:end") + + return true + } + + func applicationWillTerminate(_ application: UIApplication) { + print("WillTerminate") + os_log("AppDelegate:applicationWillTerminate") + } + + func applicationDidReceiveMemoryWarning(_ application: UIApplication) { + print("DidReceiveMemoryWarning") + os_log("AppDelegate:applicationDidReceiveMemoryWarning") + } + + func applicationDidEnterBackground(_ application: UIApplication) { + print("DidEnterBackground") + os_log("AppDelegate:applicationDidEnterBackground:start") + + backgroundTask = UIApplication.shared.beginBackgroundTask (expirationHandler: { [unowned self] in + UIApplication.shared.endBackgroundTask(self.backgroundTask) + self.backgroundTask = UIBackgroundTaskInvalid + os_log("AppDelegate:applicationDidEnterBackground:3:expirationHandler:END") + }) + + DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 15) { + if (UserDefaultsManager().LongerbgMode == true) { + os_log("AppDelegate:applicationDidEnterBackground:PushSelf:start") + let coord = self.coordinator.activeCoordinator + let runcoord = coord as! RunningCoordinator + runcoord.activeSessionCoordinator?.toxManager.chats.sendOwnPush() + } else { + os_log("AppDelegate:applicationDidEnterBackground:PushSelf:longer-bg-mode not active in settings") + } + } + + gps_was_stopped_by_forground = false + let gps = LocationManager.shared + if gps.isHasAccess() { + AppDelegate.lastStartGpsTS = Date().millisecondsSince1970 + gps.startMonitoring() + os_log("AppDelegate:applicationDidEnterBackground:gps:START") + DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + (3 * 60)) { + os_log("AppDelegate:applicationDidEnterBackground:4:gps:finishing") + let gps = LocationManager.shared + if !gps.isHasAccess() { + os_log("AppDelegate:applicationDidEnterBackground:4:gps:no_access") + } else if gps.state == .Monitoring { + if (self.gps_was_stopped_by_forground == false) { + + let diffTime = Date().millisecondsSince1970 - AppDelegate.lastStartGpsTS + os_log("AppDelegate:applicationDidEnterBackground:4:gps:Tlast=%ld", AppDelegate.lastStartGpsTS) + os_log("AppDelegate:applicationDidEnterBackground:4:gps:Tnow=%ld", Date().millisecondsSince1970) + os_log("AppDelegate:applicationDidEnterBackground:4:gps:Tdiff=%ld", diffTime) + + if (diffTime > (((3 * 60) - 4) * 1000)) + { + os_log("AppDelegate:applicationDidEnterBackground:4:gps:STOP") + gps.stopMonitoring() + } else { + os_log("AppDelegate:applicationDidEnterBackground:4:gps:STOP skipped, must be an old timer") + } + } else { + os_log("AppDelegate:applicationDidEnterBackground:4:gps:was stopped by forground, skipping") + } + } else { + os_log("AppDelegate:applicationDidEnterBackground:4:gps:STOP skipped, gps was stopped already") + } + } + } else { + os_log("AppDelegate:applicationDidEnterBackground:gps:no_access") + } + + DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 25) { + UIApplication.shared.endBackgroundTask(self.backgroundTask) + self.backgroundTask = UIBackgroundTaskInvalid + os_log("AppDelegate:applicationDidEnterBackground:1:END") + } + } + + func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + + print("performFetchWithCompletionHandler:start") + os_log("AppDelegate:performFetchWithCompletionHandler:start") + // HINT: we have 30 seconds here. use 25 of those 30 seconds to be on the safe side + DispatchQueue.main.asyncAfter(deadline: .now() + 25) { [weak self] in + completionHandler(UIBackgroundFetchResult.newData) + print("performFetchWithCompletionHandler:end") + os_log("AppDelegate:performFetchWithCompletionHandler:end") + } + } + + func application(_ application: UIApplication, didReceive notification: UILocalNotification) { + coordinator.handleLocalNotification(notification) + } + + func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool { + coordinator.handleInboxURL(url) + + return true + } + + func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { + coordinator.handleInboxURL(url) + + return true + } + + // Device received notification (legacy callback) + // + func application(_ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { + if let messageID = userInfo[gcmMessageIDKey] { + print("Message ID: \(messageID)") + } + } + + // tells the app that a remote notification arrived that indicates there is data to be fetched. + // ios 7+ + // + func application(_ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable: Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) + -> Void) { + if let messageID = userInfo[gcmMessageIDKey] { + print("Message ID: \(messageID)") + } + + os_log("AppDelegate:didReceiveRemoteNotification:start") + // HINT: we have 30 seconds here. use 25 of those 30 seconds to be on the safe side + DispatchQueue.main.asyncAfter(deadline: .now() + 25) { [weak self] in + completionHandler(UIBackgroundFetchResult.newData) + os_log("AppDelegate:didReceiveRemoteNotification:start") + } + } + + // APNs failed to register the device for push notifications + // + func application(_ application: UIApplication, + didFailToRegisterForRemoteNotificationsWithError error: Error) { + print("Unable to register for remote notifications: \(error.localizedDescription)") + } + + func application(_ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + print("APNs token retrieved: \(deviceToken)") + os_log("AppDelegate:didRegisterForRemoteNotificationsWithDeviceToken") + } +} + +@available(iOS 10, *) +extension AppDelegate: UNUserNotificationCenterDelegate { + + // determine what to do if app is in foreground when a notification is coming + // ios 10+ UNUserNotificationCenterDelegate method + // + func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) + -> Void) { + let userInfo = notification.request.content.userInfo + + if let messageID = userInfo[gcmMessageIDKey] { + print("Message ID: \(messageID)") + } + completionHandler([[.alert, .sound]]) + } + + // Process and handle the user's response to a delivered notification. + // ios 10+ UNUserNotificationCenterDelegate method + // + func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + let userInfo = response.notification.request.content.userInfo + + if let messageID = userInfo[gcmMessageIDKey] { + print("Message ID: \(messageID)") + } + completionHandler() + } + +} + +private extension AppDelegate { + func configureLoggingStuff() { + DDLog.add(DDASLLogger.sharedInstance()) + // DDLog.add(DDTTYLogger.sharedInstance()) + } +} + +extension AppDelegate: MessagingDelegate { + func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { + print("Firebase registration token: \(String(describing: fcmToken))") + + let dataDict: [String: String] = ["token": fcmToken ?? ""] + NotificationCenter.default.post( + name: Notification.Name("FCMToken"), + object: nil, + userInfo: dataDict + ) + } +} + +// Convenience AppWide Simple Alert +extension AppDelegate { + func alert(_ title: String, _ msg: String? = nil) { + os_log("AppDelegate:alert") + let cnt = UIAlertController(title: title, message: msg, preferredStyle: .alert) + cnt.addAction(UIAlertAction(title: "Ok", style: .default, handler: { [weak cnt] act in + cnt?.dismiss(animated: true, completion: nil) + })) + + // guard let vc = AppDelegate.topViewController() else { return } + // vc.present(cnt, animated: true, completion: nil) + } +} + +extension Date { + var millisecondsSince1970: Int64 { + Int64((self.timeIntervalSince1970 * 1000.0).rounded()) + } + + init(milliseconds: Int64) { + self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000) + } +} diff --git a/Antidote/AudioPlayer.swift b/Antidote/AudioPlayer.swift new file mode 100644 index 0000000..c24111d --- /dev/null +++ b/Antidote/AudioPlayer.swift @@ -0,0 +1,81 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import AVFoundation + +class AudioPlayer { + enum Sound: String { + case Calltone = "isotoxin_Calltone" + case Hangup = "isotoxin_Hangup" + case Ringtone = "isotoxin_Ringtone" + case RingtoneWhileCall = "isotoxin_RingtoneWhileCall" + } + + var playOnlyIfApplicationIsActive = true + + fileprivate var players = [Sound: AVAudioPlayer]() + + func playSound(_ sound: Sound, loop: Bool) { + if playOnlyIfApplicationIsActive && !UIApplication.isActive { + return + } + + guard let player = playerForSound(sound) else { + return + } + + player.numberOfLoops = loop ? -1 : 1 + player.currentTime = 0.0 + player.play() + } + + func isPlayingSound(_ sound: Sound) -> Bool { + guard let player = playerForSound(sound) else { + return false + } + + return player.isPlaying + } + + func isPlaying() -> Bool { + let pl = players.filter { + $0.1.isPlaying + } + + return !pl.isEmpty + } + + func stopSound(_ sound: Sound) { + guard let player = playerForSound(sound) else { + return + } + player.stop() + } + + func stopAll() { + for (_, player) in players { + player.stop() + } + } +} + +private extension AudioPlayer { + func playerForSound(_ sound: Sound) -> AVAudioPlayer? { + if let player = players[sound] { + return player + } + + guard let path = Bundle.main.path(forResource: sound.rawValue, ofType: "aac") else { + return nil + } + + guard let player = try? AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) else { + return nil + } + + players[sound] = player + return player + } +} diff --git a/Antidote/AutomationCoordinator.swift b/Antidote/AutomationCoordinator.swift new file mode 100644 index 0000000..87cb50e --- /dev/null +++ b/Antidote/AutomationCoordinator.swift @@ -0,0 +1,110 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import MobileCoreServices +import os + +private struct Constants { + static let MaxFileSizeWiFi: OCTToxFileSize = 100 * 1024 * 1024 + static let MaxFileSizeWWAN: OCTToxFileSize = 20 * 1024 * 1024 +} + +class AutomationCoordinator: NSObject { + fileprivate weak var submanagerFiles: OCTSubmanagerFiles! + + fileprivate var fileMessagesToken: RLMNotificationToken? + fileprivate let userDefaults = UserDefaultsManager() + fileprivate let reachability = Reach() + + init(submanagerObjects: OCTSubmanagerObjects, submanagerFiles: OCTSubmanagerFiles) { + self.submanagerFiles = submanagerFiles + + super.init() + + let predicate = NSPredicate(format: "senderUniqueIdentifier != nil AND messageFile != nil") + let results = submanagerObjects.messages(predicate: predicate) + fileMessagesToken = results.addNotificationBlock { [unowned self] change in + switch change { + case .initial: + break + case .update(let results, _, let insertions, _): + guard let results = results else { + break + } + + for index in insertions { + let message = results[index] + self.proceedNewFileMessage(message) + } + case .error(let error): + fatalError("\(error)") + } + } + } +} + +extension AutomationCoordinator: CoordinatorProtocol { + func startWithOptions(_ options: CoordinatorOptions?) { + // nop + } +} + +private extension AutomationCoordinator { + func proceedNewFileMessage(_ message: OCTMessageAbstract) { + let usingWiFi = self.usingWiFi() + os_log("AutomationCoordinator:usingWiFi=%d", usingWiFi) + switch userDefaults.autodownloadImages { + case .Never: + return + case .UsingWiFi: + if !usingWiFi { + return + } + case .Always: + break + } + + // HINT: now we apply autodownload to all files, not only images + // if !UTTypeConformsTo(message.messageFile!.fileUTI as CFString? ?? "" as CFString, kUTTypeImage) { + // // download images only + // return + // } + + // skip too large files + if usingWiFi { + if message.messageFile!.fileSize > Constants.MaxFileSizeWiFi { + return + } + } + else { + if message.messageFile!.fileSize > Constants.MaxFileSizeWWAN { + return + } + } + + // workaround for deadlock in objcTox https://github.com/Antidote-for-Tox/objcTox/issues/51 + let delayTime = DispatchTime.now() + Double(Int64(0.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) + DispatchQueue.main.asyncAfter(deadline: delayTime) { [weak self] in + self?.submanagerFiles.acceptFileTransfer(message, failureBlock: nil) + } + } + + func usingWiFi() -> Bool + { + switch reachability.connectionStatus() { + case .offline: + return false + case .unknown: + return false + case .online(let type): + switch type { + case .wwan: + return false + case .wiFi: + return true + } + } + } +} diff --git a/Antidote/AvatarManager.swift b/Antidote/AvatarManager.swift new file mode 100644 index 0000000..320392d --- /dev/null +++ b/Antidote/AvatarManager.swift @@ -0,0 +1,131 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class AvatarManager { + enum AvatarType: String { + case Normal + case Call + } + + fileprivate let theme: Theme + fileprivate let cache: NSCache + + init(theme: Theme) { + self.theme = theme + self.cache = NSCache() + } + + /** + Returns round avatar created from string with a given diameter. Searches for an avatar in cache first, + if not found creates it. + + - Parameters: + - string: String to create avatar from. In case of empty string avatar will be set to "?". + - diameter: Diameter of circle with avatar. + + - Returns: Avatar from given string with given size. + */ + func avatarFromString(_ string: String, diameter: CGFloat, type: AvatarType = .Normal) -> UIImage { + var string = string + + if string.isEmpty { + string = "?" + } + + let key = keyFromString(string, diameter: diameter, type: type) + + if let avatar = cache.object(forKey: key as AnyObject) as? UIImage { + return avatar + } + + let avatar = createAvatarFromString(string, diameter: diameter, type: type) + cache.setObject(avatar, forKey: key as AnyObject) + + return avatar + } +} + +private extension AvatarManager { + func keyFromString(_ string: String, diameter: CGFloat, type: AvatarType) -> String { + return "\(string)-\(diameter)-\(type.rawValue)" + } + + func createAvatarFromString(_ string: String, diameter: CGFloat, type: AvatarType) -> UIImage { + let avatarString = avatarStringFromString(string) + + let label = UILabel() + label.layer.borderWidth = 1.0 + label.layer.masksToBounds = true + label.textAlignment = .center + label.text = avatarString + + switch type { + case .Normal: + label.backgroundColor = theme.colorForType(.NormalBackground) + label.layer.borderColor = theme.colorForType(.LinkText).cgColor + label.textColor = theme.colorForType(.LinkText) + case .Call: + label.backgroundColor = .clear + label.layer.borderColor = theme.colorForType(.CallButtonIconColor).cgColor + label.textColor = theme.colorForType(.CallButtonIconColor) + } + + var size: CGSize + var fontSize = diameter + + repeat { + fontSize -= 1 + + let font = UIFont.antidoteFontWithSize(fontSize, weight: .light) + size = avatarString.stringSizeWithFont(font) + } + while (max(size.width, size.height) > diameter) + + let frame = CGRect(x: 0, y: 0, width: diameter, height: diameter) + + label.font = UIFont.antidoteFontWithSize(fontSize * 0.6, weight: .light) + label.layer.cornerRadius = frame.size.width / 2 + label.frame = frame + + return imageWithLabel(label) + } + + func avatarStringFromString(_ string: String) -> String { + guard !string.isEmpty else { + return "" + } + + // Avatar can have alphanumeric symbols and ? sign. + let badSymbols = (CharacterSet.alphanumerics.inverted as NSCharacterSet).mutableCopy() as! NSMutableCharacterSet + badSymbols.removeCharacters(in: "?") + + let words = string.components(separatedBy: CharacterSet.whitespaces).map { + $0.components(separatedBy: badSymbols as CharacterSet).joined(separator: "") + }.filter { + !$0.isEmpty + } + + var result = words.map { + $0.isEmpty ? "" : $0[$0.startIndex ..< $0.index($0.startIndex, offsetBy: 1)] + }.joined(separator: "") + + let numberOfLetters = min(2, result.count) + + result = result.uppercased() + return String(result[result.startIndex ..< result.index(result.startIndex, offsetBy: numberOfLetters)]) + } + + func imageWithLabel(_ label: UILabel) -> UIImage { + UIGraphicsBeginImageContextWithOptions(label.bounds.size, false, 0.0) + label.layer.render(in: UIGraphicsGetCurrentContext()!) + + let image = UIGraphicsGetImageFromCurrentImageContext() + + UIGraphicsEndImageContext() + + return image! + } +} diff --git a/Antidote/BaseCell.swift b/Antidote/BaseCell.swift new file mode 100644 index 0000000..c9a9df4 --- /dev/null +++ b/Antidote/BaseCell.swift @@ -0,0 +1,39 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +class BaseCell: UITableViewCell { + static var staticReuseIdentifier: String { + get { + return NSStringFromClass(self) + } + } + + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + createViews() + installConstraints() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /** + Override this method in subclass. + */ + func setupWithTheme(_ theme: Theme, model: BaseCellModel) {} + + /** + Override this method in subclass. + */ + func createViews() {} + + /** + Override this method in subclass. + */ + func installConstraints() {} +} diff --git a/Antidote/BaseCellModel.swift b/Antidote/BaseCellModel.swift new file mode 100644 index 0000000..cf547b5 --- /dev/null +++ b/Antidote/BaseCellModel.swift @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class BaseCellModel { + +} diff --git a/Antidote/BubbleView.swift b/Antidote/BubbleView.swift new file mode 100644 index 0000000..30bed86 --- /dev/null +++ b/Antidote/BubbleView.swift @@ -0,0 +1,104 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let TextViewMinWidth = 5.0 + static let TextViewMaxWidth = 260.0 + static let TextViewMinHeight = 10.0 + + static let TextViewVerticalOffset = 1.0 + static let TextViewHorizontalOffset = 5.0 +} + +class BubbleView: UIView { + fileprivate var textView: UITextView! + + var text: String? { + get { + return textView.text + } + set { + textView.text = newValue + } + } + + var attributedText: NSAttributedString? { + get { + return textView.attributedText + } + set { + textView.attributedText = newValue + } + } + + var textColor: UIColor { + get { + return textView.textColor! + } + set { + textView.textColor = newValue + } + } + + var font: UIFont? { + get { + return textView.font + } + set { + textView.font = newValue + } + } + + override var tintColor: UIColor! { + didSet { + textView.linkTextAttributes = [ + NSAttributedStringKey.foregroundColor.rawValue: tintColor, + NSAttributedStringKey.underlineStyle.rawValue: NSUnderlineStyle.styleSingle.rawValue, + ] + } + } + + var selectable: Bool { + get { + return textView.isSelectable + } + set { + textView.isSelectable = newValue + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + layer.cornerRadius = 12.0 + layer.masksToBounds = true + + textView = UITextView() + textView.backgroundColor = .clear + textView.isEditable = false + textView.isScrollEnabled = false + textView.dataDetectorTypes = .all + textView.font = UIFont.systemFont(ofSize: 16.0) + + addSubview(textView) + + textView.snp.makeConstraints { + $0.top.equalTo(self).offset(Constants.TextViewVerticalOffset) + $0.bottom.equalTo(self).offset(-Constants.TextViewVerticalOffset) + $0.leading.equalTo(self).offset(Constants.TextViewHorizontalOffset) + $0.trailing.equalTo(self).offset(-Constants.TextViewHorizontalOffset) + + $0.width.greaterThanOrEqualTo(Constants.TextViewMinWidth) + $0.width.lessThanOrEqualTo(Constants.TextViewMaxWidth) + $0.height.greaterThanOrEqualTo(Constants.TextViewMinHeight) + } + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Antidote/Bus.swift b/Antidote/Bus.swift new file mode 100644 index 0000000..d170c09 --- /dev/null +++ b/Antidote/Bus.swift @@ -0,0 +1,86 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// +// Bus.swift +// CLBackgroundAccess +// +// Created by Samer Murad on 10.04.21. +// + +import Foundation +import UIKit + +// Convenience wrapper around NotificationCenter + +// MARK: - Decleration +class Bus { + typealias Unsubscriber = () -> Void + static let shared = Bus() + private init() {} +} + +// MARK: - Subscribe / Post +extension Bus { + /// Subscribe to an event, return an Unsubscriber method (call to remove sub) + func on(event: Events, object: Any? = nil, queue: OperationQueue? = nil, cb: @escaping (Notification) -> Void) -> Unsubscriber { + let center = NotificationCenter.default + let notificationName = event.notifciationName() + + let observer = center.addObserver(forName: notificationName, object: object, queue: queue, using: cb) + return { + if object != nil { + center.removeObserver(observer, name: notificationName, object: object) + } else { + center.removeObserver(observer) + } + } + } + + /// Post event + func post(event: Events, object: Any? = nil, userInfo: [AnyHashable: Any]? = nil) { + guard event.isManualPostSupported() else { return } + let center = NotificationCenter.default + print("Event dispatch:", event) + center.post(name: event.notifciationName(), object: object, userInfo: userInfo) + } +} + +// MARK: - Events Enum +extension Bus { + enum Events: String { + // Location Events + case LocationUpdate + case LocationAuthUpdate + case LocationManagerStateChange + // Builtin Events + case AppEnteredBackground + case AppEnteredForeground + } + +} + +// MARK: - Events enum Notification.Name support and system events guard +extension Bus.Events { + func notifciationName() -> Notification.Name { + //switch self { + //case .AppEnteredBackground: + // return UIApplication.NSNotification.Name.UIApplicationDidEnterBackground + //case .AppEnteredForeground: + // return UIApplication.NSNotification.Name.UIApplicationWillEnterForeground + //default: + return Notification.Name(self.rawValue) + //} + } + + func isManualPostSupported() -> Bool { + let name = Notification.Name(self.rawValue) + let actualNotificationName = self.notifciationName() + let isSupported = name == actualNotificationName + if !isSupported { + print("WARN: Event \"", self, "\" Wrapps the System Event \"", actualNotificationName.rawValue, "\" And should not be posted manually") + } + return isSupported + } +} diff --git a/Antidote/CallActiveController.swift b/Antidote/CallActiveController.swift new file mode 100644 index 0000000..b33a859 --- /dev/null +++ b/Antidote/CallActiveController.swift @@ -0,0 +1,403 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +protocol CallActiveControllerDelegate: class { + func callActiveController(_ controller: CallActiveController, mute: Bool) + func callActiveController(_ controller: CallActiveController, speaker: Bool) + func callActiveController(_ controller: CallActiveController, outgoingVideo: Bool) + func callActiveControllerDecline(_ controller: CallActiveController) + func callActiveControllerSwitchCamera(_ controller: CallActiveController) +} + +private struct Constants { + static let BigCenterContainerTopOffset = 50.0 + static let BigButtonOffset = 30.0 + + static let SmallButtonOffset = 20.0 + static let SmallBottomOffset: CGFloat = -20.0 + + static let VideoPreviewOffset = -20.0 + static let VideoPreviewSize = CGSize(width: 150.0, height: 110) + static let SwitchCameraOffset = 5.0 + + static let ControlsAnimationDuration = 0.3 +} + +class CallActiveController: CallBaseController { + enum State { + case none + case reaching + case active(duration: TimeInterval) + } + + weak var delegate: CallActiveControllerDelegate? + + var state: State = .none { + didSet { + // load view + _ = view + + switch state { + case .none: + infoLabel.text = nil + case .reaching: + infoLabel.text = String(localized: "call_reaching") + + bigVideoButton?.isEnabled = false + smallVideoButton?.isEnabled = false + case .active(let duration): + infoLabel.text = String(timeInterval: duration) + + bigVideoButton?.isEnabled = true + smallVideoButton?.isEnabled = true + } + } + } + + var mute: Bool = false { + didSet { + bigMuteButton?.isSelected = mute + smallMuteButton?.isSelected = mute + } + } + + var speaker: Bool = false { + didSet { + bigSpeakerButton?.isSelected = speaker + smallSpeakerButton?.isSelected = speaker + } + } + + var outgoingVideo: Bool = false { + didSet { + bigVideoButton?.isSelected = outgoingVideo + smallVideoButton?.isSelected = outgoingVideo + } + } + + var videoFeed: UIView? { + didSet { + if oldValue === videoFeed { + return + } + + if let old = oldValue { + old.removeFromSuperview() + } + + if let feed = videoFeed { + view.insertSubview(feed, belowSubview: videoPreviewView) + + feed.bounds.size = view.bounds.size + + feed.snp.makeConstraints { + $0.edges.equalTo(view) + } + + updateViewsWithTraitCollection(self.traitCollection) + } + } + } + + var videoPreviewLayer: CALayer? { + didSet { + if oldValue === videoPreviewLayer { + return + } + + if let old = oldValue { + old.removeFromSuperlayer() + videoPreviewView.isHidden = true + } + + if let layer = videoPreviewLayer { + videoPreviewView.layer.addSublayer(layer) + videoPreviewView.bringSubview(toFront: switchCameraButton) + videoPreviewView.isHidden = false + view.layoutIfNeeded() + } + + updateViewsWithTraitCollection(self.traitCollection) + } + } + + fileprivate var showControls = true { + didSet { + let offset = showControls ? Constants.SmallBottomOffset : smallContainerView.frame.size.height + smallContainerViewBottomConstraint.update(offset: offset) + + toggleTopContainer(hidden: !showControls) + + UIView.animate(withDuration: Constants.ControlsAnimationDuration, animations: { [unowned self] in + self.view.layoutIfNeeded() + }) + } + } + + fileprivate var videoPreviewView: UIView! + fileprivate var switchCameraButton: UIButton! + + fileprivate var bigContainerView: UIView! + fileprivate var bigCenterContainer: UIView! + fileprivate var bigMuteButton: CallButton? + fileprivate var bigSpeakerButton: CallButton? + fileprivate var bigVideoButton: CallButton? + fileprivate var bigDeclineButton: CallButton? + + fileprivate var smallContainerViewBottomConstraint: Constraint! + + fileprivate var smallContainerView: UIView! + fileprivate var smallMuteButton: CallButton? + fileprivate var smallSpeakerButton: CallButton? + fileprivate var smallVideoButton: CallButton? + fileprivate var smallDeclineButton: CallButton? + + override init(theme: Theme, callerName: String) { + super.init(theme: theme, callerName: callerName) + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + super.loadView() + + createGestureRecognizers() + createVideoPreviewView() + createBigViews() + createSmallViews() + installConstraints() + + view.bringSubview(toFront: topContainer) + + setButtonsInitValues() + + updateViewsWithTraitCollection(self.traitCollection) + } + + override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { + updateViewsWithTraitCollection(newCollection) + showControls = true + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + if let layer = videoPreviewLayer { + layer.frame.size = videoPreviewView.frame.size + } + } + + override func prepareForRemoval() { + super.prepareForRemoval() + + bigMuteButton?.isEnabled = false + bigSpeakerButton?.isEnabled = false + bigVideoButton?.isEnabled = false + bigDeclineButton?.isEnabled = false + + smallMuteButton?.isEnabled = false + smallSpeakerButton?.isEnabled = false + smallVideoButton?.isEnabled = false + smallDeclineButton?.isEnabled = false + } +} + +// MARK: Actions +extension CallActiveController { + @objc func tapOnView() { + guard !smallContainerView.isHidden else { + return + } + + showControls = !showControls + } + + @objc func muteButtonPressed(_ button: CallButton) { + mute = !button.isSelected + delegate?.callActiveController(self, mute: mute) + } + + @objc func speakerButtonPressed(_ button: CallButton) { + speaker = !button.isSelected + delegate?.callActiveController(self, speaker: speaker) + } + + @objc func videoButtonPressed(_ button: CallButton) { + outgoingVideo = !button.isSelected + delegate?.callActiveController(self, outgoingVideo: outgoingVideo) + } + + @objc func declineButtonPressed() { + delegate?.callActiveControllerDecline(self) + } + + @objc func switchCameraButtonPressed() { + delegate?.callActiveControllerSwitchCamera(self) + } +} + +private extension CallActiveController { + func createGestureRecognizers() { + let tapGR = UITapGestureRecognizer(target: self, action: #selector(CallActiveController.tapOnView)) + view.addGestureRecognizer(tapGR) + } + + func createVideoPreviewView() { + videoPreviewView = UIView() + videoPreviewView.backgroundColor = theme.colorForType(.CallVideoPreviewBackground) + view.addSubview(videoPreviewView) + + videoPreviewView.isHidden = !outgoingVideo + + let image = UIImage.templateNamed("switch-camera") + + switchCameraButton = UIButton() + switchCameraButton.tintColor = theme.colorForType(.CallButtonIconColor) + switchCameraButton.setImage(image, for: UIControlState()) + switchCameraButton.addTarget(self, action: #selector(CallActiveController.switchCameraButtonPressed), for: .touchUpInside) + videoPreviewView.addSubview(switchCameraButton) + } + + func createBigViews() { + bigContainerView = UIView() + bigContainerView.backgroundColor = .clear + view.addSubview(bigContainerView) + + bigCenterContainer = UIView() + bigCenterContainer.backgroundColor = .clear + bigContainerView.addSubview(bigCenterContainer) + + bigMuteButton = addButtonWithType(.mute, buttonSize: .big, action: #selector(CallActiveController.muteButtonPressed(_:)), container: bigCenterContainer) + bigSpeakerButton = addButtonWithType(.speaker, buttonSize: .big, action: #selector(CallActiveController.speakerButtonPressed(_:)), container: bigCenterContainer) + bigVideoButton = addButtonWithType(.video, buttonSize: .big, action: #selector(CallActiveController.videoButtonPressed(_:)), container: bigCenterContainer) + bigDeclineButton = addButtonWithType(.decline, buttonSize: .small, action: #selector(CallActiveController.declineButtonPressed), container: bigContainerView) + } + + func createSmallViews() { + smallContainerView = UIView() + smallContainerView.backgroundColor = .clear + view.addSubview(smallContainerView) + + smallMuteButton = addButtonWithType(.mute, buttonSize: .small, action: #selector(CallActiveController.muteButtonPressed(_:)), container: smallContainerView) + smallSpeakerButton = addButtonWithType(.speaker, buttonSize: .small, action: #selector(CallActiveController.speakerButtonPressed(_:)), container: smallContainerView) + smallVideoButton = addButtonWithType(.video, buttonSize: .small, action: #selector(CallActiveController.videoButtonPressed(_:)), container: smallContainerView) + smallDeclineButton = addButtonWithType(.decline, buttonSize: .small, action: #selector(CallActiveController.declineButtonPressed), container: smallContainerView) + } + + func addButtonWithType(_ type: CallButton.ButtonType, buttonSize: CallButton.ButtonSize, action: Selector, container: UIView) -> CallButton { + let button = CallButton(theme: theme, type: type, buttonSize: buttonSize) + button.addTarget(self, action: action, for: .touchUpInside) + container.addSubview(button) + + return button + } + + func installConstraints() { + videoPreviewView.snp.makeConstraints { + $0.trailing.equalTo(view).offset(Constants.VideoPreviewOffset) + $0.bottom.equalTo(smallContainerView.snp.top).offset(Constants.VideoPreviewOffset) + $0.width.equalTo(Constants.VideoPreviewSize.width) + $0.height.equalTo(Constants.VideoPreviewSize.height) + } + + switchCameraButton.snp.makeConstraints { + $0.top.equalTo(videoPreviewView).offset(Constants.SwitchCameraOffset) + $0.trailing.equalTo(videoPreviewView).offset(-Constants.SwitchCameraOffset) + } + + bigContainerView.snp.makeConstraints { + $0.top.equalTo(topContainer.snp.bottom) + $0.leading.trailing.bottom.equalTo(view) + } + + bigCenterContainer.snp.makeConstraints { + $0.centerX.equalTo(bigContainerView) + $0.centerY.equalTo(view) + } + + bigMuteButton!.snp.makeConstraints { + $0.top.equalTo(bigCenterContainer) + $0.leading.equalTo(bigCenterContainer) + } + + bigSpeakerButton!.snp.makeConstraints { + $0.top.equalTo(bigCenterContainer) + $0.trailing.equalTo(bigCenterContainer) + $0.leading.equalTo(bigMuteButton!.snp.trailing).offset(Constants.BigButtonOffset) + } + + bigVideoButton!.snp.makeConstraints { + $0.top.equalTo(bigMuteButton!.snp.bottom).offset(Constants.BigButtonOffset) + $0.leading.equalTo(bigCenterContainer) + $0.bottom.equalTo(bigCenterContainer) + } + + bigDeclineButton!.snp.makeConstraints { + $0.centerX.equalTo(bigContainerView) + $0.top.greaterThanOrEqualTo(bigCenterContainer).offset(Constants.BigButtonOffset) + $0.bottom.equalTo(bigContainerView).offset(-Constants.BigButtonOffset) + } + + smallContainerView.snp.makeConstraints { + smallContainerViewBottomConstraint = $0.bottom.equalTo(view).offset(Constants.SmallBottomOffset).constraint + $0.centerX.equalTo(view) + } + + smallMuteButton!.snp.makeConstraints { + $0.top.bottom.equalTo(smallContainerView) + $0.leading.equalTo(smallContainerView) + } + + smallSpeakerButton!.snp.makeConstraints { + $0.top.bottom.equalTo(smallContainerView) + $0.leading.equalTo(smallMuteButton!.snp.trailing).offset(Constants.SmallButtonOffset) + } + + smallVideoButton!.snp.makeConstraints { + $0.top.bottom.equalTo(smallContainerView) + $0.leading.equalTo(smallSpeakerButton!.snp.trailing).offset(Constants.SmallButtonOffset) + } + + smallDeclineButton!.snp.makeConstraints { + $0.top.bottom.equalTo(smallContainerView) + $0.leading.equalTo(smallVideoButton!.snp.trailing).offset(Constants.SmallButtonOffset) + $0.trailing.equalTo(smallContainerView) + } + } + + func setButtonsInitValues() { + bigMuteButton?.isSelected = mute + smallMuteButton?.isSelected = mute + + bigSpeakerButton?.isSelected = speaker + smallSpeakerButton?.isSelected = speaker + + bigVideoButton?.isSelected = outgoingVideo + smallVideoButton?.isSelected = outgoingVideo + } + + func updateViewsWithTraitCollection(_ traitCollection: UITraitCollection) { + if videoFeed != nil || videoPreviewLayer != nil { + bigContainerView.isHidden = true + smallContainerView.isHidden = false + return + } + + switch traitCollection.verticalSizeClass { + case .regular: + bigContainerView.isHidden = false + smallContainerView.isHidden = true + case .unspecified: + fallthrough + case .compact: + bigContainerView.isHidden = true + smallContainerView.isHidden = false + } + } +} diff --git a/Antidote/CallBaseController.swift b/Antidote/CallBaseController.swift new file mode 100644 index 0000000..595af78 --- /dev/null +++ b/Antidote/CallBaseController.swift @@ -0,0 +1,117 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let TopContainerHeight = 80.0 + static let CallerLabelTopOffset = 20.0 + static let InfoLabelBottomOffset = -5.0 + static let LabelHorizontalOffset = 20.0 +} + +class CallBaseController: UIViewController { + let theme: Theme + + let callerName: String + + var topContainer: UIView! + var callerLabel: UILabel! + var infoLabel: UILabel! + + fileprivate var topContainerTopConstraint: Constraint! + + init(theme: Theme, callerName: String) { + self.theme = theme + self.callerName = callerName + + super.init(nibName: nil, bundle: nil) + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + // HINT: turn on ProximitySensor to blank screen when near the ear + UIDevice.current.isProximityMonitoringEnabled = true + } + + deinit { + // HINT: reset ProximitySensor when calling screen is closed + UIDevice.current.isProximityMonitoringEnabled = false + } + + override func loadView() { + loadViewWithBackgroundColor(.clear) + + addBlurredBackground() + createTopViews() + installConstraints() + } + + /** + Prepare for removal by disabling all active views. + */ + func prepareForRemoval() { + infoLabel.text = String(localized: "call_ended") + } + + func toggleTopContainer(hidden: Bool) { + let offset = hidden ? -topContainer.frame.size.height : 0.0 + topContainerTopConstraint.update(offset: offset) + } +} + +private extension CallBaseController { + func addBlurredBackground() { + let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) + effectView.frame = view.bounds + + view.insertSubview(effectView, at: 0) + effectView.snp.makeConstraints { + $0.edges.equalTo(view) + } + } + + func createTopViews() { + topContainer = UIView() // UIVisualEffectView(effect: UIBlurEffect(style: .dark)) + view.addSubview(topContainer) + + callerLabel = UILabel() + callerLabel.text = callerName + callerLabel.textColor = theme.colorForType(.CallTextColor) + callerLabel.textAlignment = .center + callerLabel.font = UIFont.systemFont(ofSize: 20.0) + topContainer.addSubview(callerLabel) + + infoLabel = UILabel() + infoLabel.textColor = theme.colorForType(.CallTextColor) + infoLabel.textAlignment = .center + infoLabel.font = UIFont.antidoteFontWithSize(18.0, weight: .light) + topContainer.addSubview(infoLabel) + } + + func installConstraints() { + topContainer.snp.makeConstraints { + topContainerTopConstraint = $0.top.equalTo(view).constraint + $0.top.leading.trailing.equalTo(view) + $0.height.equalTo(Constants.TopContainerHeight) + } + + callerLabel.snp.makeConstraints { + $0.top.equalTo(topContainer).offset(Constants.CallerLabelTopOffset) + $0.leading.equalTo(topContainer).offset(Constants.LabelHorizontalOffset) + $0.trailing.equalTo(topContainer).offset(-Constants.LabelHorizontalOffset) + } + + infoLabel.snp.makeConstraints { + $0.bottom.equalTo(topContainer).offset(Constants.InfoLabelBottomOffset) + $0.leading.equalTo(topContainer).offset(Constants.LabelHorizontalOffset) + $0.trailing.equalTo(topContainer).offset(-Constants.LabelHorizontalOffset) + } + } +} diff --git a/Antidote/CallButton.swift b/Antidote/CallButton.swift new file mode 100644 index 0000000..3df51d4 --- /dev/null +++ b/Antidote/CallButton.swift @@ -0,0 +1,132 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +private struct Constants { + static let SmallSize: CGFloat = 60.0 + static let BigSize: CGFloat = 80.0 + static let ImageSize: CGFloat = 30.0 +} + +class CallButton: UIButton { + enum ButtonSize { + case small + case big + } + + enum ButtonType { + case decline + case answerAudio + case answerVideo + case mute + case speaker + case video + } + + override var isSelected: Bool { + didSet { + if let selectedTintColor = selectedTintColor { + tintColor = isSelected ? selectedTintColor : normalTintColor + } + } + } + + override var isHighlighted: Bool { + didSet { + if isHighlighted { + tintColor = normalTintColor + } + else { + if let selectedTintColor = selectedTintColor { + tintColor = isSelected ? selectedTintColor : normalTintColor + } + } + } + } + + fileprivate let buttonSize: ButtonSize + fileprivate let normalTintColor: UIColor + fileprivate var selectedTintColor: UIColor? + + init(theme: Theme, type: ButtonType, buttonSize: ButtonSize) { + self.buttonSize = buttonSize + self.normalTintColor = theme.colorForType(.CallButtonIconColor) + + super.init(frame: CGRect.zero) + + switch buttonSize { + case .small: + layer.cornerRadius = Constants.SmallSize / 2 + case .big: + layer.cornerRadius = Constants.BigSize / 2 + } + layer.masksToBounds = true + + let imageName: String + let backgroundColor: UIColor + var selectedBackgroundColor: UIColor? = nil + + switch type { + case .decline: + imageName = "end-call" + backgroundColor = theme.colorForType(.CallDeclineButtonBackground) + case .answerAudio: + imageName = "start-call-30" + backgroundColor = theme.colorForType(.CallAnswerButtonBackground) + case .answerVideo: + imageName = "video-call-30" + backgroundColor = theme.colorForType(.CallAnswerButtonBackground) + case .mute: + imageName = "mute" + backgroundColor = theme.colorForType(.CallControlBackground) + selectedTintColor = theme.colorForType(.CallButtonSelectedIconColor) + selectedBackgroundColor = theme.colorForType(.CallControlSelectedBackground) + case .speaker: + imageName = "speaker" + backgroundColor = theme.colorForType(.CallControlBackground) + selectedTintColor = theme.colorForType(.CallButtonSelectedIconColor) + selectedBackgroundColor = theme.colorForType(.CallControlSelectedBackground) + case .video: + imageName = "video-call-30" + backgroundColor = theme.colorForType(.CallControlBackground) + selectedTintColor = theme.colorForType(.CallButtonSelectedIconColor) + selectedBackgroundColor = theme.colorForType(.CallControlSelectedBackground) + } + + tintColor = normalTintColor + + switch type { + case .mute: + let image = UIImage.templateNamed("mute") + setImage(image, for: .normal) + let image2 = UIImage.templateNamed("mute-selected") + setImage(image2, for: .selected) + default: + let image = UIImage.templateNamed(imageName) + setImage(image, for: UIControlState()) + } + + let backgroundImage = UIImage.imageWithColor(backgroundColor, size: CGSize(width: 1.0, height: 1.0)) + setBackgroundImage(backgroundImage, for:UIControlState()) + + if let selected = selectedBackgroundColor { + let backgroundImage = UIImage.imageWithColor(selected, size: CGSize(width: 1.0, height: 1.0)) + setBackgroundImage(backgroundImage, for:UIControlState.selected) + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var intrinsicContentSize : CGSize { + switch buttonSize { + case .small: + return CGSize(width: Constants.SmallSize, height: Constants.SmallSize) + case .big: + return CGSize(width: Constants.BigSize, height: Constants.BigSize) + } + } +} diff --git a/Antidote/CallCoordinator.swift b/Antidote/CallCoordinator.swift new file mode 100644 index 0000000..1ef8ce5 --- /dev/null +++ b/Antidote/CallCoordinator.swift @@ -0,0 +1,396 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import CallKit + +protocol CallCoordinatorDelegate: class { + func callCoordinator(_ coordinator: CallCoordinator, notifyAboutBackgroundCallFrom caller: String, userInfo: String) + func callCoordinatorDidStartCall(_ coordinator: CallCoordinator) + func callCoordinatorDidFinishCall(_ coordinator: CallCoordinator) +} + +private struct Constants { + static let DeclineAfterInterval = 1.5 +} + +private class ActiveCall { + var callToken: RLMNotificationToken? + + fileprivate let call: OCTCall + fileprivate let navigation: UINavigationController + + fileprivate var usingFrontCamera: Bool = true + + init(call: OCTCall, navigation: UINavigationController) { + self.call = call + self.navigation = navigation + } + + deinit { + callToken?.invalidate() + } +} + +class CallCoordinator: NSObject { + weak var delegate: CallCoordinatorDelegate? + + fileprivate let theme: Theme + fileprivate weak var presentingController: UIViewController! + fileprivate weak var submanagerCalls: OCTSubmanagerCalls! + fileprivate weak var submanagerObjects: OCTSubmanagerObjects! + fileprivate var providerdelegate: ProviderDelegate! + + fileprivate let audioPlayer = AudioPlayer() + + fileprivate var activeCall: ActiveCall? { + didSet { + switch (oldValue, activeCall) { + case (.none, .some): + delegate?.callCoordinatorDidStartCall(self) + case (.some, .none): + delegate?.callCoordinatorDidFinishCall(self) + default: + break + } + } + } + + init(theme: Theme, presentingController: UIViewController, submanagerCalls: OCTSubmanagerCalls, submanagerObjects: OCTSubmanagerObjects) { + self.theme = theme + self.presentingController = presentingController + self.submanagerCalls = submanagerCalls + self.submanagerObjects = submanagerObjects + + super.init() + + // CALL: + print("cc:controler:init:01") + + submanagerCalls.delegate = self + } + + func callToChat(_ chat: OCTChat, enableVideo: Bool) { + + // CALL: + print("cc:controler:callToChat:01") + + do { + let call = try submanagerCalls.call(to: chat, enableAudio: true, enableVideo: enableVideo) + var nickname = String(localized: "contact_deleted") + + if let friend = chat.friends.lastObject() as? OCTFriend { + nickname = friend.nickname + } + + let controller = CallActiveController(theme: theme, callerName: nickname) + controller.delegate = self + + // CALL: + print("cc:controler:callToChat:02") + + startActiveCallWithCall(call, controller: controller) + } + catch let error as NSError { + handleErrorWithType(.callToChat, error: error) + } + } + + func answerIncomingCallWithUserInfo(_ userInfo: String) { + + // CALL: + print("cc:controler:answerIncomingCallWithUserInfo:01") + + guard let activeCall = activeCall else { return } + guard activeCall.call.uniqueIdentifier == userInfo else { return } + guard activeCall.call.status == .ringing else { return } + + answerCall(enableVideo: false) + } +} + +extension CallCoordinator: CoordinatorProtocol { + func startWithOptions(_ options: CoordinatorOptions?) { + } +} + +extension CallCoordinator: OCTSubmanagerCallDelegate { + func callSubmanager(_ callSubmanager: OCTSubmanagerCalls!, receive call: OCTCall!, audioEnabled: Bool, videoEnabled: Bool) { + guard activeCall == nil else { + // Currently we support only one call at a time + _ = try? submanagerCalls.send(.cancel, to: call) + return + } + + let nickname = call.caller?.nickname ?? "" + + // CALL: start incoming call + print("cc:controler:incoming_call:01") + + if !UIApplication.isActive { + delegate?.callCoordinator(self, notifyAboutBackgroundCallFrom: nickname, userInfo: call.uniqueIdentifier) + // CALL: start incoming call + print("cc:controler:incoming_call:BG") + + let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil) + DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 0.1) { + AppDelegate.shared.displayIncomingCall(uuid: UUID(), handle: nickname, hasVideo: false) { _ in + UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier) + } + } + } + + let controller = CallIncomingController(theme: theme, callerName: nickname) + controller.delegate = self + + startActiveCallWithCall(call, controller: controller) + + print("cc:controler:incoming_call:99") + } +} + +extension CallCoordinator: CallIncomingControllerDelegate { + func callIncomingControllerDecline(_ controller: CallIncomingController) { + // CALL: + print("cc:controler:callIncomingControllerDecline:01") + declineCall(callWasRemoved: false) + } + + func callIncomingControllerAnswerAudio(_ controller: CallIncomingController) { + // CALL: + print("cc:controler:callIncomingControllerAnswerAudio:01") + answerCall(enableVideo: false) + } + + func callIncomingControllerAnswerVideo(_ controller: CallIncomingController) { + // CALL: + print("cc:controler:callIncomingControllerAnswerVideo:01") + answerCall(enableVideo: true) + } +} + +extension CallCoordinator: CallActiveControllerDelegate { + func callActiveController(_ controller: CallActiveController, mute: Bool) { + submanagerCalls.enableMicrophone = !mute + } + + func callActiveController(_ controller: CallActiveController, speaker: Bool) { + do { + try submanagerCalls.routeAudio(toSpeaker: speaker) + } + catch { + handleErrorWithType(.routeAudioToSpeaker) + controller.speaker = !speaker + } + } + + func callActiveController(_ controller: CallActiveController, outgoingVideo: Bool) { + guard let activeCall = activeCall else { + assert(false, "This method should be called only if active call is non-nil") + return + } + + do { + try submanagerCalls.enableVideoSending(outgoingVideo, for: activeCall.call) + } + catch { + handleErrorWithType(.enableVideoSending) + controller.outgoingVideo = !outgoingVideo + } + } + + func callActiveControllerDecline(_ controller: CallActiveController) { + // CALL: + print("cc:controler:callActiveControllerDecline:02") + declineCall(callWasRemoved: false) + } + + func callActiveControllerSwitchCamera(_ controller: CallActiveController) { + guard let activeCall = activeCall else { + assert(false, "This method should be called only if active call is non-nil") + return + } + + do { + let front = !activeCall.usingFrontCamera + try submanagerCalls.switch(toCameraFront: front) + + self.activeCall?.usingFrontCamera = front + } + catch { + handleErrorWithType(.callSwitchCamera) + } + } +} + +extension CallCoordinator { + func declineCall(callWasRemoved wasRemoved: Bool) { + // CALL: + print("cc:controler:declineCall:01") + + guard let activeCall = activeCall else { + // assert(false, "This method should be called only if active call is non-nil") + return + } + + if !wasRemoved { + _ = try? submanagerCalls.send(.cancel, to: activeCall.call) + } + + audioPlayer.stopAll() + + if let controller = activeCall.navigation.topViewController as? CallBaseController { + controller.prepareForRemoval() + } + + let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil) + DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 0.1) { + AppDelegate.shared.endIncomingCalls() + UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier) + } + // self.providerdelegate.endIncomingCall() + + let delayTime = DispatchTime.now() + Double(Int64(Constants.DeclineAfterInterval * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) + DispatchQueue.main.asyncAfter(deadline: delayTime) { [weak self] in + self?.presentingController.dismiss(animated: true, completion: nil) + self?.activeCall = nil + } + } + + func startActiveCallWithCall(_ call: OCTCall, controller: CallBaseController) { + guard activeCall == nil else { + assert(false, "This method should be called only if there is no active call") + return + } + + // CALL: + print("cc:controler:startActiveCallWithCall:01") + + let navigation = UINavigationController(rootViewController: controller) + navigation.modalPresentationStyle = .overCurrentContext + navigation.isNavigationBarHidden = true + navigation.modalTransitionStyle = .crossDissolve + + activeCall = ActiveCall(call: call, navigation: navigation) + + let predicate = NSPredicate(format: "uniqueIdentifier == %@", call.uniqueIdentifier) + let results = submanagerObjects.calls(predicate: predicate) + activeCall!.callToken = results.addNotificationBlock { [unowned self] change in + switch change { + case .initial: + break + case .update(_, let deletions, _, let modifications): + if deletions.count > 0 { + self.declineCall(callWasRemoved: true) + } + else if modifications.count > 0 { + self.activeCallWasUpdated() + } + case .error(let error): + fatalError("\(error)") + } + } + + presentingController.present(navigation, animated: true, completion: nil) + activeCallWasUpdated() + } + + func answerCall(enableVideo: Bool) { + + // CALL: + print("cc:controler:answerCall:01") + + guard let activeCall = activeCall else { + // assert(false, "This method should be called only if active call is non-nil") + return + } + + guard activeCall.call.status == .ringing else { + // assert(false, "Call status should be .Ringing") + return + } + + do { + try submanagerCalls.answer(activeCall.call, enableAudio: true, enableVideo: enableVideo) + } + catch let error as NSError { + handleErrorWithType(.answerCall, error: error) + + declineCall(callWasRemoved: false) + } + } + + func activeCallWasUpdated() { + + // CALL: + print("cc:controler:activeCallWasUpdated:01") + + guard let activeCall = activeCall else { + assert(false, "This method should be called only if active call is non-nil") + return + } + + switch activeCall.call.status { + case .ringing: + if !audioPlayer.isPlayingSound(.Ringtone) { + audioPlayer.playSound(.Ringtone, loop: true) + } + + // no update for ringing status + return + case .dialing: + if !audioPlayer.isPlayingSound(.Calltone) { + audioPlayer.playSound(.Calltone, loop: true) + } + case .active: + if audioPlayer.isPlaying() { + audioPlayer.stopAll() + } + } + + var activeController = activeCall.navigation.topViewController as? CallActiveController + + if (activeController == nil) { + let nickname = activeCall.call.caller?.nickname ?? "" + activeController = CallActiveController(theme: theme, callerName: nickname) + activeController!.delegate = self + + activeCall.navigation.setViewControllers([activeController!], animated: false) + } + + switch activeCall.call.status { + case .ringing: + break + case .dialing: + activeController!.state = .reaching + case .active: + activeController!.state = .active(duration: activeCall.call.callDuration) + } + + activeController!.outgoingVideo = activeCall.call.videoIsEnabled + if activeCall.call.videoIsEnabled { + if activeController!.videoPreviewLayer == nil { + submanagerCalls.getVideoCallPreview { [weak activeController] layer in + activeController?.videoPreviewLayer = layer + } + } + } + else { + if activeController!.videoPreviewLayer != nil { + activeController!.videoPreviewLayer = nil + } + } + + if activeCall.call.friendSendingVideo { + if activeController!.videoFeed == nil { + activeController!.videoFeed = submanagerCalls.videoFeed() + } + } + else { + if activeController!.videoFeed != nil { + activeController!.videoFeed = nil + } + } + } +} diff --git a/Antidote/CallIncomingController.swift b/Antidote/CallIncomingController.swift new file mode 100644 index 0000000..3cded60 --- /dev/null +++ b/Antidote/CallIncomingController.swift @@ -0,0 +1,125 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let AvatarSize: CGFloat = 140.0 + + static let ButtonContainerTopMinOffset = 10.0 + static let ButtonContainerBottomOffset = -50.0 + + static let ButtonHorizontalOffset = 20.0 +} + +protocol CallIncomingControllerDelegate: class { + func callIncomingControllerDecline(_ controller: CallIncomingController) + func callIncomingControllerAnswerAudio(_ controller: CallIncomingController) + func callIncomingControllerAnswerVideo(_ controller: CallIncomingController) +} + +class CallIncomingController: CallBaseController { + weak var delegate: CallIncomingControllerDelegate? + + fileprivate var avatarView: UIImageView! + + fileprivate var buttonContainer: UIView! + fileprivate var declineButton: CallButton! + fileprivate var audioButton: CallButton! + fileprivate var videoButton: CallButton! + fileprivate var uuid_call: UUID! + + override func loadView() { + super.loadView() + + createViews() + installConstraints() + + infoLabel.text = String(localized: "call_incoming") + } + + override func viewDidLoad() { + super.viewDidLoad() + } + + override func prepareForRemoval() { + super.prepareForRemoval() + + declineButton.isEnabled = false + audioButton.isEnabled = false + videoButton.isEnabled = false + } +} + +// MARK: Actions +extension CallIncomingController { + @objc func declineButtonPressed() { + + // CALL: end incoming call + delegate?.callIncomingControllerDecline(self) + } + + @objc func audioButtonPressed() { + delegate?.callIncomingControllerAnswerAudio(self) + } + + @objc func videoButtonPressed() { + delegate?.callIncomingControllerAnswerVideo(self) + } +} + +private extension CallIncomingController { + func createViews() { + let avatarManager = AvatarManager(theme: theme) + + avatarView = UIImageView() + avatarView.image = avatarManager.avatarFromString(callerName, diameter: Constants.AvatarSize, type: .Call) + view.addSubview(avatarView) + + buttonContainer = UIView() + buttonContainer.backgroundColor = .clear + view.addSubview(buttonContainer) + + declineButton = CallButton(theme: theme, type: .decline, buttonSize: .small) + declineButton.addTarget(self, action: #selector(CallIncomingController.declineButtonPressed), for: .touchUpInside) + buttonContainer.addSubview(declineButton) + + audioButton = CallButton(theme: theme, type: .answerAudio, buttonSize: .small) + audioButton.addTarget(self, action: #selector(CallIncomingController.audioButtonPressed), for: .touchUpInside) + buttonContainer.addSubview(audioButton) + + videoButton = CallButton(theme: theme, type: .answerVideo, buttonSize: .small) + videoButton.addTarget(self, action: #selector(CallIncomingController.videoButtonPressed), for: .touchUpInside) + buttonContainer.addSubview(videoButton) + } + + func installConstraints() { + avatarView.snp.makeConstraints { + $0.center.equalTo(view) + } + + buttonContainer.snp.makeConstraints { + $0.centerX.equalTo(view) + $0.top.greaterThanOrEqualTo(avatarView.snp.bottom).offset(Constants.ButtonContainerTopMinOffset) + $0.bottom.equalTo(view).offset(Constants.ButtonContainerBottomOffset).priority(250) + } + + declineButton.snp.makeConstraints { + $0.top.bottom.equalTo(buttonContainer) + $0.leading.equalTo(buttonContainer) + } + + audioButton.snp.makeConstraints { + $0.top.bottom.equalTo(buttonContainer) + $0.leading.equalTo(declineButton.snp.trailing).offset(Constants.ButtonHorizontalOffset) + } + + videoButton.snp.makeConstraints { + $0.top.bottom.equalTo(buttonContainer) + $0.leading.equalTo(audioButton.snp.trailing).offset(Constants.ButtonHorizontalOffset) + $0.trailing.equalTo(buttonContainer) + } + } +} diff --git a/Antidote/CallManagement/Call.swift b/Antidote/CallManagement/Call.swift new file mode 100644 index 0000000..ce5dea9 --- /dev/null +++ b/Antidote/CallManagement/Call.swift @@ -0,0 +1,93 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/** + * Copyright (c) 2017 Razeware LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Foundation + +enum CallState { + case connecting + case active + case held + case ended +} + +enum ConnectedState { + case pending + case complete +} + +/// represents a phone call +class Call { + + let uuid: UUID + let outgoing: Bool + let handle: String + + var state: CallState = .ended { + // didSet is a property observer + // https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html + didSet { + stateChanged?() + } + } + + var connectedState: ConnectedState = .pending { + didSet { + connectedStateChanged?() + } + } + + var stateChanged: (() -> Void)? + var connectedStateChanged: (() -> Void)? + + init(uuid: UUID, outgoing: Bool = false, handle: String) { + self.uuid = uuid + self.outgoing = outgoing + self.handle = handle + } + + func start(completion: ((_ success: Bool) -> Void)?) { + completion?(true) + + DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 3) { + self.state = .connecting + self.connectedState = .pending + + DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 1.5) { + self.state = .active + self.connectedState = .complete + } + } + } + + func answer() { + state = .active + } + + func end() { + state = .ended + } + +} diff --git a/Antidote/CallManagement/CallManager.swift b/Antidote/CallManagement/CallManager.swift new file mode 100644 index 0000000..d131373 --- /dev/null +++ b/Antidote/CallManagement/CallManager.swift @@ -0,0 +1,101 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/** + * Copyright (c) 2017 Razeware LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Foundation +import CallKit + +class CallManager { + + var callsChangedHandler: (() -> Void)? + + private(set) var calls = [Call]() + + private let callController = CXCallController() + + func callWithUUID(uuid: UUID) -> Call? { + guard let index = calls.index(where: { $0.uuid == uuid }) else { + return nil + } + return calls[index] + } + + func add(call: Call) { + calls.append(call) + call.stateChanged = { [weak self] in + guard let strongSelf = self else { return } + strongSelf.callsChangedHandler?() + } + callsChangedHandler?() + } + + func startCall(handle: String, videoEnabled: Bool) { + let handle = CXHandle(type: .phoneNumber, value: handle) + // generate a new UUID, use it to instantiate startCallAction + let startCallAction = CXStartCallAction(call: UUID(), handle: handle) + + startCallAction.isVideo = videoEnabled + let transaction = CXTransaction(action: startCallAction) + + requestTransaction(transaction) + } + + func end(call: Call) { + let endCallAction = CXEndCallAction(call: call.uuid) + // wrap action in a transaction + let transaction = CXTransaction(action: endCallAction) + // send transaction to system + requestTransaction(transaction) + } + + func setHeld(call: Call, onHold: Bool) { + let setHeldCallAction = CXSetHeldCallAction(call: call.uuid, onHold: onHold) + let transaction = CXTransaction() + transaction.addAction(setHeldCallAction) + + requestTransaction(transaction) + } + + private func requestTransaction(_ transaction: CXTransaction) { + callController.request(transaction) { error in + if let error = error { + print("Error requesting transaction: \(error)") + } else { + print("Requested transaction successfully") + } + } + } + + func remove(call: Call) { + guard let index = calls.index(where: { $0 === call }) else { return } + calls.remove(at: index) + callsChangedHandler?() + } + + func removeAllCalls() { + calls.removeAll() + callsChangedHandler?() + } +} diff --git a/Antidote/CallManagement/ProviderDelegate.swift b/Antidote/CallManagement/ProviderDelegate.swift new file mode 100644 index 0000000..aba54eb --- /dev/null +++ b/Antidote/CallManagement/ProviderDelegate.swift @@ -0,0 +1,238 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// +// ProviderDelegate.swift +// Hotline +// +// Created by Steve Baker on 10/27/17. +// Copyright © 2017 Razeware LLC. All rights reserved. +// + +import AVFoundation +import CallKit +import os + +class ProviderDelegate: NSObject { + + fileprivate let callManager: CallManager + fileprivate let provider: CXProvider + + init(callManager: CallManager) { + os_log("ProviderDelegate:init") + self.callManager = callManager + provider = CXProvider(configuration: type(of: self).providerConfiguration) + + super.init() + + provider.setDelegate(self, queue: nil) + } + + // static var belongs to the type + // subclasses can't override static + static var providerConfiguration: CXProviderConfiguration { + // initialize + let providerConfiguration = CXProviderConfiguration(localizedName: "Antidote") + + // set call capabilities + providerConfiguration.supportsVideo = true + providerConfiguration.maximumCallsPerCallGroup = 2 // Signal Messenger seems to think 2 is needed + providerConfiguration.supportedHandleTypes = [.generic] + + return providerConfiguration + } + + func endIncomingCalls() { + + os_log("ProviderDelegate:endIncomingCalls") + + for call in callManager.calls { + os_log("ProviderDelegate:endcall") + provider.reportCall(with: call.uuid, endedAt: Date(), reason: .remoteEnded) + call.end() + } + + callManager.removeAllCalls() + } + + func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) { + + os_log("ProviderDelegate:reportIncomingCall") + + // prepare update to send to system + let update = CXCallUpdate() + // add call metadata + update.remoteHandle = CXHandle(type: .generic, value: handle) + update.hasVideo = hasVideo + + // use provider to notify system + provider.reportNewIncomingCall(with: uuid, update: update) { error in + + // now we are inside reportNewIncomingCall's final argument, a completion block + if error == nil { + // no error, so add call + let call = Call(uuid: uuid, handle: handle) + self.callManager.add(call: call) + } + + // execute "completion", the final argument that was passed to outer method reportIncomingCall + // execute if it isn't nil + completion?(error as NSError?) + } + } + +} + +extension ProviderDelegate: CXProviderDelegate { + + func providerDidReset(_ provider: CXProvider) { + os_log("ProviderDelegate:providerDidReset") + + // stopAudio() + + for call in callManager.calls { + call.end() + } + + callManager.removeAllCalls() + } + + func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { + os_log("cc:ProviderDelegate:didActivate") + print("cc:ProviderDelegate:didActivate %@", audioSession) + + // HINT: audio session has to be started here! + + // also answer Tox Call ------------- + // -- HaXX0r -- + // -- HaXX0r -- + // -- HaXX0r -- + let coord = AppDelegate.shared.coordinator.activeCoordinator + let runcoord = coord as! RunningCoordinator + runcoord.activeSessionCoordinator?.callCoordinator.answerCall(enableVideo: false) + // -- HaXX0r -- + // -- HaXX0r -- + // -- HaXX0r -- + // also answer Tox Call ------------- + + // startAudio() + } + + func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { + + os_log("cc:ProviderDelegate:call-answer %@", action) + + guard let call = callManager.callWithUUID(uuid: action.callUUID) else { + action.fail() + return + } + + // HINT: audio session has to be configured here! + configureAudioSession() + os_log("cc:ProviderDelegate:call-answer:answer()") + call.answer() + // when processing an action, app should fulfill it or fail + os_log("cc:ProviderDelegate:call-answer:fulfill()") + action.fulfill() + } + + func configureAudioSession() + { + os_log("cc:ProviderDelegate:configureAudioSession:start") + + let session = AVAudioSession.sharedInstance() + do { + try session.setCategory(AVAudioSessionCategoryPlayAndRecord) + os_log("cc:ProviderDelegate:configureAudioSession:try_001") + try session.setMode(AVAudioSessionModeVoiceChat) + os_log("cc:ProviderDelegate:configureAudioSession:try_002") + // try session.setActive(true) + // os_log("cc:ProviderDelegate:configureAudioSession:try_003") + } catch (let error) { + os_log("cc:ProviderDelegate:configureAudioSession:EE_01") + print("cc:ProviderDelegate:configureAudioSession:Error while configuring audio session: \(error)") + } + + os_log("ProviderDelegate:configureAudioSession:end") + } + + func provider(_ provider: CXProvider, perform action: CXEndCallAction) { + + os_log("ProviderDelegate:call-end %@", action) + + guard let call = callManager.callWithUUID(uuid: action.callUUID) else { + action.fail() + return + } + + // also decline Tox Call ------------- + // -- HaXX0r -- + // -- HaXX0r -- + // -- HaXX0r -- + let coord = AppDelegate.shared.coordinator.activeCoordinator + let runcoord = coord as! RunningCoordinator + runcoord.activeSessionCoordinator?.callCoordinator.declineCall(callWasRemoved: false) + // -- HaXX0r -- + // -- HaXX0r -- + // -- HaXX0r -- + // also decline Tox Call ------------- + + // stopAudio() + // call.end changes the call's status, allows other classes to react to new state + call.end() + action.fulfill() + callManager.remove(call: call) + } + + func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) { + + os_log("ProviderDelegate:call-held %@", action) + + guard let call = callManager.callWithUUID(uuid: action.callUUID) else { + action.fail() + return + } + + call.state = action.isOnHold ? .held : .active + + if call.state == .held { + // stopAudio() + } else { + // startAudio() + } + + action.fulfill() + } + + func provider(_ provider: CXProvider, perform action: CXStartCallAction) { + let call = Call(uuid: action.callUUID, outgoing: true, handle: action.handle.value) + // configure. provider(_:didActivate) will start audio + configureAudioSession() + + os_log("cc:ProviderDelegate:call-start %s", action.handle.value) + + // set connectedStateChanged as a closure to monitor call lifecycle + call.connectedStateChanged = { [weak self, weak call] in + guard let strongSelf = self, let call = call else { return } + + if call.connectedState == .pending { + strongSelf.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: nil) + } else if call.connectedState == .complete { + strongSelf.provider.reportOutgoingCall(with: call.uuid, connectedAt: nil) + } + } + + call.start { [weak self, weak call] success in + guard let strongSelf = self, let call = call else { return } + + if success { + action.fulfill() + strongSelf.callManager.add(call: call) + } else { + action.fail() + } + } + } + +} diff --git a/Antidote/ChangeAutodownloadImagesController.swift b/Antidote/ChangeAutodownloadImagesController.swift new file mode 100644 index 0000000..8e52003 --- /dev/null +++ b/Antidote/ChangeAutodownloadImagesController.swift @@ -0,0 +1,78 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +protocol ChangeAutodownloadImagesControllerDelegate: class { + func changeAutodownloadImagesControllerDidChange(_ controller: ChangeAutodownloadImagesController) +} + +class ChangeAutodownloadImagesController: StaticTableController { + weak var delegate: ChangeAutodownloadImagesControllerDelegate? + + fileprivate let userDefaults: UserDefaultsManager + fileprivate let selectedStatus: UserDefaultsManager.AutodownloadImages + + fileprivate let neverModel = StaticTableDefaultCellModel() + fileprivate let wifiModel = StaticTableDefaultCellModel() + fileprivate let alwaysModel = StaticTableDefaultCellModel() + + init(theme: Theme) { + self.userDefaults = UserDefaultsManager() + self.selectedStatus = userDefaults.autodownloadImages + + super.init(theme: theme, style: .plain, model: [ + [ + neverModel, + wifiModel, + alwaysModel, + ], + ]) + + updateModels() + + title = String(localized: "settings_autodownload_images") + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private extension ChangeAutodownloadImagesController { + func updateModels() { + neverModel.value = String(localized: "settings_never") + neverModel.didSelectHandler = changeNever + + wifiModel.value = String(localized: "settings_using_wifi") + wifiModel.didSelectHandler = changeUsingWifi + + alwaysModel.value = String(localized: "settings_always") + alwaysModel.didSelectHandler = changeAlways + + switch selectedStatus { + case .Never: + neverModel.rightImageType = .checkmark + case .UsingWiFi: + wifiModel.rightImageType = .checkmark + case .Always: + alwaysModel.rightImageType = .checkmark + } + } + + func changeNever(_: StaticTableBaseCell) { + userDefaults.autodownloadImages = .Never + delegate?.changeAutodownloadImagesControllerDidChange(self) + } + + func changeUsingWifi(_: StaticTableBaseCell) { + userDefaults.autodownloadImages = .UsingWiFi + delegate?.changeAutodownloadImagesControllerDidChange(self) + } + + func changeAlways(_: StaticTableBaseCell) { + userDefaults.autodownloadImages = .Always + delegate?.changeAutodownloadImagesControllerDidChange(self) + } +} diff --git a/Antidote/ChangePasswordController.swift b/Antidote/ChangePasswordController.swift new file mode 100644 index 0000000..b379d50 --- /dev/null +++ b/Antidote/ChangePasswordController.swift @@ -0,0 +1,254 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let HorizontalOffset = 40.0 + static let ButtonVerticalOffset = 20.0 + static let FieldsOffset = 10.0 + + static let MaxFormWidth = 350.0 +} + +protocol ChangePasswordControllerDelegate: class { + func changePasswordControllerDidFinishPresenting(_ controller: ChangePasswordController) +} + +class ChangePasswordController: KeyboardNotificationController { + weak var delegate: ChangePasswordControllerDelegate? + + fileprivate let theme: Theme + + fileprivate weak var toxManager: OCTManager! + + fileprivate var scrollView: UIScrollView! + fileprivate var containerView: IncompressibleView! + + fileprivate var oldPasswordField: ExtendedTextField! + fileprivate var newPasswordField: ExtendedTextField! + fileprivate var repeatPasswordField: ExtendedTextField! + fileprivate var button: RoundedButton! + + init(theme: Theme, toxManager: OCTManager) { + self.theme = theme + self.toxManager = toxManager + + super.init() + + edgesForExtendedLayout = UIRectEdge() + addNavigationButtons() + + title = String(localized: "change_password") + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + createViews() + installConstraints() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let old = oldPasswordField { + _ = old.becomeFirstResponder() + } + else if let new = newPasswordField { + _ = new.becomeFirstResponder() + } + } + + override func keyboardWillShowAnimated(keyboardFrame frame: CGRect) { + scrollView.contentInset.bottom = frame.size.height + scrollView.scrollIndicatorInsets.bottom = frame.size.height + } + + override func keyboardWillHideAnimated(keyboardFrame frame: CGRect) { + scrollView.contentInset.bottom = 0.0 + scrollView.scrollIndicatorInsets.bottom = 0.0 + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + scrollView.contentSize.width = scrollView.frame.size.width + scrollView.contentSize.height = containerView.frame.maxY + } +} + +// MARK: Actions +extension ChangePasswordController { + @objc func cancelButtonPressed() { + delegate?.changePasswordControllerDidFinishPresenting(self) + } + + @objc func buttonPressed() { + guard validatePasswordFields() else { + return + } + + let oldPassword = oldPasswordField.text! + let newPassword = newPasswordField.text! + + let hud = JGProgressHUD(style: .dark) + hud?.show(in: view) + + DispatchQueue.global(qos: .default).async { [unowned self] in + let result = self.toxManager.changeEncryptPassword(newPassword, oldPassword: oldPassword) + + if result { + let keychainManager = KeychainManager() + if keychainManager.toxPasswordForActiveAccount != nil { + keychainManager.toxPasswordForActiveAccount = newPassword + } + } + + DispatchQueue.main.async { [unowned self] in + hud?.dismiss() + + if result { + self.delegate?.changePasswordControllerDidFinishPresenting(self) + } + else { + handleErrorWithType(.wrongOldPassword) + } + } + } + } +} + +extension ChangePasswordController: ExtendedTextFieldDelegate { + func loginExtendedTextFieldReturnKeyPressed(_ field: ExtendedTextField) { + if field === oldPasswordField { + _ = newPasswordField!.becomeFirstResponder() + } + else if field === newPasswordField { + _ = repeatPasswordField!.becomeFirstResponder() + } + else if field === repeatPasswordField { + buttonPressed() + } + } +} + +private extension ChangePasswordController { + func addNavigationButtons() { + navigationItem.leftBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .cancel, + target: self, + action: #selector(ChangePasswordController.cancelButtonPressed)) + } + + func createViews() { + scrollView = UIScrollView() + view.addSubview(scrollView) + + containerView = IncompressibleView() + containerView.backgroundColor = .clear + scrollView.addSubview(containerView) + + button = RoundedButton(theme: theme, type: .runningPositive) + button.setTitle(String(localized: "change_password_done"), for: UIControlState()) + button.addTarget(self, action: #selector(ChangePasswordController.buttonPressed), for: .touchUpInside) + containerView.addSubview(button) + + oldPasswordField = createPasswordFieldWithTitle(String(localized: "old_password")) + newPasswordField = createPasswordFieldWithTitle(String(localized: "new_password")) + repeatPasswordField = createPasswordFieldWithTitle(String(localized: "repeat_password")) + + oldPasswordField.returnKeyType = .next + newPasswordField.returnKeyType = .next + repeatPasswordField.returnKeyType = .done + } + + func createPasswordFieldWithTitle(_ title: String) -> ExtendedTextField { + let field = ExtendedTextField(theme: theme, type: .normal) + field.delegate = self + field.title = title + field.secureTextEntry = true + containerView.addSubview(field) + + return field + } + + func installConstraints() { + scrollView.snp.makeConstraints { + $0.edges.equalTo(view) + } + + containerView.customIntrinsicContentSize.width = CGFloat(Constants.MaxFormWidth) + containerView.snp.makeConstraints { + $0.top.equalTo(scrollView) + $0.centerX.equalTo(scrollView) + $0.width.lessThanOrEqualTo(Constants.MaxFormWidth) + $0.width.lessThanOrEqualTo(scrollView).offset(-2 * Constants.HorizontalOffset) + } + + var topConstraint = containerView.snp.top + + if installConstraintsForField(oldPasswordField, topConstraint: topConstraint) { + topConstraint = oldPasswordField!.snp.bottom + } + + if installConstraintsForField(newPasswordField, topConstraint: topConstraint) { + topConstraint = newPasswordField!.snp.bottom + } + + if installConstraintsForField(repeatPasswordField, topConstraint: topConstraint) { + topConstraint = repeatPasswordField!.snp.bottom + } + + button.snp.makeConstraints { + $0.top.equalTo(topConstraint).offset(Constants.ButtonVerticalOffset) + $0.leading.trailing.equalTo(containerView) + $0.bottom.equalTo(containerView) + } + } + + /** + Returns true if field exists, no otherwise. + */ + func installConstraintsForField(_ field: ExtendedTextField?, topConstraint: ConstraintItem) -> Bool { + guard let field = field else { + return false + } + + field.snp.makeConstraints { + $0.top.equalTo(topConstraint).offset(Constants.FieldsOffset) + $0.leading.trailing.equalTo(containerView) + } + + return true + } + + func validatePasswordFields() -> Bool { + guard let oldText = oldPasswordField.text, !oldText.isEmpty else { + handleErrorWithType(.passwordIsEmpty) + return false + } + guard let newText = newPasswordField.text, !newText.isEmpty else { + handleErrorWithType(.passwordIsEmpty) + return false + } + + guard let repeatText = repeatPasswordField.text, !repeatText.isEmpty else { + handleErrorWithType(.passwordIsEmpty) + return false + } + + guard newText == repeatText else { + handleErrorWithType(.passwordsDoNotMatch) + return false + } + + return true + } +} diff --git a/Antidote/ChangePinTimeoutController.swift b/Antidote/ChangePinTimeoutController.swift new file mode 100644 index 0000000..8628e6d --- /dev/null +++ b/Antidote/ChangePinTimeoutController.swift @@ -0,0 +1,111 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +protocol ChangePinTimeoutControllerDelegate: class { + func changePinTimeoutControllerDone(_ controller: ChangePinTimeoutController) +} + +class ChangePinTimeoutController: StaticTableController { + weak var delegate: ChangePinTimeoutControllerDelegate? + + fileprivate weak var submanagerObjects: OCTSubmanagerObjects! + + fileprivate let immediatelyModel = StaticTableDefaultCellModel() + fileprivate let seconds30Model = StaticTableDefaultCellModel() + fileprivate let minute1Model = StaticTableDefaultCellModel() + fileprivate let minute2Model = StaticTableDefaultCellModel() + fileprivate let minute5Model = StaticTableDefaultCellModel() + + init(theme: Theme, submanagerObjects: OCTSubmanagerObjects) { + self.submanagerObjects = submanagerObjects + + super.init(theme: theme, style: .plain, model: [ + [ + immediatelyModel, + seconds30Model, + minute1Model, + minute2Model, + minute5Model, + ], + ]) + + updateModels() + + title = String(localized: "pin_lock_timeout") + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private extension ChangePinTimeoutController { + func updateModels() { + let settings = submanagerObjects.getProfileSettings() + + immediatelyModel.value = String(localized: "pin_lock_immediately") + immediatelyModel.didSelectHandler = immediatelyHandler + immediatelyModel.rightImageType = .none + + seconds30Model.value = String(localized: "pin_lock_30_seconds") + seconds30Model.didSelectHandler = seconds30Handler + seconds30Model.rightImageType = .none + + minute1Model.value = String(localized: "pin_lock_1_minute") + minute1Model.didSelectHandler = minute1Handler + minute1Model.rightImageType = .none + + minute2Model.value = String(localized: "pin_lock_2_minutes") + minute2Model.didSelectHandler = minute2Handler + minute2Model.rightImageType = .none + + minute5Model.value = String(localized: "pin_lock_5_minutes") + minute5Model.didSelectHandler = minute5Handler + minute5Model.rightImageType = .none + + + switch settings.lockTimeout { + case .Immediately: + immediatelyModel.rightImageType = .checkmark + case .Seconds30: + seconds30Model.rightImageType = .checkmark + case .Minute1: + minute1Model.rightImageType = .checkmark + case .Minute2: + minute2Model.rightImageType = .checkmark + case .Minute5: + minute5Model.rightImageType = .checkmark + } + } + + func immediatelyHandler(_: StaticTableBaseCell) { + selectedTimeout(.Immediately) + } + + func seconds30Handler(_: StaticTableBaseCell) { + selectedTimeout(.Seconds30) + } + + func minute1Handler(_: StaticTableBaseCell) { + selectedTimeout(.Minute1) + } + + func minute2Handler(_: StaticTableBaseCell) { + selectedTimeout(.Minute2) + } + + func minute5Handler(_: StaticTableBaseCell) { + selectedTimeout(.Minute5) + } + + func selectedTimeout(_ timeout: ProfileSettings.LockTimeout) { + let settings = submanagerObjects.getProfileSettings() + settings.lockTimeout = timeout + submanagerObjects.saveProfileSettings(settings) + + delegate?.changePinTimeoutControllerDone(self) + } +} diff --git a/Antidote/ChangeUserStatusController.swift b/Antidote/ChangeUserStatusController.swift new file mode 100644 index 0000000..3ffc55f --- /dev/null +++ b/Antidote/ChangeUserStatusController.swift @@ -0,0 +1,81 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +protocol ChangeUserStatusControllerDelegate: class { + func changeUserStatusController(_ controller: ChangeUserStatusController, selectedStatus: OCTToxUserStatus) +} + +class ChangeUserStatusController: StaticTableController { + weak var delegate: ChangeUserStatusControllerDelegate? + + fileprivate let selectedStatus: OCTToxUserStatus + + fileprivate let onlineModel = StaticTableDefaultCellModel() + fileprivate let awayModel = StaticTableDefaultCellModel() + fileprivate let busyModel = StaticTableDefaultCellModel() + + init(theme: Theme, selectedStatus: OCTToxUserStatus) { + self.selectedStatus = selectedStatus + + super.init(theme: theme, style: .plain, model: [ + [ + onlineModel, + awayModel, + busyModel, + ], + ]) + + updateModels() + + title = String(localized: "status_title") + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private extension ChangeUserStatusController { + func updateModels() { + // Hardcoding any connected status to show only online/away/busy statuses here. + let online = UserStatus(connectionStatus: OCTToxConnectionStatus.TCP, userStatus: OCTToxUserStatus.none) + let away = UserStatus(connectionStatus: OCTToxConnectionStatus.TCP, userStatus: OCTToxUserStatus.away) + let busy = UserStatus(connectionStatus: OCTToxConnectionStatus.TCP, userStatus: OCTToxUserStatus.busy) + + onlineModel.userStatus = online + onlineModel.value = online.toString() + onlineModel.didSelectHandler = changeOnlineStatus + + awayModel.userStatus = away + awayModel.value = away.toString() + awayModel.didSelectHandler = changeAwayStatus + + busyModel.userStatus = busy + busyModel.value = busy.toString() + busyModel.didSelectHandler = changeBusyStatus + + switch selectedStatus { + case .none: + onlineModel.rightImageType = .checkmark + case .away: + awayModel.rightImageType = .checkmark + case .busy: + busyModel.rightImageType = .checkmark + } + } + + func changeOnlineStatus(_: StaticTableBaseCell) { + delegate?.changeUserStatusController(self, selectedStatus: .none) + } + + func changeAwayStatus(_: StaticTableBaseCell) { + delegate?.changeUserStatusController(self, selectedStatus: .away) + } + + func changeBusyStatus(_: StaticTableBaseCell) { + delegate?.changeUserStatusController(self, selectedStatus: .busy) + } +} diff --git a/Antidote/ChatBaseTextCell.swift b/Antidote/ChatBaseTextCell.swift new file mode 100644 index 0000000..5735e4c --- /dev/null +++ b/Antidote/ChatBaseTextCell.swift @@ -0,0 +1,100 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +class ChatBaseTextCell: ChatMovableDateCell { + struct Constants { + static let BubbleVerticalOffset = 1.0 + static let BubbleHorizontalOffset = 10.0 + } + + var bubbleNormalBackground: UIColor? + var bubbleView: BubbleView! + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let textModel = model as? ChatBaseTextCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + canBeCopied = true + bubbleView.text = textModel.message + bubbleView.textColor = theme.colorForType(.NormalText) + } + + override func createViews() { + super.createViews() + + bubbleView = BubbleView() + contentView.addSubview(bubbleView) + } + + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + + bubbleView.isUserInteractionEnabled = !editing + } + + override func setHighlighted(_ highlighted: Bool, animated: Bool) { + super.setHighlighted(highlighted, animated: animated) + bubbleView.backgroundColor = bubbleNormalBackground + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + if isEditing { + bubbleView.backgroundColor = bubbleNormalBackground + return + } + + if selected { + bubbleView.backgroundColor = bubbleNormalBackground?.darkerColor() + } + else { + bubbleView.backgroundColor = bubbleNormalBackground + } + } +} + +// Accessibility +extension ChatBaseTextCell { + override var accessibilityValue: String? { + get { + var value = bubbleView.text! + if let sValue = super.accessibilityValue { + value += ", " + sValue + } + + return value + } + set {} + } +} + +// ChatEditable +extension ChatBaseTextCell { + override func shouldShowMenu() -> Bool { + return true + } + + override func menuTargetRect() -> CGRect { + return bubbleView.frame + } + + override func willShowMenu() { + super.willShowMenu() + + bubbleView.selectable = false + } + + override func willHideMenu() { + super.willHideMenu() + + bubbleView.selectable = true + } +} diff --git a/Antidote/ChatBaseTextCellModel.swift b/Antidote/ChatBaseTextCellModel.swift new file mode 100644 index 0000000..9dbe82e --- /dev/null +++ b/Antidote/ChatBaseTextCellModel.swift @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatBaseTextCellModel: ChatMovableDateCellModel { + var message: String = "" +} diff --git a/Antidote/ChatBottomStatusViewManager.swift b/Antidote/ChatBottomStatusViewManager.swift new file mode 100644 index 0000000..e2ff37e --- /dev/null +++ b/Antidote/ChatBottomStatusViewManager.swift @@ -0,0 +1,88 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// +// ChatBottomStatusViewManager.swift +// Antidote +// +// Created by Dmytro Vorobiov on 26/04/2017. +// Copyright © 2017 dvor. All rights reserved. +// + +import Foundation + +class ChatBottomStatusViewManager { + fileprivate weak var submanagerObjects: OCTSubmanagerObjects! + + fileprivate let friend: OCTFriend? + fileprivate let undeliveredMessages: Results + + fileprivate var friendToken: RLMNotificationToken? + fileprivate var undeliveredMessagesToken: RLMNotificationToken? + + init(friend: OCTFriend?, messages: Results, submanagerObjects: OCTSubmanagerObjects) { + self.submanagerObjects = submanagerObjects + self.friend = friend + self.undeliveredMessages = messages.undeliveredMessages() + + addFriendNotification() + addMessagesNotification() + } + + deinit { + friendToken?.invalidate() + undeliveredMessagesToken?.invalidate() + } +} + +private extension ChatBottomStatusViewManager { + func addFriendNotification() { + guard let friend = self.friend else { + return + } + + friendToken = submanagerObjects.notificationBlock(for: friend) { [unowned self] change in + switch change { + case .initial: + break + case .update: + self.updateTableHeaderView() + case .error(let error): + break + } + } + } + + func addMessagesNotification() { + // self.undeliveredMessagesToken = undeliveredMessages.addNotificationBlock { [unowned self] change in + // guard let tableView = self.tableView else { + // return + // } + // switch change { + // case .initial: + // break + // case .update(_, let deletions, let insertions, let modifications): + // tableView.beginUpdates() + // self.updateTableViewWithDeletions(deletions) + // self.updateTableViewWithInsertions(insertions) + // self.updateTableViewWithModifications(modifications) + + // self.visibleMessages = self.visibleMessages + insertions.count - deletions.count + // tableView.endUpdates() + + // self.updateTableHeaderView() + + // if insertions.contains(0) { + // self.handleNewMessage() + // } + // case .error(let error): + // fatalError("\(error)") + // } + // } + } + + func updateTableHeaderView() { + + } +} diff --git a/Antidote/ChatEditable.swift b/Antidote/ChatEditable.swift new file mode 100644 index 0000000..196b4b0 --- /dev/null +++ b/Antidote/ChatEditable.swift @@ -0,0 +1,29 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +/** + Chat cell can confirm to this protocol to support editing with UIMenuController. + */ +protocol ChatEditable { + /** + Return true to show menu for given cell, false otherwise. + */ + func shouldShowMenu() -> Bool + + /** + Target rect in view to show menu from. + + - Returns: rect to show menu from. + */ + func menuTargetRect() -> CGRect + + /** + Methods fired when menu is going to be shown/hide. + If you override this methods, you must call super at some point in your implementation. + */ + func willShowMenu() + func willHideMenu() +} diff --git a/Antidote/ChatFauxOfflineHeaderView.swift b/Antidote/ChatFauxOfflineHeaderView.swift new file mode 100644 index 0000000..7ddc86f --- /dev/null +++ b/Antidote/ChatFauxOfflineHeaderView.swift @@ -0,0 +1,49 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import SnapKit + +fileprivate struct Constants { + static let verticalOffset = 7.0 + static let maxLabelWidth: CGFloat = 280.0 +} + +class ChatFauxOfflineHeaderView: UIView { + fileprivate var label: UILabel! + + init(theme: Theme) { + super.init(frame: CGRect.zero) + + backgroundColor = theme.colorForType(.NormalBackground) + createViews(theme: theme) + installConstraints() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private extension ChatFauxOfflineHeaderView { + func createViews(theme: Theme) { + label = UILabel() + label.text = String(localized: "chat_pending_faux_offline_messages") + label.font = UIFont.antidoteFontWithSize(14.0, weight: .medium) + label.textAlignment = .center + label.textColor = theme.colorForType(.ChatInformationText) + label.numberOfLines = 0 + label.preferredMaxLayoutWidth = Constants.maxLabelWidth + addSubview(label) + } + + func installConstraints() { + label.snp.makeConstraints { + $0.top.equalTo(self).offset(Constants.verticalOffset) + $0.bottom.equalTo(self).offset(-Constants.verticalOffset) + $0.centerX.equalTo(self) + $0.width.equalTo(Constants.maxLabelWidth) + } + } +} diff --git a/Antidote/ChatGenericFileCell.swift b/Antidote/ChatGenericFileCell.swift new file mode 100644 index 0000000..39c95e5 --- /dev/null +++ b/Antidote/ChatGenericFileCell.swift @@ -0,0 +1,178 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +class ChatGenericFileCell: ChatMovableDateCell { + var loadingView: LoadingImageView! + var cancelButton: UIButton! + var retryButton: UIButton! + + var progressObject: ChatProgressProtocol? { + didSet { + progressObject?.updateProgress = { [weak self] (progress: Float) -> Void in + self?.updateProgress(CGFloat(progress)) + } + + progressObject?.updateEta = { [weak self] (eta: CFTimeInterval, bytesPerSecond: OCTToxFileSize) -> Void in + self?.updateEta(String(timeInterval: eta)) + self?.updateBytesPerSecond(bytesPerSecond) + } + } + } + + var state: ChatGenericFileCellModel.State = .waitingConfirmation + + var startLoadingHandle: (() -> Void)? + var cancelHandle: (() -> Void)? + var retryHandle: (() -> Void)? + var pauseOrResumeHandle: (() -> Void)? + var openHandle: (() -> Void)? + + /** + This method should be called after setupWithTheme:model: + */ + func setButtonImage(_ image: UIImage) { + let square: UIImage + + canBeCopied = true + + if image.size.width == image.size.height { + square = image + } + else { + let side = min(image.size.width, image.size.height) + let x = (image.size.width - side) / 2 + let y = (image.size.height - side) / 2 + let rect = CGRect(x: x, y: y, width: side, height: side) + + square = image.cropWithRect(rect) + } + + loadingView.imageButton.setBackgroundImage(square, for: UIControlState()) + + if state == .waitingConfirmation || state == .done { + loadingView.centerImageView.image = nil + } + } + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let fileModel = model as? ChatGenericFileCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + state = fileModel.state + startLoadingHandle = fileModel.startLoadingHandle + cancelHandle = fileModel.cancelHandle + retryHandle = fileModel.retryHandle + pauseOrResumeHandle = fileModel.pauseOrResumeHandle + openHandle = fileModel.openHandle + + canBeCopied = false + + switch state { + case .loading: + loadingView.centerImageView.image = UIImage.templateNamed("chat-file-pause-big") + case .paused: + loadingView.centerImageView.image = UIImage.templateNamed("chat-file-play-big") + case .waitingConfirmation: + fallthrough + case .cancelled: + fallthrough + case .done: + var fileExtension: String? = nil + + if let fileName = fileModel.fileName { + fileExtension = (fileName as NSString).pathExtension + } + + loadingView.setImageWithUti(fileModel.fileUTI, fileExtension: fileExtension) + } + + updateViewsWithState(fileModel.state, fileModel: fileModel) + + loadingView.imageButton.setImage(nil, for: UIControlState()) + + let backgroundColor = theme.colorForType(.FileImageBackgroundActive) + let backgroundImage = UIImage.imageWithColor(backgroundColor, size: CGSize(width: 1.0, height: 1.0)) + loadingView.imageButton.setBackgroundImage(backgroundImage, for: UIControlState()) + + loadingView.progressView.backgroundLineColor = theme.colorForType(.FileImageAcceptButtonTint).withAlphaComponent(0.3) + loadingView.progressView.lineColor = theme.colorForType(.FileImageAcceptButtonTint) + + loadingView.centerImageView.tintColor = theme.colorForType(.FileImageAcceptButtonTint) + + loadingView.topLabel.textColor = theme.colorForType(.FileImageCancelledText) + loadingView.bottomLabel.textColor = theme.colorForType(.FileImageCancelledText) + + cancelButton.tintColor = theme.colorForType(.FileImageCancelButtonTint) + retryButton.tintColor = theme.colorForType(.FileImageCancelButtonTint) + } + + override func createViews() { + super.createViews() + + loadingView = LoadingImageView() + loadingView.pressedHandle = loadingViewPressed + + let cancelImage = UIImage.templateNamed("chat-file-cancel") + + cancelButton = UIButton() + cancelButton.setImage(cancelImage, for: UIControlState()) + cancelButton.addTarget(self, action: #selector(ChatGenericFileCell.cancelButtonPressed), for: .touchUpInside) + + let retryImage = UIImage.templateNamed("chat-file-retry") + + retryButton = UIButton() + retryButton.setImage(retryImage, for: UIControlState()) + retryButton.addTarget(self, action: #selector(ChatGenericFileCell.retryButtonPressed), for: .touchUpInside) + } + + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + + loadingView.isUserInteractionEnabled = !editing + cancelButton.isUserInteractionEnabled = !editing + retryButton.isUserInteractionEnabled = !editing + } + + func updateProgress(_ progress: CGFloat) { + loadingView.progressView.progress = progress + } + + func updateEta(_ eta: String) { + loadingView.bottomLabel.text = eta + } + + func updateBytesPerSecond(_ bytesPerSecond: OCTToxFileSize) {} + + @objc func cancelButtonPressed() { + cancelHandle?() + } + + @objc func retryButtonPressed() { + retryHandle?() + } + + /// Override in subclass + func updateViewsWithState(_ state: ChatGenericFileCellModel.State, fileModel: ChatGenericFileCellModel) {} + + /// Override in subclass + func loadingViewPressed() {} +} + +// ChatEditable +extension ChatGenericFileCell { + override func shouldShowMenu() -> Bool { + return true + } + + override func menuTargetRect() -> CGRect { + return loadingView.frame + } +} diff --git a/Antidote/ChatGenericFileCellModel.swift b/Antidote/ChatGenericFileCellModel.swift new file mode 100644 index 0000000..1c9a573 --- /dev/null +++ b/Antidote/ChatGenericFileCellModel.swift @@ -0,0 +1,26 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatGenericFileCellModel: ChatMovableDateCellModel { + enum State { + case waitingConfirmation + case loading + case paused + case cancelled + case done + } + + var state: State = .waitingConfirmation + var fileName: String? + var fileSize: String? + var fileUTI: String? + + var startLoadingHandle: (() -> Void)? + var cancelHandle: (() -> Void)? + var retryHandle: (() -> Void)? + var pauseOrResumeHandle: (() -> Void)? + var openHandle: (() -> Void)? +} diff --git a/Antidote/ChatIncomingCallCell.swift b/Antidote/ChatIncomingCallCell.swift new file mode 100644 index 0000000..23ace11 --- /dev/null +++ b/Antidote/ChatIncomingCallCell.swift @@ -0,0 +1,86 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let LeftOffset = 20.0 + static let ImageViewToLabelOffset = 5.0 + static let ImageViewYOffset = -1.0 + static let VerticalOffset = 8.0 +} + +class ChatIncomingCallCell: ChatMovableDateCell { + fileprivate var callImageView: UIImageView! + fileprivate var label: UILabel! + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let incomingModel = model as? ChatIncomingCallCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + label.textColor = theme.colorForType(.ChatListCellMessage) + callImageView.tintColor = theme.colorForType(.LinkText) + + if incomingModel.answered { + label.text = String(localized: "chat_call_message") + String(timeInterval: incomingModel.callDuration) + } + else { + label.text = String(localized: "chat_missed_call_message") + } + } + + override func createViews() { + super.createViews() + + let image = UIImage.templateNamed("start-call-small") + + callImageView = UIImageView(image: image) + contentView.addSubview(callImageView) + + label = UILabel() + label.font = UIFont.antidoteFontWithSize(16.0, weight: .light) + contentView.addSubview(label) + } + + override func installConstraints() { + super.installConstraints() + + callImageView.snp.makeConstraints { + $0.centerY.equalTo(label).offset(Constants.ImageViewYOffset) + $0.leading.equalTo(contentView).offset(Constants.LeftOffset) + } + + label.snp.makeConstraints { + $0.top.equalTo(contentView).offset(Constants.VerticalOffset) + $0.bottom.equalTo(contentView).offset(-Constants.VerticalOffset) + $0.leading.equalTo(callImageView.snp.trailing).offset(Constants.ImageViewToLabelOffset) + } + } +} + +// Accessibility +extension ChatIncomingCallCell { + override var accessibilityLabel: String? { + get { + return label.text + } + set {} + } +} + +// ChatEditable +extension ChatIncomingCallCell { + override func shouldShowMenu() -> Bool { + return true + } + + override func menuTargetRect() -> CGRect { + return label.frame + } +} diff --git a/Antidote/ChatIncomingCallCellModel.swift b/Antidote/ChatIncomingCallCellModel.swift new file mode 100644 index 0000000..136c7ee --- /dev/null +++ b/Antidote/ChatIncomingCallCellModel.swift @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatIncomingCallCellModel: ChatMovableDateCellModel { + var callDuration: TimeInterval = 0 + var answered: Bool = true +} diff --git a/Antidote/ChatIncomingFileCell.swift b/Antidote/ChatIncomingFileCell.swift new file mode 100644 index 0000000..acb8947 --- /dev/null +++ b/Antidote/ChatIncomingFileCell.swift @@ -0,0 +1,88 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let BigOffset = 20.0 + static let SmallOffset = 8.0 + static let ImageButtonSize = 180.0 + static let CloseButtonSize = 25.0 +} + +class ChatIncomingFileCell: ChatGenericFileCell { + override func setButtonImage(_ image: UIImage) { + super.setButtonImage(image) + loadingView.bottomLabel.isHidden = true + } + + override func createViews() { + super.createViews() + + contentView.addSubview(loadingView) + contentView.addSubview(cancelButton) + } + + override func installConstraints() { + super.installConstraints() + + loadingView.snp.makeConstraints { + $0.leading.equalTo(contentView).offset(Constants.BigOffset) + $0.top.equalTo(contentView).offset(Constants.SmallOffset) + $0.bottom.equalTo(contentView).offset(-Constants.SmallOffset) + $0.size.equalTo(Constants.ImageButtonSize) + } + + cancelButton.snp.makeConstraints { + $0.leading.equalTo(loadingView.snp.trailing).offset(Constants.SmallOffset) + $0.top.equalTo(loadingView) + $0.size.equalTo(Constants.CloseButtonSize) + } + } + + override func updateViewsWithState(_ state: ChatGenericFileCellModel.State, fileModel: ChatGenericFileCellModel) { + loadingView.imageButton.isUserInteractionEnabled = true + loadingView.progressView.isHidden = true + loadingView.topLabel.isHidden = false + loadingView.topLabel.text = fileModel.fileName + loadingView.bottomLabel.text = fileModel.fileSize + loadingView.bottomLabel.isHidden = false + + cancelButton.isHidden = false + + switch state { + case .waitingConfirmation: + loadingView.centerImageView.image = UIImage.templateNamed("chat-file-download-big") + case .loading: + loadingView.progressView.isHidden = false + case .paused: + break + case .cancelled: + loadingView.setCancelledImage() + loadingView.imageButton.isUserInteractionEnabled = false + cancelButton.isHidden = true + loadingView.bottomLabel.text = String(localized: "chat_file_cancelled") + case .done: + cancelButton.isHidden = true + loadingView.topLabel.isHidden = true + loadingView.bottomLabel.text = fileModel.fileName + } + } + + override func loadingViewPressed() { + switch state { + case .waitingConfirmation: + startLoadingHandle?() + case .loading: + pauseOrResumeHandle?() + case .paused: + pauseOrResumeHandle?() + case .cancelled: + break + case .done: + openHandle?() + } + } +} diff --git a/Antidote/ChatIncomingFileCellModel.swift b/Antidote/ChatIncomingFileCellModel.swift new file mode 100644 index 0000000..6738127 --- /dev/null +++ b/Antidote/ChatIncomingFileCellModel.swift @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatIncomingFileCellModel: ChatGenericFileCellModel { +} diff --git a/Antidote/ChatIncomingTextCell.swift b/Antidote/ChatIncomingTextCell.swift new file mode 100644 index 0000000..0d35762 --- /dev/null +++ b/Antidote/ChatIncomingTextCell.swift @@ -0,0 +1,37 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +class ChatIncomingTextCell: ChatBaseTextCell { + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + bubbleNormalBackground = theme.colorForType(.ChatIncomingBubble) + bubbleView.backgroundColor = bubbleNormalBackground + bubbleView.tintColor = theme.colorForType(.LinkText) + bubbleView.font = UIFont.preferredFont(forTextStyle: .body) + } + + override func installConstraints() { + super.installConstraints() + + bubbleView.snp.makeConstraints { + $0.top.equalTo(contentView).offset(ChatBaseTextCell.Constants.BubbleVerticalOffset) + $0.bottom.equalTo(contentView).offset(-ChatBaseTextCell.Constants.BubbleVerticalOffset) + $0.leading.equalTo(contentView).offset(ChatBaseTextCell.Constants.BubbleHorizontalOffset) + } + } +} + +// Accessibility +extension ChatIncomingTextCell { + override var accessibilityLabel: String? { + get { + return String(localized: "accessibility_incoming_message_label") + } + set {} + } +} diff --git a/Antidote/ChatInputView.swift b/Antidote/ChatInputView.swift new file mode 100644 index 0000000..5c60624 --- /dev/null +++ b/Antidote/ChatInputView.swift @@ -0,0 +1,217 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let TopBorderHeight = 0.5 + static let Offset: CGFloat = 5.0 + static let CameraHorizontalOffset: CGFloat = 10.0 + static let CameraBottomOffset: CGFloat = -10.0 + static let TextViewMinHeight: CGFloat = 35.0 + static let MIN_MYHEIGHT: CGFloat = 45 + static let MAX_MYHEIGHT: CGFloat = 90 + static let MARGIN_MYHEIGHT: CGFloat = 5 + static let MAX_TEXT_INPUT_CHARS = 1000 +} + +protocol ChatInputViewDelegate: class { + func chatInputViewCameraButtonPressed(_ view: ChatInputView, cameraView: UIView) + func chatInputViewSendButtonPressed(_ view: ChatInputView) + func chatInputViewTextDidChange(_ view: ChatInputView) +} + +class ChatInputView: UIView { + weak var delegate: ChatInputViewDelegate? + + var text: String { + get { + return textView.text + } + set { + textView.text = newValue + updateViews() + } + } + + var maxHeight: CGFloat { + didSet { + updateViews() + } + } + + var cameraButtonEnabled: Bool = true{ + didSet { + updateViews() + } + } + + fileprivate var topBorder: UIView! + fileprivate var cameraButton: UIButton! + fileprivate var textView: UITextView! + fileprivate var sendButton: UIButton! + fileprivate var myHeight: Constraint! + fileprivate var didconstraint = 0 + + init(theme: Theme) { + self.maxHeight = 0.0 + + super.init(frame: CGRect.zero) + + backgroundColor = theme.colorForType(.ChatInputBackground) + + createViews(theme) + installConstraints() + updateViews() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func becomeFirstResponder() -> Bool { + return textView.becomeFirstResponder() + } + + override func resignFirstResponder() -> Bool { + return textView.resignFirstResponder() + } +} + +// MARK: Actions +extension ChatInputView { + @objc func cameraButtonPressed() { + delegate?.chatInputViewCameraButtonPressed(self, cameraView: cameraButton) + } + + @objc func sendButtonPressed() { + delegate?.chatInputViewSendButtonPressed(self) + updateTextviewHeight(textView) + } +} + +extension ChatInputView: UITextViewDelegate { + func textViewDidChange(_ textView: UITextView) { + updateViews() + updateTextviewHeight(textView) + delegate?.chatInputViewTextDidChange(self) + } + + override func didMoveToWindow() { + updateTextviewHeight(textView) + } + + func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + // get the current text, or use an empty string if that failed + let currentText = textView.text ?? "" + + // attempt to read the range they are trying to change, or exit if we can't + guard let stringRange = Range(range, in: currentText) else { return false } + + // add their new text to the existing text + let updatedText = currentText.replacingCharacters(in: stringRange, with: text) + + // make sure the result is under MAX_TEXT_INPUT_CHARS characters + return updatedText.count <= Constants.MAX_TEXT_INPUT_CHARS + } +} + +private extension ChatInputView { + + func createViews(_ theme: Theme) { + topBorder = UIView() + topBorder.backgroundColor = theme.colorForType(.SeparatorsAndBorders) + addSubview(topBorder) + + let cameraImage = UIImage.templateNamed("chat-camera") + + cameraButton = UIButton() + cameraButton.setImage(cameraImage, for: UIControlState()) + cameraButton.tintColor = theme.colorForType(.LinkText) + cameraButton.addTarget(self, action: #selector(ChatInputView.cameraButtonPressed), for: .touchUpInside) + cameraButton.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) + addSubview(cameraButton) + + textView = UITextView() + textView.delegate = self + textView.font = UIFont.systemFont(ofSize: 16.0) + textView.backgroundColor = theme.colorForType(.NormalBackground) + textView.layer.cornerRadius = 5.0 + textView.layer.borderWidth = 0.5 + textView.layer.borderColor = theme.colorForType(.SeparatorsAndBorders).cgColor + textView.layer.masksToBounds = true + textView.setContentHuggingPriority(UILayoutPriority(rawValue: 0.0), for: .horizontal) + textView.autocapitalizationType = .none + + addSubview(textView) + + sendButton = UIButton(type: .system) + sendButton.setTitle(String(localized: "chat_send_button"), for: UIControlState()) + sendButton.titleLabel?.font = UIFont.antidoteFontWithSize(16.0, weight: .bold) + sendButton.addTarget(self, action: #selector(ChatInputView.sendButtonPressed), for: .touchUpInside) + sendButton.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) + addSubview(sendButton) + } + + func installConstraints() { + topBorder.snp.makeConstraints { + $0.top.leading.trailing.equalTo(self) + $0.height.equalTo(Constants.TopBorderHeight) + } + + cameraButton.snp.makeConstraints { + $0.leading.equalTo(self).offset(Constants.CameraHorizontalOffset) + $0.bottom.equalTo(self).offset(Constants.CameraBottomOffset) + } + + textView.snp.makeConstraints { + $0.leading.equalTo(cameraButton.snp.trailing).offset(Constants.CameraHorizontalOffset) + $0.top.equalTo(self).offset(Constants.Offset) + $0.bottom.equalTo(self).offset(-Constants.Offset) + $0.height.greaterThanOrEqualTo(Constants.TextViewMinHeight) + } + + sendButton.snp.makeConstraints { + $0.leading.equalTo(textView.snp.trailing).offset(Constants.Offset) + $0.trailing.equalTo(self).offset(-Constants.Offset) + $0.bottom.equalTo(self).offset(-Constants.Offset) + } + } + + func updateTextviewHeight(_ t : UITextView) + { + if (self.didconstraint == 1) + { + self.myHeight.uninstall() + self.didconstraint = 0 + } + + let text_needs_size = t.sizeThatFits( + CGSize(width: t.frame.size.width, + height: CGFloat.greatestFiniteMagnitude)) + var new_height = text_needs_size.height + Constants.MARGIN_MYHEIGHT + if (text_needs_size.height > Constants.MAX_MYHEIGHT) + { + new_height = Constants.MAX_MYHEIGHT + } + else if (text_needs_size.height < Constants.MIN_MYHEIGHT) { + new_height = Constants.MIN_MYHEIGHT + } + + if (self.didconstraint == 0) { + self.didconstraint = 1 + self.snp.makeConstraints { + self.myHeight = $0.height.equalTo(new_height).constraint + } + } + } + + func updateViews() { + textView.isScrollEnabled = true + textView.autocapitalizationType = .none + cameraButton.isEnabled = cameraButtonEnabled + sendButton.isEnabled = !textView.text.isEmpty + } +} diff --git a/Antidote/ChatInputViewManager.swift b/Antidote/ChatInputViewManager.swift new file mode 100644 index 0000000..c83a2e8 --- /dev/null +++ b/Antidote/ChatInputViewManager.swift @@ -0,0 +1,195 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import MobileCoreServices +import Photos +import os + +fileprivate struct Constants { + static let inactivityTimeout = 4.0 +} + +/** + Manager responsible for sending messages and files, updating typing notification, + saving entered text in database. + */ +class ChatInputViewManager: NSObject { + fileprivate var chat: OCTChat! + fileprivate weak var inputView: ChatInputView? + + fileprivate weak var submanagerChats: OCTSubmanagerChats! + fileprivate weak var submanagerFiles: OCTSubmanagerFiles! + fileprivate weak var submanagerObjects: OCTSubmanagerObjects! + + fileprivate weak var presentingViewController: UIViewController! + + fileprivate var inactivityTimer: Timer? + + init(inputView: ChatInputView, + chat: OCTChat, + submanagerChats: OCTSubmanagerChats, + submanagerFiles: OCTSubmanagerFiles, + submanagerObjects: OCTSubmanagerObjects, + presentingViewController: UIViewController) { + + self.chat = chat + self.inputView = inputView + self.submanagerChats = submanagerChats + self.submanagerFiles = submanagerFiles + self.submanagerObjects = submanagerObjects + self.presentingViewController = presentingViewController + + super.init() + + inputView.delegate = self + inputView.text = chat.enteredText ?? "" + } + + deinit { + endUserInteraction() + } +} + +extension ChatInputViewManager: ChatInputViewDelegate { + func chatInputViewCameraButtonPressed(_ view: ChatInputView, cameraView: UIView) { + let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + alert.popoverPresentationController?.sourceView = cameraView + alert.popoverPresentationController?.sourceRect = CGRect(x: cameraView.frame.size.width / 2, y: cameraView.frame.size.height / 2, width: 1.0, height: 1.0) + + func addAction(title: String, sourceType: UIImagePickerControllerSourceType) { + if UIImagePickerController.isSourceTypeAvailable(sourceType) { + alert.addAction(UIAlertAction(title: title, style: .default) { [unowned self] _ -> Void in + let controller = UIImagePickerController() + controller.delegate = self + controller.sourceType = sourceType + controller.mediaTypes = [kUTTypeImage as String, kUTTypeMovie as String] + controller.videoQuality = .typeHigh + self.presentingViewController.present(controller, animated: true, completion: nil) + }) + } + } + + addAction(title: String(localized: "photo_from_camera"), sourceType: .camera) + addAction(title: String(localized: "photo_from_photo_library"), sourceType: .photoLibrary) + alert.addAction(UIAlertAction(title: String(localized: "alert_cancel"), style: .cancel, handler: nil)) + + presentingViewController.present(alert, animated: true, completion: nil) + } + + func chatInputViewSendButtonPressed(_ view: ChatInputView) { + // HINT: call OCTSubmanagerChatsImpl.m -> sendMessageToChat() + submanagerChats.sendMessage(to: chat, text: view.text, type: .normal, successBlock: nil, failureBlock: nil) + DispatchQueue.main.asyncAfter(deadline: .now() + 10) { + os_log("PUSH:10_seconds") + self.submanagerChats.sendMessagePush(to: self.chat) + } + + view.text = "" + endUserInteraction() + } + + func chatInputViewTextDidChange(_ view: ChatInputView) { + try? submanagerChats.setIsTyping(true, in: chat) + inactivityTimer?.invalidate() + + inactivityTimer = Timer.scheduledTimer(timeInterval: Constants.inactivityTimeout, closure: {[weak self] _ -> Void in + self?.endUserInteraction() + }, repeats: false) + } +} + +extension ChatInputViewManager: UIImagePickerControllerDelegate { + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { + presentingViewController.dismiss(animated: true, completion: nil) + + guard let type = info[UIImagePickerControllerMediaType] as? String else { + return + } + + let typeImage = kUTTypeImage as String + let typeMovie = kUTTypeMovie as String + + switch type { + case typeImage: + sendImage(imagePickerInfo: info) + case typeMovie: + sendMovie(imagePickerInfo: info) + default: + return + } + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + presentingViewController.dismiss(animated: true, completion: nil) + } +} + +extension ChatInputViewManager: UINavigationControllerDelegate {} + +fileprivate extension ChatInputViewManager { + func endUserInteraction() { + try? submanagerChats.setIsTyping(false, in: chat) + inactivityTimer?.invalidate() + + if let inputView = inputView { + submanagerObjects.change(chat, enteredText: inputView.text) + } + } + + func sendImage(imagePickerInfo: [String : Any]) { + guard let image = imagePickerInfo[UIImagePickerControllerOriginalImage] as? UIImage else { + return + } + guard let data = UIImageJPEGRepresentation(image, 0.9) else { + return + } + + var fileName: String? = fileNameFromImageInfo(imagePickerInfo) + + if fileName == nil { + let dateString = DateFormatter(type: .dateAndTime).string(from: Date()) + fileName = "Photo \(dateString).jpg".replacingOccurrences(of: "/", with: "-") + } + + submanagerFiles.send(data, withFileName: fileName!, to: chat) { (error: Error) in + handleErrorWithType(.sendFileToFriend, error: error as NSError) + } + } + + func sendMovie(imagePickerInfo: [String : Any]) { + guard let url = imagePickerInfo[UIImagePickerControllerMediaURL] as? URL else { + return + } + + submanagerFiles.sendFile(atPath: url.path, moveToUploads: true, to: chat) { (error: Error) in + handleErrorWithType(.sendFileToFriend, error: error as NSError) + } + } + + func fileNameFromImageInfo(_ info: [String: Any]) -> String? { + guard let url = info[UIImagePickerControllerReferenceURL] as? URL else { + return nil + } + + let fetchResult = PHAsset.fetchAssets(withALAssetURLs: [url], options: nil) + + guard let asset = fetchResult.firstObject else { + return nil + } + + if #available(iOS 9.0, *) { + if let resource = PHAssetResource.assetResources(for: asset).first { + return resource.originalFilename + } + } else { + // Fallback on earlier versions + if let name = asset.value(forKey: "filename") as? String { + return name + } + } + + return nil + } +} diff --git a/Antidote/ChatListCell.swift b/Antidote/ChatListCell.swift new file mode 100644 index 0000000..ba034dc --- /dev/null +++ b/Antidote/ChatListCell.swift @@ -0,0 +1,160 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +class ChatListCell: BaseCell { + struct Constants { + static let AvatarSize = 40.0 + static let AvatarLeftOffset = 10.0 + static let AvatarRightOffset = 16.0 + + static let NicknameLabelHeight = 22.0 + static let MessageLabelHeight = 22.0 + + static let NicknameToDateMinOffset = 5.0 + static let DateToArrowOffset = 5.0 + + static let RightOffset = -7.0 + static let VerticalOffset = 3.0 + } + + fileprivate var avatarView: ImageViewWithStatus! + fileprivate var nicknameLabel: UILabel! + fileprivate var messageLabel: UILabel! + fileprivate var dateLabel: UILabel! + fileprivate var arrowImageView: UIImageView! + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let chatModel = model as? ChatListCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + separatorInset.left = CGFloat(Constants.AvatarLeftOffset + Constants.AvatarSize + Constants.AvatarRightOffset) + + avatarView.imageView.image = chatModel.avatar + avatarView.userStatusView.theme = theme + avatarView.userStatusView.userStatus = chatModel.status + avatarView.userStatusView.connectionStatus = chatModel.connectionstatus + + nicknameLabel.text = chatModel.nickname + nicknameLabel.textColor = theme.colorForType(.NormalText) + + messageLabel.text = chatModel.message + messageLabel.textColor = theme.colorForType(.ChatListCellMessage) + + dateLabel.text = chatModel.dateText + dateLabel.textColor = theme.colorForType(.ChatListCellMessage) + + backgroundColor = chatModel.isUnread ? theme.colorForType(.ChatListCellUnreadBackground) : .clear + + if (chatModel.isUnread) { + arrowImageView.backgroundColor = theme.colorForType(.ChatListCellUnreadArrowBackground) + } else { + arrowImageView.backgroundColor = .clear + } + + // HINT: make the arrow image view a nice circle shape + arrowImageView.layer.cornerRadius = arrowImageView.frame.height / 2 + } + + override func createViews() { + super.createViews() + + avatarView = ImageViewWithStatus() + contentView.addSubview(avatarView) + + nicknameLabel = UILabel() + nicknameLabel.font = UIFont.systemFont(ofSize: 18.0) + contentView.addSubview(nicknameLabel) + + messageLabel = UILabel() + messageLabel.font = UIFont.systemFont(ofSize: 12.0) + contentView.addSubview(messageLabel) + + dateLabel = UILabel() + dateLabel.font = UIFont.antidoteFontWithSize(12.0, weight: .light) + contentView.addSubview(dateLabel) + + let image = UIImage(named: "right-arrow")!.flippedToCorrectLayout() + + arrowImageView = UIImageView(image: image) + arrowImageView.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) + contentView.addSubview(arrowImageView) + } + + override func installConstraints() { + super.installConstraints() + + avatarView.snp.makeConstraints { + $0.leading.equalTo(contentView).offset(Constants.AvatarLeftOffset) + $0.centerY.equalTo(contentView) + $0.size.equalTo(Constants.AvatarSize) + } + + nicknameLabel.snp.makeConstraints { + $0.leading.equalTo(avatarView.snp.trailing).offset(Constants.AvatarRightOffset) + $0.top.equalTo(contentView).offset(Constants.VerticalOffset) + $0.height.equalTo(Constants.NicknameLabelHeight) + } + + messageLabel.snp.makeConstraints { + $0.leading.equalTo(nicknameLabel) + $0.trailing.equalTo(contentView).offset(Constants.RightOffset) + $0.top.equalTo(nicknameLabel.snp.bottom) + $0.bottom.equalTo(contentView).offset(-Constants.VerticalOffset) + $0.height.equalTo(Constants.MessageLabelHeight) + } + + dateLabel.snp.makeConstraints { + $0.leading.greaterThanOrEqualTo(nicknameLabel.snp.trailing).offset(Constants.NicknameToDateMinOffset) + $0.top.equalTo(nicknameLabel) + $0.height.equalTo(nicknameLabel) + } + + arrowImageView.snp.makeConstraints { + $0.centerY.equalTo(dateLabel) + $0.leading.greaterThanOrEqualTo(dateLabel.snp.trailing).offset(Constants.DateToArrowOffset) + $0.trailing.equalTo(contentView).offset(Constants.RightOffset) + } + } +} + +// Accessibility +extension ChatListCell { + override var isAccessibilityElement: Bool { + get { + return true + } + set {} + } + + override var accessibilityLabel: String? { + get { + var label = nicknameLabel.text ?? "" + label += ", " + avatarView.userStatusView.userStatus.toString() + + return label + } + set {} + } + + override var accessibilityValue: String? { + get { + return messageLabel.text! + ", " + dateLabel.text! + } + set {} + } + + override var accessibilityTraits: UIAccessibilityTraits { + get { + return UIAccessibilityTraitSelected + } + set {} + } +} diff --git a/Antidote/ChatListCellModel.swift b/Antidote/ChatListCellModel.swift new file mode 100644 index 0000000..f1f0952 --- /dev/null +++ b/Antidote/ChatListCellModel.swift @@ -0,0 +1,18 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatListCellModel: BaseCellModel { + var avatar: UIImage? + + var nickname: String = "" + var message: String = "" + var dateText: String = "" + + var status: UserStatus = .offline + var connectionstatus: ConnectionStatus = .none + + var isUnread: Bool = false +} diff --git a/Antidote/ChatListController.swift b/Antidote/ChatListController.swift new file mode 100644 index 0000000..340e8f2 --- /dev/null +++ b/Antidote/ChatListController.swift @@ -0,0 +1,107 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +protocol ChatListControllerDelegate: class { + func chatListController(_ controller: ChatListController, didSelectChat chat: OCTChat) +} + +class ChatListController: UIViewController { + weak var delegate: ChatListControllerDelegate? + + fileprivate let theme: Theme + fileprivate weak var submanagerChats: OCTSubmanagerChats! + fileprivate weak var submanagerObjects: OCTSubmanagerObjects! + + fileprivate var placeholderLabel: UILabel! + fileprivate var tableManager: ChatListTableManager! + + init(theme: Theme, submanagerChats: OCTSubmanagerChats, submanagerObjects: OCTSubmanagerObjects) { + self.theme = theme + self.submanagerChats = submanagerChats + self.submanagerObjects = submanagerObjects + + super.init(nibName: nil, bundle: nil) + + edgesForExtendedLayout = UIRectEdge() + title = String(localized: "chats_title") + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + createTableView() + createPlaceholderView() + installConstraints() + + updateViewsVisibility() + } + + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + + tableManager.tableView.setEditing(editing, animated: animated) + } +} + +extension ChatListController: ChatListTableManagerDelegate { + func chatListTableManager(_ manager: ChatListTableManager, didSelectChat chat: OCTChat) { + delegate?.chatListController(self, didSelectChat: chat) + } + + func chatListTableManager(_ manager: ChatListTableManager, presentAlertController controller: UIAlertController) { + present(controller, animated: true, completion: nil) + } + + func chatListTableManagerWasUpdated(_ manager: ChatListTableManager) { + updateViewsVisibility() + } +} + +private extension ChatListController { + func updateViewsVisibility() { + navigationItem.leftBarButtonItem = tableManager.isEmpty ? nil : editButtonItem + placeholderLabel.isHidden = !tableManager.isEmpty + } + + func createTableView() { + let tableView = UITableView() + tableView.estimatedRowHeight = 44.0 + tableView.backgroundColor = theme.colorForType(.NormalBackground) + tableView.sectionIndexColor = theme.colorForType(.LinkText) + // removing separators on empty lines + tableView.tableFooterView = UIView() + + view.addSubview(tableView) + + tableView.register(ChatListCell.self, forCellReuseIdentifier: ChatListCell.staticReuseIdentifier) + + tableManager = ChatListTableManager(theme: theme, tableView: tableView, submanagerChats: submanagerChats, submanagerObjects: submanagerObjects) + tableManager.delegate = self + } + + func createPlaceholderView() { + placeholderLabel = UILabel() + placeholderLabel.text = String(localized: "chat_no_chats") + placeholderLabel.textColor = theme.colorForType(.EmptyScreenPlaceholderText) + placeholderLabel.font = UIFont.antidoteFontWithSize(26.0, weight: .light) + view.addSubview(placeholderLabel) + } + + func installConstraints() { + tableManager.tableView.snp.makeConstraints { + $0.edges.equalTo(view) + } + + placeholderLabel.snp.makeConstraints { + $0.center.equalTo(view) + $0.size.equalTo(placeholderLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))) + } + } +} diff --git a/Antidote/ChatListTableManager.swift b/Antidote/ChatListTableManager.swift new file mode 100644 index 0000000..fbfa6f1 --- /dev/null +++ b/Antidote/ChatListTableManager.swift @@ -0,0 +1,222 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +protocol ChatListTableManagerDelegate: class { + func chatListTableManager(_ manager: ChatListTableManager, didSelectChat chat: OCTChat) + func chatListTableManager(_ manager: ChatListTableManager, presentAlertController controller: UIAlertController) + func chatListTableManagerWasUpdated(_ manager: ChatListTableManager) +} + +class ChatListTableManager: NSObject { + weak var delegate: ChatListTableManagerDelegate? + + let tableView: UITableView + + var isEmpty: Bool { + get { + return chats.count == 0 + } + } + + fileprivate let theme: Theme + fileprivate let avatarManager: AvatarManager + fileprivate let dateFormatter: DateFormatter + fileprivate let timeFormatter: DateFormatter + + fileprivate weak var submanagerChats: OCTSubmanagerChats! + + fileprivate let chats: Results + fileprivate var chatsToken: RLMNotificationToken? + fileprivate let friends: Results + fileprivate var friendsToken: RLMNotificationToken? + + init(theme: Theme, tableView: UITableView, submanagerChats: OCTSubmanagerChats, submanagerObjects: OCTSubmanagerObjects) { + self.tableView = tableView + + self.theme = theme + self.avatarManager = AvatarManager(theme: theme) + self.dateFormatter = DateFormatter(type: .relativeDate) + self.timeFormatter = DateFormatter(type: .time) + + self.submanagerChats = submanagerChats + + self.chats = submanagerObjects.chats().sortedResultsUsingProperty("lastActivityDateInterval", ascending: false) + self.friends = submanagerObjects.friends() + + super.init() + + tableView.delegate = self + tableView.dataSource = self + + addNotificationBlocks() + } + + deinit { + chatsToken?.invalidate() + friendsToken?.invalidate() + } +} + +extension ChatListTableManager: UITableViewDataSource { + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + var avatarData: Data? + var nickname = String(localized: "contact_deleted") + var connectionStatus = OCTToxConnectionStatus.none + var userStatus = OCTToxUserStatus.none + + let chat = chats[indexPath.row] + let friend = chat.friends.lastObject() as? OCTFriend + + if let friend = friend { + avatarData = friend.avatarData + nickname = friend.nickname + connectionStatus = friend.connectionStatus + userStatus = friend.status + } + + let model = ChatListCellModel() + if let data = avatarData { + model.avatar = UIImage(data: data) + } + else { + model.avatar = avatarManager.avatarFromString( + nickname, + diameter: CGFloat(ChatListCell.Constants.AvatarSize)) + } + + model.nickname = nickname + model.message = lastMessage(in: chat, friend: friend) + if let date = chat.lastActivityDate() { + model.dateText = dateTextFromDate(date) + } + + model.status = UserStatus(connectionStatus: connectionStatus, userStatus: userStatus) + model.connectionstatus = ConnectionStatus(connectionStatus: connectionStatus) + model.isUnread = chat.hasUnreadMessages() + + let cell = tableView.dequeueReusableCell(withIdentifier: ChatListCell.staticReuseIdentifier) as! ChatListCell + cell.setupWithTheme(theme, model: model) + + return cell + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return chats.count + } + + func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + let alert = UIAlertController(title: String(localized:"delete_chat_title"), message: nil, preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: String(localized: "alert_cancel"), style: .default, handler: nil)) + alert.addAction(UIAlertAction(title: String(localized: "alert_delete"), style: .destructive) { [unowned self] _ -> Void in + let chat = self.chats[indexPath.row] + self.submanagerChats.removeAllMessages(in: chat, removeChat: true) + }) + + delegate?.chatListTableManager(self, presentAlertController: alert) + } + } +} + +extension ChatListTableManager: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + let chat = self.chats[indexPath.row] + delegate?.chatListTableManager(self, didSelectChat: chat) + } +} + +private extension ChatListTableManager { + func addNotificationBlocks() { + chatsToken = chats.addNotificationBlock { [unowned self] change in + switch change { + case .initial: + break + case .update(_, let deletions, let insertions, let modifications): + // TODO: fix me, this is a hack to avoid the crash + self.tableView.reloadData() + self.tableView.beginUpdates() + /* + self.tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, + with: .automatic) + self.tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, + with: .automatic) + self.tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, + with: .none) + */ + self.tableView.endUpdates() + + self.delegate?.chatListTableManagerWasUpdated(self) + case .error(let error): + fatalError("\(error)") + } + } + + friendsToken = friends.addNotificationBlock { [unowned self] change in + switch change { + case .initial: + break + case .update(let friends, _, _, let modifications): + guard let friends = friends else { + break + } + + for index in modifications { + let friend = friends[index] + + let pathsToUpdate = self.tableView.indexPathsForVisibleRows?.filter { + let chat = self.chats[$0.row] + + return Int(chat.friends.index(of: friend)) != NSNotFound + } + + if let paths = pathsToUpdate { + // TODO: fix me, this crashes + // self.tableView.reloadRows(at: paths, with: .none) + } + } + case .error(let error): + fatalError("\(error)") + } + } + } + + func lastMessage(in chat: OCTChat, friend: OCTFriend?) -> String { + guard let message = chat.lastMessage else { + return "" + } + + if let friend = friend, friend.isTyping { + return String(localized: "chat_is_typing_text") + } + else if let text = message.messageText { + return text.text ?? "" + } + else if let file = message.messageFile { + let fileName = file.fileName ?? "" + return String(localized: message.isOutgoing() ? "chat_outgoing_file" : "chat_incoming_file") + " \(fileName)" + } + else if let call = message.messageCall { + switch call.callEvent { + case .answered: + let timeString = String(timeInterval: call.callDuration) + return String(localized: "chat_call_finished") + " - \(timeString)" + case .unanswered: + return message.isOutgoing() ? String(localized: "chat_unanwered_call") : String(localized: "chat_missed_call_message") + } + } + + return "" + } + + func dateTextFromDate(_ date: Date) -> String { + let isToday = (Calendar.current as NSCalendar).compare(Date(), to: date, toUnitGranularity: .day) == .orderedSame + + return isToday ? timeFormatter.string(from: date) : dateFormatter.string(from: date) + } +} diff --git a/Antidote/ChatMovableDateCell.swift b/Antidote/ChatMovableDateCell.swift new file mode 100644 index 0000000..15233d6 --- /dev/null +++ b/Antidote/ChatMovableDateCell.swift @@ -0,0 +1,189 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +protocol ChatMovableDateCellDelegate: class { + func chatMovableDateCellCopyPressed(_ cell: ChatMovableDateCell) + func chatMovableDateCellDeletePressed(_ cell: ChatMovableDateCell) + func chatMovableDateCellMorePressed(_ cell: ChatMovableDateCell) +} + +class ChatMovableDateCell: BaseCell { + private static var __once: () = { + var items = UIMenuController.shared.menuItems ?? [UIMenuItem]() + items += [ + UIMenuItem(title: String(localized: "chat_more_menu_item"), action: #selector(moreAction)) + ] + + UIMenuController.shared.menuItems = items + }() + weak var delegate: ChatMovableDateCellDelegate? + + var canBeCopied = false + + /** + Superview for content that should move while panning table to the left. + */ + var movableContentView: UIView! + + var movableOffset: CGFloat = 0 { + didSet { + var offset = movableOffset + + if (UserDefaultsManager().DateonmessageMode == true) { + offset = 39 + } + + if #available(iOS 9.0, *) { + if UIView.userInterfaceLayoutDirection(for: self.semanticContentAttribute) == .rightToLeft { + offset = -offset + } + } + + if offset > 0.0 { + offset = 0.0 + } + + let minOffset = -dateLabel.frame.size.width - 5.0 + + if offset < minOffset { + offset = minOffset + } + + movableContentViewLeftConstraint.update(offset: offset) + layoutIfNeeded() + } + } + + fileprivate var movableContentViewLeftConstraint: Constraint! + fileprivate var dateLabel: UILabel! + + fileprivate var isShowingMenu: Bool = false + fileprivate static var setupOnceToken: Int = 0 + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let movableModel = model as? ChatMovableDateCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + _ = ChatMovableDateCell.__once + + dateLabel.text = movableModel.dateString + dateLabel.numberOfLines = 0 // --> multiline label + dateLabel.textColor = theme.colorForType(.ChatListCellMessage) + } + + override func createViews() { + super.createViews() + + movableContentView = UIView() + movableContentView.backgroundColor = .clear + contentView.addSubview(movableContentView) + + dateLabel = UILabel() + dateLabel.font = UIFont.antidoteFontWithSize(11.0, weight: .medium) + movableContentView.addSubview(dateLabel) + + // Using empty view for multiple selection background. + multipleSelectionBackgroundView = UIView() + } + + override func installConstraints() { + super.installConstraints() + + movableContentView.snp.makeConstraints { + $0.top.equalTo(contentView) + if (UserDefaultsManager().DateonmessageMode == true) { + movableContentViewLeftConstraint = $0.leading.equalTo(contentView).constraint.update(offset: -39) + } else { + movableContentViewLeftConstraint = $0.leading.equalTo(contentView).constraint + } + $0.size.equalTo(contentView) + } + + dateLabel.snp.makeConstraints { + $0.centerY.equalTo(movableContentView) + $0.leading.equalTo(movableContentView.snp.trailing) + } + } + + override func setSelected(_ selected: Bool, animated: Bool) { + if !isEditing { + // don't call super in case of editing to avoid background change + return + } + + super.setSelected(selected, animated: animated) + } +} + +// Accessibility +extension ChatMovableDateCell { + override var isAccessibilityElement: Bool { + get { + return true + } + set {} + } + + override var accessibilityValue: String? { + get { + return dateLabel.text! + } + set {} + } +} + +extension ChatMovableDateCell: ChatEditable { + // Override in subclass to enable menu + @objc func shouldShowMenu() -> Bool { + return false + } + + // Override in subclass to enable menu + @objc func menuTargetRect() -> CGRect { + return CGRect.zero + } + + @objc func willShowMenu() { + isShowingMenu = true + } + + @objc func willHideMenu() { + isShowingMenu = false + } +} + +// Methods to make UIMenuController work. +extension ChatMovableDateCell { + func isMenuActionSupportedByCell(_ action: Selector) -> Bool { + switch action { + case #selector(copy(_:)): + return canBeCopied + case #selector(delete(_:)): + return true + case #selector(moreAction): + return true + default: + return false + } + } + + override func copy(_ sender: Any?) { + delegate?.chatMovableDateCellCopyPressed(self) + } + + override func delete(_ sender: Any?) { + delegate?.chatMovableDateCellDeletePressed(self) + } + + @objc func moreAction() { + delegate?.chatMovableDateCellMorePressed(self) + } +} diff --git a/Antidote/ChatMovableDateCellModel.swift b/Antidote/ChatMovableDateCellModel.swift new file mode 100644 index 0000000..c5fdf40 --- /dev/null +++ b/Antidote/ChatMovableDateCellModel.swift @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatMovableDateCellModel: BaseCellModel { + var dateString: String = "" +} diff --git a/Antidote/ChatOutgoingCallCell.swift b/Antidote/ChatOutgoingCallCell.swift new file mode 100644 index 0000000..e27c6ef --- /dev/null +++ b/Antidote/ChatOutgoingCallCell.swift @@ -0,0 +1,86 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let RightOffset = -20.0 + static let ImageViewToLabelOffset = -5.0 + static let ImageViewYOffset = -1.0 + static let VerticalOffset = 8.0 +} + +class ChatOutgoingCallCell: ChatMovableDateCell { + fileprivate var callImageView: UIImageView! + fileprivate var label: UILabel! + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let outgoingModel = model as? ChatOutgoingCallCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + label.textColor = theme.colorForType(.ChatListCellMessage) + callImageView.tintColor = theme.colorForType(.LinkText) + + if outgoingModel.answered { + label.text = String(localized: "chat_call_message") + String(timeInterval: outgoingModel.callDuration) + } + else { + label.text = String(localized: "chat_unanwered_call") + } + } + + override func createViews() { + super.createViews() + + let image = UIImage.templateNamed("start-call-small") + + callImageView = UIImageView(image: image) + movableContentView.addSubview(callImageView) + + label = UILabel() + label.font = UIFont.antidoteFontWithSize(16.0, weight: .light) + movableContentView.addSubview(label) + } + + override func installConstraints() { + super.installConstraints() + + callImageView.snp.makeConstraints { + $0.centerY.equalTo(label).offset(Constants.ImageViewYOffset) + $0.trailing.equalTo(label.snp.leading).offset(Constants.ImageViewToLabelOffset) + } + + label.snp.makeConstraints { + $0.top.equalTo(contentView).offset(Constants.VerticalOffset) + $0.bottom.equalTo(contentView).offset(-Constants.VerticalOffset) + $0.trailing.equalTo(movableContentView).offset(Constants.RightOffset) + } + } +} + +// Accessibility +extension ChatOutgoingCallCell { + override var accessibilityLabel: String? { + get { + return label.text + } + set {} + } +} + +// ChatEditable +extension ChatOutgoingCallCell { + override func shouldShowMenu() -> Bool { + return true + } + + override func menuTargetRect() -> CGRect { + return label.frame + } +} diff --git a/Antidote/ChatOutgoingCallCellModel.swift b/Antidote/ChatOutgoingCallCellModel.swift new file mode 100644 index 0000000..9518f93 --- /dev/null +++ b/Antidote/ChatOutgoingCallCellModel.swift @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatOutgoingCallCellModel: ChatMovableDateCellModel { + var callDuration: TimeInterval = 0 + var answered: Bool = true +} diff --git a/Antidote/ChatOutgoingFileCell.swift b/Antidote/ChatOutgoingFileCell.swift new file mode 100644 index 0000000..4943bb7 --- /dev/null +++ b/Antidote/ChatOutgoingFileCell.swift @@ -0,0 +1,98 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let BigOffset = 20.0 + static let SmallOffset = 8.0 + static let ImageButtonSize = 180.0 + static let CloseButtonSize = 25.0 +} + +class ChatOutgoingFileCell: ChatGenericFileCell { + override func setButtonImage(_ image: UIImage) { + super.setButtonImage(image) + + loadingView.bottomLabel.isHidden = true + + if state == .cancelled { + loadingView.bottomLabel.isHidden = false + loadingView.centerImageView.image = nil + } + } + + override func createViews() { + super.createViews() + + movableContentView.addSubview(loadingView) + movableContentView.addSubview(cancelButton) + movableContentView.addSubview(retryButton) + } + + override func installConstraints() { + super.installConstraints() + + cancelButton.snp.makeConstraints { + $0.trailing.equalTo(loadingView.snp.leading).offset(-Constants.SmallOffset) + $0.top.equalTo(loadingView) + $0.size.equalTo(Constants.CloseButtonSize) + } + + retryButton.snp.makeConstraints { + $0.center.equalTo(cancelButton) + $0.size.equalTo(cancelButton) + } + + loadingView.snp.makeConstraints { + $0.trailing.equalTo(movableContentView).offset(-Constants.BigOffset) + $0.top.equalTo(movableContentView).offset(Constants.SmallOffset) + $0.bottom.equalTo(movableContentView).offset(-Constants.SmallOffset) + $0.size.equalTo(Constants.ImageButtonSize) + } + } + + override func updateViewsWithState(_ state: ChatGenericFileCellModel.State, fileModel: ChatGenericFileCellModel) { + loadingView.imageButton.isUserInteractionEnabled = true + loadingView.progressView.isHidden = true + loadingView.topLabel.isHidden = true + loadingView.bottomLabel.isHidden = false + loadingView.bottomLabel.text = fileModel.fileName + + cancelButton.isHidden = false + retryButton.isHidden = true + + switch state { + case .waitingConfirmation: + loadingView.imageButton.isUserInteractionEnabled = false + loadingView.bottomLabel.text = String(localized: "chat_waiting") + case .loading: + loadingView.progressView.isHidden = false + case .paused: + break + case .cancelled: + loadingView.bottomLabel.text = String(localized: "chat_file_cancelled") + cancelButton.isHidden = true + retryButton.isHidden = false + case .done: + cancelButton.isHidden = true + } + } + + override func loadingViewPressed() { + switch state { + case .waitingConfirmation: + break + case .loading: + pauseOrResumeHandle?() + case .paused: + pauseOrResumeHandle?() + case .cancelled: + openHandle?() + case .done: + openHandle?() + } + } +} diff --git a/Antidote/ChatOutgoingFileCellModel.swift b/Antidote/ChatOutgoingFileCellModel.swift new file mode 100644 index 0000000..de6f906 --- /dev/null +++ b/Antidote/ChatOutgoingFileCellModel.swift @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatOutgoingFileCellModel: ChatGenericFileCellModel { + +} diff --git a/Antidote/ChatOutgoingTextCell.swift b/Antidote/ChatOutgoingTextCell.swift new file mode 100644 index 0000000..6356fbd --- /dev/null +++ b/Antidote/ChatOutgoingTextCell.swift @@ -0,0 +1,51 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +class ChatOutgoingTextCell: ChatBaseTextCell { + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let textModel = model as? ChatOutgoingTextCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + bubbleNormalBackground = theme.colorForType(.ChatOutgoingBubble) + if !textModel.delivered { + if !textModel.sentpush { + bubbleNormalBackground = theme.colorForType(.ChatOutgoingUnreadBubble) + } else { + bubbleNormalBackground = theme.colorForType(.ChatOutgoingSentPushBubble) + } + } + + bubbleView.textColor = theme.colorForType(.ConnectingText) + bubbleView.backgroundColor = bubbleNormalBackground + bubbleView.tintColor = theme.colorForType(.NormalText) + bubbleView.font = UIFont.preferredFont(forTextStyle: .body) + } + + override func installConstraints() { + super.installConstraints() + + bubbleView.snp.makeConstraints { + $0.top.equalTo(movableContentView).offset(ChatBaseTextCell.Constants.BubbleVerticalOffset) + $0.bottom.equalTo(movableContentView).offset(-ChatBaseTextCell.Constants.BubbleVerticalOffset) + $0.trailing.equalTo(movableContentView).offset(-ChatBaseTextCell.Constants.BubbleHorizontalOffset) + } + } +} + +// Accessibility +extension ChatOutgoingTextCell { + override var accessibilityLabel: String? { + get { + return String(localized: "accessibility_outgoing_message_label") + } + set {} + } +} diff --git a/Antidote/ChatOutgoingTextCellModel.swift b/Antidote/ChatOutgoingTextCellModel.swift new file mode 100644 index 0000000..f92b695 --- /dev/null +++ b/Antidote/ChatOutgoingTextCellModel.swift @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatOutgoingTextCellModel : ChatBaseTextCellModel { + var delivered: Bool = false + var sentpush: Bool = false +} diff --git a/Antidote/ChatPrivateController.swift b/Antidote/ChatPrivateController.swift new file mode 100644 index 0000000..e23686e --- /dev/null +++ b/Antidote/ChatPrivateController.swift @@ -0,0 +1,1508 @@ + +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import CoreLocation +import SnapKit +import MobileCoreServices +import os + +private struct Constants { + static let MessagesPortionSize = 50 + + static let InputViewTopOffset: CGFloat = 50.0 + + static let NewMessageViewAllowedDelta: CGFloat = 20.0 + static let NewMessageViewEdgesOffset: CGFloat = 5.0 + static let NewMessageViewTopOffset: CGFloat = -15.0 + static let NewMessageViewAnimationDuration = 0.2 + + static let ResetPanAnimationDuration = 0.3 + + static let MaxImageSizeToShowInline: OCTToxFileSize = 20 * 1024 * 1024 + + static let MaxInlineImageSide: CGFloat = LoadingImageView.Constants.ImageButtonSize * UIScreen.main.scale +} + +protocol ChatPrivateControllerDelegate: class { + func chatPrivateControllerWillAppear(_ controller: ChatPrivateController) + func chatPrivateControllerWillDisappear(_ controller: ChatPrivateController) + func chatPrivateControllerCallToChat(_ controller: ChatPrivateController, enableVideo: Bool) + func chatPrivateControllerShowQuickLookController( + _ controller: ChatPrivateController, + dataSource: QuickLookPreviewControllerDataSource, + selectedIndex: Int) +} + +class ChatPrivateController: KeyboardNotificationController, CLLocationManagerDelegate { + let chat: OCTChat + + fileprivate weak var delegate: ChatPrivateControllerDelegate? + + let location_manager = CLLocationManager() + fileprivate let theme: Theme + fileprivate weak var submanagerChats: OCTSubmanagerChats! + fileprivate weak var submanagerObjects: OCTSubmanagerObjects! + fileprivate weak var submanagerFiles: OCTSubmanagerFiles! + + fileprivate let messages: Results + fileprivate var messagesToken: RLMNotificationToken? + fileprivate var visibleMessages: Int + + fileprivate let friend: OCTFriend? + fileprivate var friendToken: RLMNotificationToken? + + fileprivate let imageCache = NSCache() + + fileprivate let timeFormatter: DateFormatter + fileprivate let dateFormatter: DateFormatter + + fileprivate var audioButton: UIBarButtonItem! + fileprivate var videoButton: UIBarButtonItem! + fileprivate var locationButton: UIBarButtonItem! + fileprivate var CallWaitingView: UIView! + fileprivate var callwaiting_running: Bool! + fileprivate var CallWaitingCancelButton: CallButton? + fileprivate let linearBar: LinearProgressBar = LinearProgressBar() + + fileprivate var titleView: ChatPrivateTitleView! + fileprivate var tableView: UITableView? + fileprivate var typingHeaderView: ChatTypingHeaderView! + fileprivate var fauxOfflineHeaderView: ChatFauxOfflineHeaderView! + fileprivate var newMessagesView: UIView! + fileprivate var chatInputView: ChatInputView! + fileprivate var editMessagesToolbar: UIToolbar! + + fileprivate var chatInputViewManager: ChatInputViewManager! + + fileprivate var tableViewTapGestureRecognizer: UITapGestureRecognizer! + + fileprivate var tableViewToChatInputConstraint: Constraint! + fileprivate var typingViewToChatInputConstraint: Constraint! + + fileprivate var newMessageViewTopConstraint: Constraint? + fileprivate var chatInputViewBottomConstraint: Constraint? + + fileprivate var newMessagesViewVisible = false + + /// Index path for cell with UIMenu presented. + fileprivate var selectedMenuIndexPath: IndexPath? + + fileprivate let showKeyboardOnAppear: Bool + fileprivate var disableNextInputViewAnimation = false + + init(theme: Theme, chat: OCTChat, submanagerChats: OCTSubmanagerChats, submanagerObjects: OCTSubmanagerObjects, submanagerFiles: OCTSubmanagerFiles, delegate: ChatPrivateControllerDelegate, showKeyboardOnAppear: Bool = false) { + self.theme = theme + self.chat = chat + self.friend = chat.friends.lastObject() as? OCTFriend + self.submanagerChats = submanagerChats + self.submanagerObjects = submanagerObjects + self.submanagerFiles = submanagerFiles + self.delegate = delegate + self.showKeyboardOnAppear = showKeyboardOnAppear + self.callwaiting_running = false + + let predicate = NSPredicate(format: "chatUniqueIdentifier == %@", chat.uniqueIdentifier) + self.messages = submanagerObjects.messages(predicate: predicate).sortedResultsUsingProperty("dateInterval", ascending: false) + self.visibleMessages = Constants.MessagesPortionSize + + self.timeFormatter = DateFormatter(type: .time) + self.dateFormatter = DateFormatter() + dateFormatter.dateFormat = "MMMdd" + + super.init() + + edgesForExtendedLayout = UIRectEdge() + hidesBottomBarWhenPushed = true + + NotificationCenter.default.addObserver( + self, + selector: #selector(ChatPrivateController.applicationDidBecomeActive), + name: NSNotification.Name.UIApplicationDidBecomeActive, + object: nil) + + NotificationCenter.default.addObserver( + self, + selector: #selector(ChatPrivateController.willShowMenuNotification(_:)), + name: NSNotification.Name.UIMenuControllerWillShowMenu, + object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(ChatPrivateController.willHideMenuNotification), + name: NSNotification.Name.UIMenuControllerWillHideMenu, + object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + + messagesToken?.invalidate() + friendToken?.invalidate() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + createTableView() + createTableHeaderViews() + createNewMessagesView() + createInputView() + createEditMessageToolbar() + installConstraints() + } + + override func viewDidLoad() { + super.viewDidLoad() + + addMessagesNotification() + + createNavigationViews() + addFriendNotification() + + // HINT: request Location updates here + LocationManager.shared.requestAccess() + + // HINT: location manager to get location on button pressed + location_manager.delegate = self + + self.configureLinearProgressBar() + } + + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + if let location = locations.first { + let lat_str = String(format: "%.5f", location.coordinate.latitude) + let lon_str = String(format: "%.5f", location.coordinate.longitude) + let location_string = lat_str + ", " + lon_str + let zoom_level = "14" + print("location: \(location_string)") + // let location_url = "https://www.openstreetmap.org/search?query=" + lat_str + "%2C%20" + lon_str + "#map=" + zoom_level + "/" + lat_str + "/" + lon_str + let location_url = "https://www.openstreetmap.org/?mlat=" + lat_str + "&mlon=" + lon_str + "#map=" + zoom_level + "/" + lat_str + "/" + lon_str + + // chatInputView.text = "my Location: " + location_string + "\n" + location_url + + //DispatchQueue.main.async { + self.submanagerChats.sendMessage(to: self.chat, text: "my Location: " + location_string + "\n" + location_url, type: .normal, successBlock: nil, failureBlock: nil) + //} + } + } + + func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + // print("Failed to find user's location: \(error.localizedDescription)") + } + + fileprivate func configureLinearProgressBar(){ + linearBar.backgroundColor = UIColor(red:0.68, green:0.81, blue:0.72, alpha:1.0) + linearBar.progressBarColor = UIColor(red:0.26, green:0.65, blue:0.45, alpha:1.0) + linearBar.heightForLinearBar = 5 + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + updateLastReadDate() + delegate?.chatPrivateControllerWillAppear(self) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + delegate?.chatPrivateControllerWillDisappear(self) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if showKeyboardOnAppear { + disableNextInputViewAnimation = true + _ = chatInputView.becomeFirstResponder() + } + } + + override func keyboardWillShowAnimated(keyboardFrame frame: CGRect) { + super.keyboardWillShowAnimated(keyboardFrame: frame) + + guard let constraint = chatInputViewBottomConstraint else { + return + } + + constraint.update(offset: -frame.size.height) + + if disableNextInputViewAnimation { + disableNextInputViewAnimation = false + + UIView.setAnimationsEnabled(false) + view.layoutIfNeeded() + UIView.setAnimationsEnabled(true) + } + else { + view.layoutIfNeeded() + } + } + + override func keyboardWillHideAnimated(keyboardFrame frame: CGRect) { + super.keyboardWillHideAnimated(keyboardFrame: frame) + + guard let constraint = chatInputViewBottomConstraint else { + return + } + + // TODO: this moves the input view a bit more to the top, because the home button "line" is in the way otherwise + // please fix me properly in the future + constraint.update(offset: 0.0) + if #available(iOS 11.0, *) { + let keyWindow = UIApplication.shared.keyWindow + let b = keyWindow?.safeAreaInsets.bottom + constraint.update(offset: -(b ?? 20)) + } + + if disableNextInputViewAnimation { + disableNextInputViewAnimation = false + + UIView.setAnimationsEnabled(false) + view.layoutIfNeeded() + UIView.setAnimationsEnabled(true) + } + else { + view.layoutIfNeeded() + } + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + updateInputViewMaxHeight() + } +} + +// MARK: Actions +extension ChatPrivateController { + @objc func tapOnTableView() { + _ = chatInputView.resignFirstResponder() + } + + @objc func panOnTableView(_ recognizer: UIPanGestureRecognizer) { + guard let tableView = tableView else { + return + } + + if (UserDefaultsManager().DateonmessageMode == true) { + return + } + + let translation = recognizer.translation(in: recognizer.view) + recognizer.setTranslation(CGPoint.zero, in: recognizer.view) + + _ = tableView.visibleCells.filter { + $0 is ChatMovableDateCell + }.map { + $0 as! ChatMovableDateCell + }.map { + switch recognizer.state { + case .possible: + fallthrough + case .began: + // nop + break + case .changed: + $0.movableOffset += translation.x + case .ended: + fallthrough + case .cancelled: + fallthrough + case .failed: + let cell = $0 + UIView.animate(withDuration: Constants.ResetPanAnimationDuration, animations: { + cell.movableOffset = 0.0 + }) + } + } + } + + @objc func newMessagesViewPressed() { + guard let tableView = tableView else { + return + } + + tableView.setContentOffset(CGPoint.zero, animated: true) + + // iOS is broken =\ + // See https://stackoverflow.com/a/30804874 + let delayTime = DispatchTime.now() + Double(Int64(0.2 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) + DispatchQueue.main.asyncAfter(deadline: delayTime) { [weak self] in + self?.tableView?.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true) + } + } + + func messageBox(messageTitle: String, messageAlert: String, messageBoxStyle: UIAlertControllerStyle, alertActionStyle: UIAlertActionStyle, completionHandler: @escaping () -> Void) + { + let alert = UIAlertController(title: messageTitle, message: messageAlert, preferredStyle: messageBoxStyle) + + let okAction = UIAlertAction(title: "Cancel", style: alertActionStyle) { _ in + completionHandler() // This will only get called after okay is tapped in the alert + } + + alert.addAction(okAction) + present(alert, animated: true, completion: nil) + } + + @objc + func buttonCallWaitingCancel() { + callwaiting_running = false + CallWaitingView.removeFromSuperview() + self.linearBar.stopAnimation() + } + + @objc func audioCallButtonPressed() { + + if let friend = self.friend { + let connection_status = ConnectionStatus(connectionStatus: friend.connectionStatus) + if (connection_status != .none) + { + // HINT: friend is online, so start the call now + callwaiting_running = false + delegate?.chatPrivateControllerCallToChat(self, enableVideo: false) + } + else + { + // HINT: friend is not online, show call waiting screen + callwaiting_running = true + let window = UIApplication.shared.keyWindow! + CallWaitingView = UIView(frame: window.bounds) + window.addSubview(CallWaitingView) + CallWaitingView.backgroundColor = .black + + CallWaitingCancelButton = CallButton(theme: theme, type: .decline, buttonSize: .big) + CallWaitingCancelButton!.addTarget(self, + action: #selector(buttonCallWaitingCancel), + for: .touchUpInside) + // CallWaitingCancelButton!.backgroundColor = .white + + let lb1 = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 80)) + lb1.text = "Call" + lb1.textAlignment = .center; + lb1.font = lb1.font.withSize(35) + lb1.textColor = .white + lb1.backgroundColor = .black + lb1.numberOfLines = 0; + lb1.sizeToFit() + + let lb2 = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 80)) + lb2.text = friend.nickname + lb2.textAlignment = .center; + lb2.font = lb1.font.withSize(30) + lb2.textColor = .white + lb2.backgroundColor = .black + lb2.numberOfLines = 0; + lb2.sizeToFit() + + let lb3 = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 80)) + lb3.text = "waiting for friend to come online ..." + lb3.textAlignment = .center; + lb3.font = lb1.font.withSize(20) + lb3.textColor = .white + lb3.backgroundColor = .black + lb3.numberOfLines = 0; + lb3.lineBreakMode = .byWordWrapping + lb3.sizeToFit() + + + CallWaitingView.addSubview(CallWaitingCancelButton!) + CallWaitingView.addSubview(lb1) + CallWaitingView.addSubview(lb2) + CallWaitingView.addSubview(lb3) + CallWaitingCancelButton!.center = CallWaitingView.center + CallWaitingView.bringSubview(toFront: lb1) + CallWaitingView.bringSubview(toFront: lb2) + CallWaitingView.bringSubview(toFront: lb3) + CallWaitingView.bringSubview(toFront: CallWaitingCancelButton!) + + let BigButtonOffset = 30.0 + + CallWaitingView.snp.makeConstraints { make in + make.leading.trailing.bottom.equalToSuperview() + // make.top.equalTo(view.safeAreaLayoutGuide) // --> that leave too much see through space at the top. strange + make.top.equalToSuperview() + } + + CallWaitingCancelButton!.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.greaterThanOrEqualToSuperview().offset(BigButtonOffset) + make.bottom.equalToSuperview().offset(-BigButtonOffset) + } + + lb1.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(60) + make.leading.trailing.equalToSuperview() + } + + lb2.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(lb1.snp.bottom).offset(35) + make.leading.trailing.equalToSuperview() + } + + lb3.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(lb2.snp.bottom).offset(35) + make.leading.trailing.equalToSuperview() + } + + DispatchQueue.global(qos: .userInitiated).async { + + print("cc:call_waiting_bg_queue") + if self.friend != nil { + + DispatchQueue.main.async { + // send a text message to trigger PUSH notification, and make friend come online (hopefully) + // HINT: call OCTSubmanagerChatsImpl.m -> sendMessageToChat() + self.submanagerChats.sendMessage(to: self.chat, text: "calling you", type: .normal, successBlock: nil, failureBlock: nil) + DispatchQueue.main.asyncAfter(deadline: .now() + 10) { + os_log("PUSH:10_seconds") + self.submanagerChats.sendMessagePush(to: self.chat) + } + + self.linearBar.startAnimation(viewToAddto: self.CallWaitingView, viewToAlignToBottomOf: lb3, bottom_margin: 10) + self.CallWaitingView.bringSubview(toFront: self.linearBar) + } + + var connection_status2: ConnectionStatus = .none + + while true { + + DispatchQueue.main.async { + connection_status2 = ConnectionStatus(connectionStatus: friend.connectionStatus) + } + + print("cc:while_friend_not_online, %@", connection_status2) + if (connection_status2 != .none) + { + DispatchQueue.main.async { + print("cc:main_queue") + if (self.callwaiting_running) + { + self.callwaiting_running = false + self.CallWaitingView.removeFromSuperview() + self.linearBar.stopAnimation() + self.delegate?.chatPrivateControllerCallToChat(self, enableVideo: false) + } + } + break + } + + // HINT: sleep for 1 second + if (self.callwaiting_running) + { + sleep(1) + } + else + { + DispatchQueue.main.async { + self.CallWaitingView.removeFromSuperview() + self.linearBar.stopAnimation() + } + break + } + } + print("cc:while_loop_end") + } + } + + } + } else { + print("Call_ERROR:no friend?") + } + } + + @objc func displayalert() { + var alert = UIAlertController(title: "Location Sharing", message: "Would you like to enable location sharing with this contact?\nYou can disable this feature by clicking on the location icon after it has been enabled.", preferredStyle: UIAlertControllerStyle.alert) + var action_title = "Enable" + + if (AppDelegate.location_sharing_contact_pubkey != "-1") + { + alert = UIAlertController(title: "Location Sharing", message: "Disable sharing with this contact " + AppDelegate.location_sharing_contact_pubkey + " ?" , preferredStyle: UIAlertControllerStyle.alert) + action_title = "Disable" + } + + alert.addAction((UIAlertAction(title: action_title, style: .default, handler: { [self] (action) -> Void in + if (action_title == "Disable") + { + AppDelegate.location_sharing_contact_pubkey = "-1" + let locationImage = UIImage(named: "location-call-medium")!.withRenderingMode(.alwaysOriginal) + locationButton.setBackgroundImage(locationImage, for: .normal, barMetrics: .default) + } + else + { + AppDelegate.location_sharing_contact_pubkey = self.friend?.publicKey ?? "-1" + let locationImage = UIImage(named: "location-call-activated-medium")!.withRenderingMode(.alwaysOriginal) + locationButton.setBackgroundImage(locationImage, for: .normal, barMetrics: .default) + + DispatchQueue.global(qos: .userInitiated).async { + + print("ll:location_sharing") + if self.friend != nil { + + while AppDelegate.location_sharing_contact_pubkey != "-1" { + location_manager.requestLocation() + // HINT: sleep for 30 seconds + sleep(30) + } + print("ll:while_loop_end") + } + } + } + alert.dismiss(animated: true, completion: nil) + }))) + + alert.addAction( + UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) -> Void in + alert.dismiss(animated: true, completion: nil) + })) + + self.present(alert, animated: true, completion: nil) + } + + @objc func videoCallButtonPressed() { + delegate?.chatPrivateControllerCallToChat(self, enableVideo: true) + } + + @objc func locationButtonPressed() { + displayalert() + } + + @objc func editMessagesDeleteButtonPressed(_ barButtonItem: UIBarButtonItem) { + guard let selectedRows = tableView?.indexPathsForSelectedRows else { + return + } + + showMessageDeletionConfirmation(messagesCount: selectedRows.count, + showFromItem: barButtonItem, + deleteClosure: { [unowned self] in + self.toggleTableViewEditing(false, animated: true) + + let toRemove = selectedRows.map { + return self.messages[$0.row] + } + + self.submanagerChats.removeMessages(toRemove) + }) + } + + @objc func deleteAllMessagesButtonPressed(_ barButtonItem: UIBarButtonItem) { + toggleTableViewEditing(false, animated: true) + + showMessageDeletionConfirmation(messagesCount: messages.count, + showFromItem: barButtonItem, + deleteClosure: { [unowned self] in + self.submanagerChats.removeAllMessages(in: self.chat, removeChat: false) + }) + } + + @objc func cancelEditingButtonPressed() { + toggleTableViewEditing(false, animated: true) + } +} + +// MARK: Notifications +extension ChatPrivateController { + @objc func applicationDidBecomeActive() { + updateLastReadDate() + } + + @objc func willShowMenuNotification(_ notification: Notification) { + guard let indexPath = selectedMenuIndexPath else { + return + } + guard let cell = tableView?.cellForRow(at: indexPath) else { + return + } + guard let editable = cell as? ChatEditable else { + return + } + guard let menu = notification.object as? UIMenuController else { + return + } + + NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIMenuControllerWillShowMenu, object: nil) + + menu.setMenuVisible(false, animated: false) + + let rect = cell.convert(editable.menuTargetRect(), to: view) + + menu.setTargetRect(rect, in: view) + menu.setMenuVisible(true, animated: true) + + NotificationCenter.default.addObserver( + self, + selector: #selector(ChatPrivateController.willShowMenuNotification(_:)), + name: NSNotification.Name.UIMenuControllerWillShowMenu, + object: nil) + } + + @objc func willHideMenuNotification() { + guard let indexPath = selectedMenuIndexPath else { + return + } + selectedMenuIndexPath = nil + + guard let editable = tableView?.cellForRow(at: indexPath) as? ChatEditable else { + return + } + + editable.willHideMenu() + } +} + +extension ChatPrivateController: UITableViewDataSource { + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let message = messages[indexPath.row] + + // setting default values to avoid crash + var model: ChatMovableDateCellModel = ChatMovableDateCellModel() + var cell: ChatMovableDateCell = tableView.dequeueReusableCell(withIdentifier: ChatMovableDateCell.staticReuseIdentifier) as! ChatMovableDateCell + + if message.isOutgoing() { + if let messageText = message.messageText { + let outgoingModel = ChatOutgoingTextCellModel() + + if (UserDefaultsManager().DebugMode == false) { + outgoingModel.message = messageText.text ?? "" + } else { + let s1 = (messageText.text ?? "") + let s2 = (messageText.msgv3HashHex ?? "") + let s3 = (message.senderUniqueIdentifier ?? "") + let s4 = (message.chatUniqueIdentifier ) + let s5 = String(messageText.isDelivered) + let s6 = String(messageText.sentPush) + let s7 = String(message.tssent) + let s8 = String(message.tsrcvd) + let s9 = String(message.dateInterval) + outgoingModel.message = s1 + "\n" + + "msgv3HashHex:\n" + s2 + "\n" + + "senderUniqueIdentifier:\n" + s3 + "\n" + + "chatUniqueIdentifier:\n" + s4 + "\n" + + "isDelivered:\n" + s5 + "\n" + + "sentPush:\n" + s6 + "\n" + + "tssent:\n" + s7 + "\n" + + "tsrcvd:\n" + s8 + "\n" + + "dateInterval:\n" + s9 + "\n" + } + + outgoingModel.delivered = messageText.isDelivered + outgoingModel.sentpush = messageText.sentPush + + model = outgoingModel + + cell = tableView.dequeueReusableCell(withIdentifier: ChatOutgoingTextCell.staticReuseIdentifier) as! ChatOutgoingTextCell + } + else if let messageCall = message.messageCall { + let outgoingModel = ChatOutgoingCallCellModel() + outgoingModel.callDuration = messageCall.callDuration + outgoingModel.answered = (messageCall.callEvent == .answered) + model = outgoingModel + + cell = tableView.dequeueReusableCell(withIdentifier: ChatOutgoingCallCell.staticReuseIdentifier) as! ChatOutgoingCallCell + } + else if let _ = message.messageFile { + (model, cell) = imageCellWithMessage(message, incoming: false) + } + } + else { + if let messageText = message.messageText { + let incomingModel = ChatBaseTextCellModel() + + if (UserDefaultsManager().DebugMode == false) { + incomingModel.message = messageText.text ?? "" + } else { + let s1 = (messageText.text ?? "") + let s2 = (messageText.msgv3HashHex ?? "") + let s3 = (message.senderUniqueIdentifier ?? "") + let s4 = (message.chatUniqueIdentifier) + let s5 = String(messageText.isDelivered) + let s6 = String(message.tssent) + let s7 = String(message.tsrcvd) + let s8 = String(message.dateInterval) + incomingModel.message = "" + s1 + "\n" + + "msgv3HashHex:\n" + s2 + "\n" + + "senderUniqueIdentifier:\n" + s3 + "\n" + + "chatUniqueIdentifier:\n" + s4 + "\n" + + "isDelivered:\n" + s5 + "\n" + + "tssent:\n" + s6 + "\n" + + "tsrcvd:\n" + s7 + "\n" + + "dateInterval:\n" + s8 + "\n" + } + + model = incomingModel + + cell = tableView.dequeueReusableCell(withIdentifier: ChatIncomingTextCell.staticReuseIdentifier) as! ChatIncomingTextCell + } + else if let messageCall = message.messageCall { + let incomingModel = ChatIncomingCallCellModel() + incomingModel.callDuration = messageCall.callDuration + incomingModel.answered = (messageCall.callEvent == .answered) + model = incomingModel + + cell = tableView.dequeueReusableCell(withIdentifier: ChatIncomingCallCell.staticReuseIdentifier) as! ChatIncomingCallCell + } + else if let _ = message.messageFile { + (model, cell) = imageCellWithMessage(message, incoming: true) + } + } + + var incoming_text_message: Bool = false + if (!message.isOutgoing()) { + if let messageText = message.messageText { + incoming_text_message = true + } + } + + if (incoming_text_message) { + if (message.tssent == 0) { + model.dateString = timeFormatter.string(from: message.date()) + "\n" + dateFormatter.string(from: message.date()) + } else { + let real_datetime = Date(timeIntervalSince1970: TimeInterval(message.tssent)) + model.dateString = timeFormatter.string(from: real_datetime) + "\n" + dateFormatter.string(from: real_datetime) + // os_log("mmm:%s", timeFormatter.string(from: real_datetime)) + } + } else { + model.dateString = timeFormatter.string(from: message.date()) + "\n" + dateFormatter.string(from: message.date()) + } + + cell.delegate = self + cell.setupWithTheme(theme, model: model) + cell.transform = tableView.transform; + + return cell + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return min(visibleMessages, messages.count) + } +} + +extension ChatPrivateController: UITableViewDelegate { + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + if indexPath.row == 0 { + toggleNewMessageView(show: false) + } + + maybeLoadImageForCellAtPath(cell, indexPath: indexPath) + } + + func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool { + guard !tableView.isEditing else { + return false + } + guard let editable = tableView.cellForRow(at: indexPath) as? ChatEditable else { + return false + } + + if !editable.shouldShowMenu() { + return false + } + + selectedMenuIndexPath = indexPath + editable.willShowMenu() + + return true + } + + func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool { + guard let cell = tableView.cellForRow(at: indexPath) as? ChatMovableDateCell else { + return false + } + + return cell.isMenuActionSupportedByCell(action) + } + + func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) { + // Dummy method to make tableView:shouldShowMenuForRowAtIndexPath: work. + } +} + +extension ChatPrivateController: UIScrollViewDelegate { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + guard let tableView = tableView else { + return + } + guard scrollView === tableView else { + return + } + + if tableView.contentOffset.y > (tableView.contentSize.height - tableView.frame.size.height) { + let previous = visibleMessages + + visibleMessages = visibleMessages + Constants.MessagesPortionSize + + if visibleMessages > messages.count { + visibleMessages = messages.count + } + + if visibleMessages != previous { + tableView.reloadData() + } + } + } +} + +extension ChatPrivateController: ChatMovableDateCellDelegate { + func chatMovableDateCellCopyPressed(_ cell: ChatMovableDateCell) { + guard let indexPath = tableView?.indexPath(for: cell) else { + return + } + + let message = messages[indexPath.row] + + if let messageText = message.messageText { + UIPasteboard.general.string = messageText.text + } + else if let _ = message.messageCall { + fatalError("Message call cannot be copied") + } + else if let messageFile = message.messageFile { + guard UTTypeConformsTo(messageFile.fileUTI as CFString? ?? "" as CFString, kUTTypeImage) else { + fatalError("Cannot copy non-image file") + } + guard let file = messageFile.filePath() else { + assertionFailure("Tried to copy non-existing file") + return + } + guard let image = UIImage(contentsOfFile: file) else { + assertionFailure("Cannot create image from file") + return + } + + UIPasteboard.general.image = image + } + } + + func chatMovableDateCellDeletePressed(_ cell: ChatMovableDateCell) { + guard let indexPath = tableView?.indexPath(for: cell) else { + return + } + + let message = messages[indexPath.row] + + submanagerChats.removeMessages([message]) + } + + func chatMovableDateCellMorePressed(_ cell: ChatMovableDateCell) { + toggleTableViewEditing(true, animated: true) + + // TODO select row + // guard let indexPath = tableView?.indexPathForCell(cell) else { + // return + // } + + // tableView?.selectRowAtIndexPath(indexPath, animated: false, scrollPosition: .None) + } +} + +extension ChatPrivateController: UIGestureRecognizerDelegate { + func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + guard let panGR = gestureRecognizer as? UIPanGestureRecognizer else { + return false + } + + let translation = panGR.translation(in: panGR.view) + + return fabsf(Float(translation.x)) > fabsf(Float(translation.y)) + } +} + +private extension ChatPrivateController { + func createNavigationViews() { + titleView = ChatPrivateTitleView(theme: theme) + navigationItem.titleView = titleView + + // create correct navigation buttons + toggleTableViewEditing(false, animated: false) + } + + func createTableView() { + let tableView = UITableView() + self.tableView = tableView + tableView.dataSource = self + tableView.delegate = self + tableView.transform = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: 0) + tableView.scrollsToTop = false + tableView.allowsSelection = false + tableView.estimatedRowHeight = 44.0 + tableView.backgroundColor = theme.colorForType(.NormalBackground) + tableView.allowsMultipleSelectionDuringEditing = true + tableView.separatorStyle = .none + + view.addSubview(tableView) + + tableView.register(ChatMovableDateCell.self, forCellReuseIdentifier: ChatMovableDateCell.staticReuseIdentifier) + tableView.register(ChatIncomingTextCell.self, forCellReuseIdentifier: ChatIncomingTextCell.staticReuseIdentifier) + tableView.register(ChatOutgoingTextCell.self, forCellReuseIdentifier: ChatOutgoingTextCell.staticReuseIdentifier) + tableView.register(ChatIncomingCallCell.self, forCellReuseIdentifier: ChatIncomingCallCell.staticReuseIdentifier) + tableView.register(ChatOutgoingCallCell.self, forCellReuseIdentifier: ChatOutgoingCallCell.staticReuseIdentifier) + tableView.register(ChatIncomingFileCell.self, forCellReuseIdentifier: ChatIncomingFileCell.staticReuseIdentifier) + tableView.register(ChatOutgoingFileCell.self, forCellReuseIdentifier: ChatOutgoingFileCell.staticReuseIdentifier) + + tableViewTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ChatPrivateController.tapOnTableView)) + tableView.addGestureRecognizer(tableViewTapGestureRecognizer) + + let panGR = UIPanGestureRecognizer(target: self, action: #selector(ChatPrivateController.panOnTableView(_:))) + panGR.delegate = self + tableView.addGestureRecognizer(panGR) + } + + func createTableHeaderViews() { + typingHeaderView = ChatTypingHeaderView(theme: theme) + typingHeaderView.transform = tableView!.transform + view.addSubview(typingHeaderView) + + fauxOfflineHeaderView = ChatFauxOfflineHeaderView(theme: theme) + fauxOfflineHeaderView.transform = tableView!.transform + view.addSubview(fauxOfflineHeaderView) + } + + func createNewMessagesView() { + newMessagesView = UIView() + newMessagesView.backgroundColor = theme.colorForType(.ConnectingBackground) + newMessagesView.layer.cornerRadius = 5.0 + newMessagesView.layer.masksToBounds = true + newMessagesView.isHidden = true + view.addSubview(newMessagesView) + + let label = UILabel() + label.text = String(localized: "chat_new_messages") + label.textColor = theme.colorForType(.ConnectingText) + label.backgroundColor = .clear + label.font = UIFont.systemFont(ofSize: 12.0) + newMessagesView.addSubview(label) + + let button = UIButton() + button.addTarget(self, action: #selector(ChatPrivateController.newMessagesViewPressed), for: .touchUpInside) + newMessagesView.addSubview(button) + + label.snp.makeConstraints { + $0.leading.equalTo(newMessagesView).offset(Constants.NewMessageViewEdgesOffset) + $0.trailing.equalTo(newMessagesView).offset(-Constants.NewMessageViewEdgesOffset) + $0.top.equalTo(newMessagesView).offset(Constants.NewMessageViewEdgesOffset) + $0.bottom.equalTo(newMessagesView).offset(-Constants.NewMessageViewEdgesOffset) + } + + button.snp.makeConstraints { + $0.edges.equalTo(newMessagesView) + } + } + + func createInputView() { + chatInputView = ChatInputView(theme: theme) + view.addSubview(chatInputView) + + chatInputViewManager = ChatInputViewManager(inputView: chatInputView, + chat: chat, + submanagerChats: submanagerChats, + submanagerFiles: submanagerFiles, + submanagerObjects: submanagerObjects, + presentingViewController: self) + } + + func createEditMessageToolbar() { + editMessagesToolbar = UIToolbar() + editMessagesToolbar.isHidden = true + editMessagesToolbar.tintColor = theme.colorForType(.LinkText) + editMessagesToolbar.items = [ + UIBarButtonItem(barButtonSystemItem: .trash, target: self, action: #selector(ChatPrivateController.editMessagesDeleteButtonPressed(_:))) + ] + view.addSubview(editMessagesToolbar) + } + + func installConstraints() { + tableView!.snp.makeConstraints { + $0.top.leading.trailing.equalTo(view) + + tableViewToChatInputConstraint = $0.bottom.equalTo(chatInputView.snp.top).constraint + } + + typingHeaderView.snp.makeConstraints { + $0.leading.trailing.equalTo(view) + $0.top.equalTo(tableView!.snp.bottom) + typingViewToChatInputConstraint = $0.bottom.equalTo(chatInputView.snp.top).constraint + } + + typingViewToChatInputConstraint.deactivate() + + newMessagesView.snp.makeConstraints { + $0.centerX.equalTo(tableView!) + newMessageViewTopConstraint = $0.top.equalTo(chatInputView.snp.top).constraint + } + + chatInputView.snp.makeConstraints { + $0.leading.trailing.equalTo(view) + $0.top.greaterThanOrEqualTo(view).offset(Constants.InputViewTopOffset) + chatInputViewBottomConstraint = $0.bottom.equalTo(view).constraint + // TODO: this moves the input view a bit more to the top, because the home button "line" is in the way otherwise + // please fix me properly in the future + if #available(iOS 11.0, *) { + let keyWindow = UIApplication.shared.keyWindow + let b = keyWindow?.safeAreaInsets.bottom + chatInputViewBottomConstraint?.update(offset: -(b ?? 20)) + } + } + + editMessagesToolbar.snp.makeConstraints { + $0.edges.equalTo(chatInputView) + } + } + + func addMessagesNotification() { + self.messagesToken = messages.addNotificationBlock { [unowned self] change in + guard let tableView = self.tableView else { + return + } + switch change { + case .initial: + break + case .update(_, let deletions, let insertions, let modifications): + + // TODO: this is a very bad workaround. when more than 1 message is incoming + // those would crash. + /* + tableView.beginUpdates() + self.updateTableViewWithDeletions(deletions) + self.updateTableViewWithInsertions(insertions) + self.updateTableViewWithModifications(modifications) + + self.visibleMessages = self.visibleMessages + insertions.count - deletions.count + tableView.endUpdates() + */ + // now just reload the whole table to avoid the crash, until there is a fix + tableView.reloadData() + + self.updateTableHeaderView() + + if insertions.contains(0) { + self.handleNewMessage() + } + case .error(let error): + fatalError("\(error)") + } + } + } + + func updateTableViewWithDeletions(_ deletions: [Int]) { + guard let tableView = tableView else { + return + } + + for index in deletions { + if index >= visibleMessages { + continue + } + let indexPath = IndexPath(row: index, section: 0) + tableView.deleteRows(at: [indexPath], with: .top) + } + } + + func updateTableViewWithInsertions(_ insertions: [Int]) { + guard let tableView = tableView else { + return + } + + for index in insertions { + if index >= visibleMessages { + continue + } + let indexPath = IndexPath(row: index, section: 0) + tableView.insertRows(at: [indexPath], with: .top) + } + } + + func updateTableViewWithModifications(_ modifications: [Int]) { + guard let tableView = tableView else { + return + } + + for index in modifications { + if index >= visibleMessages { + continue + } + let message = messages[index] + let indexPath = IndexPath(row: index, section: 0) + + if message.messageFile == nil { + tableView.reloadRows(at: [indexPath], with: .none) + continue + } + + guard let cell = tableView.cellForRow(at: indexPath) as? ChatGenericFileCell else { + continue + } + + let model = ChatIncomingFileCellModel() + prepareFileCell(cell, andModel: model, withMessage: message) + cell.setupWithTheme(theme, model: model) + + maybeLoadImageForCellAtPath(cell, indexPath: indexPath) + } + } + + func addFriendNotification() { + guard let friend = self.friend else { + titleView.name = String(localized: "contact_deleted") + titleView.userStatus = UserStatus(connectionStatus: .none, userStatus: .none) + titleView.connectionStatus = ConnectionStatus(connectionStatus: .none) + audioButton.isEnabled = true + videoButton.isEnabled = false + locationButton.isEnabled = true + chatInputView.cameraButtonEnabled = false + return + } + + titleView.name = friend.nickname + titleView.userStatus = UserStatus(connectionStatus: friend.connectionStatus, userStatus: friend.status) + titleView.connectionStatus = ConnectionStatus(connectionStatus: friend.connectionStatus) + + let predicate = NSPredicate(format: "uniqueIdentifier == %@", friend.uniqueIdentifier) + let results = submanagerObjects.friends(predicate: predicate) + + friendToken = results.addNotificationBlock { [unowned self] change in + guard let friend = self.friend else { + return + } + + switch change { + case .initial: + fallthrough + case .update: + self.titleView.name = friend.nickname + self.titleView.userStatus = UserStatus(connectionStatus: friend.connectionStatus, userStatus: friend.status) + self.titleView.connectionStatus = ConnectionStatus(connectionStatus: friend.connectionStatus) + + let isConnected = friend.isConnected + + self.audioButton.isEnabled = true + self.videoButton.isEnabled = isConnected + self.locationButton.isEnabled = true + self.chatInputView.cameraButtonEnabled = isConnected + + self.updateTableHeaderView() + case .error(let error): + fatalError("\(error)") + } + } + } + + func updateTableHeaderView() { + guard let tableView = tableView else { + return + } + + guard let friend = friend else { + // tableView.tableHeaderView = nil + return + } + + UIView.animate(withDuration: Constants.NewMessageViewAnimationDuration, animations: { + if friend.isConnected { + if friend.isTyping { + self.tableViewToChatInputConstraint.deactivate() + self.typingViewToChatInputConstraint.activate() + self.typingHeaderView.isHidden = false + self.typingHeaderView.startAnimation() + } + else { + self.tableViewToChatInputConstraint.activate() + self.typingViewToChatInputConstraint.deactivate() + self.typingHeaderView.isHidden = true + self.typingHeaderView.stopAnimation() + } + + self.view.layoutIfNeeded() + } + else { + // let predicate = NSPredicate(format: "messageText.isDelivered == NO AND senderUniqueIdentifier == nil") + // let hasUnsendMessages = self.messages.objects(with: predicate).count > 0 + + // tableView.tableHeaderView = hasUnsendMessages ? self.fauxOfflineHeaderView : nil + } + }) + + updateTableHeaderViewLayout() + } + + func updateTableHeaderViewLayout() { + guard let headerView = tableView?.tableHeaderView else { + return + } + + headerView.setNeedsLayout() + headerView.layoutIfNeeded() + let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height + headerView.frame.size.height = height + tableView?.tableHeaderView = headerView + } + + func updateInputViewMaxHeight() { + chatInputView.maxHeight = chatInputView.frame.maxY - Constants.InputViewTopOffset + } + + func handleNewMessage() { + if UIApplication.isActive { + updateLastReadDate() + } + + guard let tableView = tableView else { + return + } + guard let visible = tableView.indexPathsForVisibleRows else { + return + } + + let first = IndexPath(row: 0, section: 0) + + if !visible.contains(first) { + toggleNewMessageView(show: true) + } + } + + func toggleNewMessageView(show: Bool) { + guard show != newMessagesViewVisible else { + return + } + newMessagesViewVisible = show + + if show { + newMessagesView.isHidden = false + } + + UIView.animate(withDuration: Constants.NewMessageViewAnimationDuration, animations: { + if show { + self.newMessageViewTopConstraint?.update(offset: Constants.NewMessageViewTopOffset - self.newMessagesView.frame.size.height) + } + else { + self.newMessageViewTopConstraint?.update(offset: 0.0) + } + + self.view.layoutIfNeeded() + + }, completion: { finished in + if !show { + self.newMessagesView.isHidden = true + } + }) + } + + func updateLastReadDate() { + submanagerObjects.change(chat, lastReadDateInterval: Date().timeIntervalSince1970) + } + + func imageCellWithMessage(_ message: OCTMessageAbstract, incoming: Bool) -> (ChatMovableDateCellModel, ChatMovableDateCell) { + let cell: ChatGenericFileCell + + if incoming { + cell = tableView!.dequeueReusableCell(withIdentifier: ChatIncomingFileCell.staticReuseIdentifier) as! ChatIncomingFileCell + } + else { + cell = tableView!.dequeueReusableCell(withIdentifier: ChatOutgoingFileCell.staticReuseIdentifier) as! ChatOutgoingFileCell + } + let model = ChatIncomingFileCellModel() + + prepareFileCell(cell, andModel: model, withMessage: message) + + return (model, cell) + } + + func prepareFileCell(_ cell: ChatGenericFileCell, andModel model: ChatGenericFileCellModel, withMessage message: OCTMessageAbstract) { + cell.progressObject = nil + + model.fileName = message.messageFile!.fileName + model.fileSize = ByteCountFormatter.string(fromByteCount: message.messageFile!.fileSize, countStyle: .file) + model.fileUTI = message.messageFile!.fileUTI + + switch message.messageFile!.fileType { + case .waitingConfirmation: + model.state = .waitingConfirmation + case .loading: + model.state = .loading + + let bridge = ChatProgressBridge() + cell.progressObject = bridge + _ = try? self.submanagerFiles.add(bridge, forFileTransfer: message) + case .paused: + model.state = .paused + case .canceled: + model.state = .cancelled + case .ready: + model.state = .done + } + + if !message.isOutgoing() { + model.startLoadingHandle = { [weak self] in + self?.submanagerFiles.acceptFileTransfer(message) { (error: Error) -> Void in + handleErrorWithType(.acceptIncomingFile, error: error as NSError) + } + } + } + + model.cancelHandle = { [weak self] in + do { + try self?.submanagerFiles.cancelFileTransfer(message) + } + catch let error as NSError { + handleErrorWithType(.cancelFileTransfer, error: error) + } + } + + model.retryHandle = { [weak self] in + self?.submanagerFiles.retrySendingFile(message) { (error: Error) in + handleErrorWithType(.sendFileToFriend, error: error as NSError) + } + } + + model.pauseOrResumeHandle = { [weak self] in + let isPaused = (message.messageFile!.pausedBy.rawValue & OCTMessageFilePausedBy.user.rawValue) != 0 + + do { + try self?.submanagerFiles.pauseFileTransfer(!isPaused, message: message) + } + catch let error as NSError { + handleErrorWithType(.cancelFileTransfer, error: error) + } + } + + model.openHandle = { [weak self] in + guard let sself = self else { + return + } + let qlDataSource = FilePreviewControllerDataSource(chat: sself.chat, submanagerObjects: sself.submanagerObjects) + guard let index = qlDataSource.indexOfMessage(message) else { + return + } + + sself.delegate?.chatPrivateControllerShowQuickLookController(sself, dataSource: qlDataSource, selectedIndex: index) + } + } + + func maybeLoadImageForCellAtPath(_ cell: UITableViewCell, indexPath: IndexPath) { + let message = messages[indexPath.row] + + guard let messageFile = message.messageFile else { + return + } + + guard UTTypeConformsTo(messageFile.fileUTI as CFString? ?? "" as CFString, kUTTypeImage) else { + return + } + + guard let file = messageFile.filePath() else { + return + } + + if messageFile.fileSize >= Constants.MaxImageSizeToShowInline { + return + } + + if let image = imageCache.object(forKey: file as AnyObject) as? UIImage { + let cell = (cell as? ChatIncomingFileCell) ?? (cell as? ChatOutgoingFileCell) + + cell?.setButtonImage(image) + } + else { + loadImageForCellAtIndexPath(indexPath, fromFile: file) + } + } + + func loadImageForCellAtIndexPath(_ indexPath: IndexPath, fromFile: String) { + DispatchQueue.global(qos: .default).async { [weak self] in + guard var image = UIImage(contentsOfFile: fromFile) else { + return + } + + var size = image.size + guard size.width > 0 || size.height > 0 else { + return + } + + let delta = (size.width > size.height) ? (Constants.MaxInlineImageSide / size.width) : (Constants.MaxInlineImageSide / size.height) + + size.width *= delta + size.height *= delta + + image = image.scaleToSize(size) + self?.imageCache.setObject(image, forKey: fromFile as AnyObject) + + DispatchQueue.main.async { + let optionalCell = self?.tableView?.cellForRow(at: indexPath) + guard let cell = (optionalCell as? ChatIncomingFileCell) ?? (optionalCell as? ChatOutgoingFileCell) else { + return + } + + cell.setButtonImage(image) + } + } + } + + func toggleTableViewEditing(_ editing: Bool, animated: Bool) { + tableView?.setEditing(editing, animated: animated) + + tableViewTapGestureRecognizer.isEnabled = !editing + editMessagesToolbar.isHidden = !editing + + if editing { + _ = chatInputView.resignFirstResponder() + + navigationItem.leftBarButtonItems = [UIBarButtonItem( + title: String(localized: "delete_all_messages"), + style: .plain, + target: self, + action: #selector(ChatPrivateController.deleteAllMessagesButtonPressed(_:)))] + + navigationItem.rightBarButtonItems = [UIBarButtonItem( + barButtonSystemItem: .cancel, + target: self, + action: #selector(ChatPrivateController.cancelEditingButtonPressed))] + } + else { + let audioImage = UIImage(named: "start-call-medium")! + let videoImage = UIImage(named: "video-call-medium")! + let locationImage = UIImage(named: "location-call-medium")!.withRenderingMode(.alwaysOriginal) + audioButton = UIBarButtonItem(image: audioImage, style: .plain, target: self, action: #selector(ChatPrivateController.audioCallButtonPressed)) + videoButton = UIBarButtonItem(image: videoImage, style: .plain, target: self, action: #selector(ChatPrivateController.videoCallButtonPressed)) + locationButton = UIBarButtonItem(image: locationImage, style: .plain, target: self, action: #selector(ChatPrivateController.locationButtonPressed)) + + if (AppDelegate.location_sharing_contact_pubkey != "-1") { + let locationImage = UIImage(named: "location-call-activated-medium")!.withRenderingMode(.alwaysOriginal) + locationButton.setBackgroundImage(locationImage, for: .normal, barMetrics: .default) + } + + navigationItem.leftBarButtonItems = nil + navigationItem.rightBarButtonItems = [ + videoButton, + audioButton, + locationButton + ] + } + } + + func showMessageDeletionConfirmation(messagesCount: Int, + showFromItem barButtonItem: UIBarButtonItem, + deleteClosure: @escaping () -> Void) { + let deleteButtonText = messagesCount > 1 ? + String(localized: "delete_multiple_messages") + " (\(messagesCount))" : + String(localized: "delete_single_message") + + let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + alert.popoverPresentationController?.barButtonItem = barButtonItem + + alert.addAction(UIAlertAction(title: deleteButtonText, style: .destructive) { _ -> Void in + deleteClosure() + }) + + alert.addAction(UIAlertAction(title: String(localized: "alert_cancel"), style: .cancel, handler: nil)) + + present(alert, animated: true, completion: nil) + } +} diff --git a/Antidote/ChatPrivateTitleView.swift b/Antidote/ChatPrivateTitleView.swift new file mode 100644 index 0000000..34e9861 --- /dev/null +++ b/Antidote/ChatPrivateTitleView.swift @@ -0,0 +1,111 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let StatusViewLeftOffset: CGFloat = 5.0 + static let StatusViewSize: CGFloat = 10.0 +} + +class ChatPrivateTitleView: UIView { + var name: String { + get { + return nameLabel.text ?? "" + } + set { + nameLabel.text = newValue + + updateFrame() + } + } + + var userStatus: UserStatus { + get { + return statusView.userStatus + } + set { + statusView.userStatus = newValue + statusLabel.text = newValue.toString() + + updateFrame() + } + } + + var connectionStatus: ConnectionStatus { + get { + return statusView.connectionStatus + } + set { + statusView.connectionStatus = newValue + + updateFrame() + } + } + + fileprivate var nameLabel: UILabel! + fileprivate var statusView: UserStatusView! + fileprivate var statusLabel: UILabel! + + init(theme: Theme) { + super.init(frame: CGRect.zero) + + backgroundColor = .clear + + createViews(theme) + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private extension ChatPrivateTitleView { + func createViews(_ theme: Theme) { + nameLabel = UILabel() + nameLabel.textAlignment = .center + nameLabel.textColor = theme.colorForType(.NormalText) + nameLabel.font = UIFont.antidoteFontWithSize(16.0, weight: .bold) + addSubview(nameLabel) + + statusView = UserStatusView() + statusView.showExternalCircle = false + statusView.theme = theme + addSubview(statusView) + + statusLabel = UILabel() + statusLabel.textAlignment = .center + statusLabel.textColor = theme.colorForType(.NormalText) + statusLabel.font = UIFont.antidoteFontWithSize(12.0, weight: .light) + addSubview(statusLabel) + + nameLabel.snp.makeConstraints { + $0.top.equalTo(self) + $0.leading.equalTo(self) + } + + statusView.snp.makeConstraints { + $0.centerY.equalTo(nameLabel) + $0.leading.equalTo(nameLabel.snp.trailing).offset(Constants.StatusViewLeftOffset) + $0.trailing.equalTo(self) + $0.size.equalTo(Constants.StatusViewSize) + } + + statusLabel.snp.makeConstraints { + $0.top.equalTo(nameLabel.snp.bottom) + $0.leading.equalTo(nameLabel) + $0.trailing.equalTo(nameLabel) + $0.bottom.equalTo(self) + } + } + + func updateFrame() { + nameLabel.sizeToFit() + statusLabel.sizeToFit() + + frame.size.width = max(nameLabel.frame.size.width, statusLabel.frame.size.width) + Constants.StatusViewLeftOffset + Constants.StatusViewSize + frame.size.height = nameLabel.frame.size.height + statusLabel.frame.size.height + } +} diff --git a/Antidote/ChatProgressBridge.swift b/Antidote/ChatProgressBridge.swift new file mode 100644 index 0000000..d6ab275 --- /dev/null +++ b/Antidote/ChatProgressBridge.swift @@ -0,0 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +/** + Bridge between objcTox subscriber and chat progress protocol. + */ +class ChatProgressBridge: NSObject, ChatProgressProtocol { + var updateProgress: ((_ progress: Float) -> Void)? + var updateEta: ((_ eta: CFTimeInterval, _ bytesPerSecond: OCTToxFileSize) -> Void)? +} + +extension ChatProgressBridge: OCTSubmanagerFilesProgressSubscriber { + func submanagerFiles(onProgressUpdate progress: Float, message: OCTMessageAbstract) { + updateProgress?(progress) + } + + func submanagerFiles(onEtaUpdate eta: CFTimeInterval, bytesPerSecond: OCTToxFileSize, message: OCTMessageAbstract) { + updateEta?(eta, bytesPerSecond) + } +} diff --git a/Antidote/ChatProgressProtocol.swift b/Antidote/ChatProgressProtocol.swift new file mode 100644 index 0000000..7bfa58b --- /dev/null +++ b/Antidote/ChatProgressProtocol.swift @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +protocol ChatProgressProtocol { + var updateProgress: ((_ progress: Float) -> Void)? { get set } + var updateEta: ((_ eta: CFTimeInterval, _ bytesPerSecond: OCTToxFileSize) -> Void)? { get set } +} diff --git a/Antidote/ChatTypingHeaderView.swift b/Antidote/ChatTypingHeaderView.swift new file mode 100644 index 0000000..bd999c9 --- /dev/null +++ b/Antidote/ChatTypingHeaderView.swift @@ -0,0 +1,101 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import SnapKit + +fileprivate struct Constants { + static let verticalOffset = 7.0 + static let horizontalOffset = 20.0 + + static let animationStepDuration = 0.7 +} + +class ChatTypingHeaderView: UIView { + fileprivate let theme: Theme + + fileprivate var bubbleView: BubbleView! + fileprivate var label: UILabel! + + fileprivate var animationTimer: Timer? + fileprivate var animationStep: Int = 0 + + init(theme: Theme) { + self.theme = theme + + super.init(frame: CGRect.zero) + + createViews() + installConstraints() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func startAnimation() { + animationTimer = Timer.scheduledTimer(timeInterval: Constants.animationStepDuration, closure: {[weak self] _ -> Void in + guard let sself = self else { + return + } + + sself.animationStep += 1 + if sself.animationStep > 2 { + sself.animationStep = 0 + } + + sself.updateDotsString() + }, repeats: true) + } + + func stopAnimation() { + animationTimer?.invalidate() + animationTimer = nil + } + +} + +private extension ChatTypingHeaderView { + func createViews() { + bubbleView = BubbleView() + bubbleView.text = " " + bubbleView.backgroundColor = theme.colorForType(.ChatIncomingBubble) + bubbleView.isUserInteractionEnabled = false + addSubview(bubbleView) + + label = UILabel() + addSubview(label) + + updateDotsString() + } + + func installConstraints() { + bubbleView.snp.makeConstraints() { + $0.top.equalTo(self).offset(Constants.verticalOffset) + $0.bottom.equalTo(self).offset(-Constants.verticalOffset) + $0.leading.equalTo(self).offset(Constants.horizontalOffset) + } + + label.snp.makeConstraints() { + $0.center.equalTo(bubbleView) + } + } + + func updateDotsString() { + let mutable = NSMutableAttributedString() + let font = UIFont.systemFont(ofSize: 22.0) + let colorNormal = theme.colorForType(.ChatInformationText) + let colorSelected = colorNormal.darkerColor().darkerColor() + + for i in 0..<3 { + let color = (i == animationStep) ? colorSelected : colorNormal + // HINT: u{2022} --> UTF-8 BULLET --> e2 80 a2 + mutable.append(NSMutableAttributedString(string: "\u{2022}", + attributes: [NSAttributedStringKey.font : font, + NSAttributedStringKey.foregroundColor: color])) + } + + label.attributedText = mutable + } +} diff --git a/Antidote/ChatsTabCoordinator.swift b/Antidote/ChatsTabCoordinator.swift new file mode 100644 index 0000000..c37fa7c --- /dev/null +++ b/Antidote/ChatsTabCoordinator.swift @@ -0,0 +1,94 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +protocol ChatsTabCoordinatorDelegate: class { + func chatsTabCoordinator(_ coordinator: ChatsTabCoordinator, chatWillAppear chat: OCTChat) + func chatsTabCoordinator(_ coordinator: ChatsTabCoordinator, chatWillDisapper chat: OCTChat) + func chatsTabCoordinator(_ coordinator: ChatsTabCoordinator, callToChat chat: OCTChat, enableVideo: Bool) +} + +class ChatsTabCoordinator: ActiveSessionNavigationCoordinator { + weak var delegate: ChatsTabCoordinatorDelegate? + + fileprivate weak var submanagerObjects: OCTSubmanagerObjects! + fileprivate weak var submanagerChats: OCTSubmanagerChats! + fileprivate weak var submanagerFiles: OCTSubmanagerFiles! + + init(theme: Theme, submanagerObjects: OCTSubmanagerObjects, submanagerChats: OCTSubmanagerChats, submanagerFiles: OCTSubmanagerFiles) { + self.submanagerObjects = submanagerObjects + self.submanagerChats = submanagerChats + self.submanagerFiles = submanagerFiles + + super.init(theme: theme) + } + + override func startWithOptions(_ options: CoordinatorOptions?) { + let controller = ChatListController(theme: theme, submanagerChats: submanagerChats, submanagerObjects: submanagerObjects) + controller.delegate = self + + navigationController.pushViewController(controller, animated: false) + } + + func showChat(_ chat: OCTChat, animated: Bool) { + if let top = navigationController.topViewController as? ChatPrivateController { + if top.chat == chat { + // controller is already visible + return + } + } + + let controller = ChatPrivateController( + theme: theme, + chat: chat, + submanagerChats: submanagerChats, + submanagerObjects: submanagerObjects, + submanagerFiles: submanagerFiles, + delegate: self) + + navigationController.popToRootViewController(animated: false) + navigationController.pushViewController(controller, animated: animated) + } + + /** + Returns active chat controller if it is visible, nil otherwise. + */ + func activeChatController() -> ChatPrivateController? { + return navigationController.topViewController as? ChatPrivateController + } +} + +extension ChatsTabCoordinator: ChatListControllerDelegate { + func chatListController(_ controller: ChatListController, didSelectChat chat: OCTChat) { + showChat(chat, animated: true) + } +} + +extension ChatsTabCoordinator: ChatPrivateControllerDelegate { + func chatPrivateControllerWillAppear(_ controller: ChatPrivateController) { + delegate?.chatsTabCoordinator(self, chatWillAppear: controller.chat) + } + + func chatPrivateControllerWillDisappear(_ controller: ChatPrivateController) { + delegate?.chatsTabCoordinator(self, chatWillDisapper: controller.chat) + } + + func chatPrivateControllerCallToChat(_ controller: ChatPrivateController, enableVideo: Bool) { + delegate?.chatsTabCoordinator(self, callToChat: controller.chat, enableVideo: enableVideo) + } + + func chatPrivateControllerShowQuickLookController( + _ controller: ChatPrivateController, + dataSource: QuickLookPreviewControllerDataSource, + selectedIndex: Int) + { + let controller = QuickLookPreviewController() + controller.dataSource = dataSource + controller.dataSourceStorage = dataSource + controller.currentPreviewItemIndex = selectedIndex + + navigationController.present(controller, animated: true, completion: nil) + } +} diff --git a/Antidote/ConnectionStatus.swift b/Antidote/ConnectionStatus.swift new file mode 100644 index 0000000..82323f4 --- /dev/null +++ b/Antidote/ConnectionStatus.swift @@ -0,0 +1,24 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +enum ConnectionStatus { + case none + case tcp + case udp + + init(connectionStatus: OCTToxConnectionStatus) { + switch (connectionStatus) { + case (.none): + self = .none + case (.TCP): + self = .tcp + case (.UDP): + self = .udp + default: + self = .none + } + } +} diff --git a/Antidote/CoordinatorProtocol.swift b/Antidote/CoordinatorProtocol.swift new file mode 100644 index 0000000..ecb8c9b --- /dev/null +++ b/Antidote/CoordinatorProtocol.swift @@ -0,0 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +typealias CoordinatorOptions = [String: Any] + +protocol CoordinatorProtocol { + /** + This method will be called when coordinator should start working. + + - Parameters: + - options: Options to start with. Options are used for recovering state of coordinator on recreation. + */ + func startWithOptions(_ options: CoordinatorOptions?) +} diff --git a/Antidote/CopyLabel.swift b/Antidote/CopyLabel.swift new file mode 100644 index 0000000..d77596a --- /dev/null +++ b/Antidote/CopyLabel.swift @@ -0,0 +1,58 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +class CopyLabel: UILabel { + var copyable = true { + didSet { + recognizer.isEnabled = copyable + } + } + + fileprivate var recognizer: UITapGestureRecognizer! + + override init(frame: CGRect) { + super.init(frame: frame) + + isUserInteractionEnabled = true + + recognizer = UITapGestureRecognizer(target: self, action: #selector(CopyLabel.tapGesture)) + addGestureRecognizer(recognizer) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: Actions +extension CopyLabel { + @objc func tapGesture() { + // This fixes issue with calling UIMenuController after UIActionSheet was presented. + let appDelegate = UIApplication.shared.delegate as! AppDelegate + appDelegate.window?.makeKey() + + becomeFirstResponder() + + let menu = UIMenuController.shared + menu.setTargetRect(frame, in: superview!) + menu.setMenuVisible(true, animated: true) + } +} + +// MARK: Copying +extension CopyLabel { + override func copy(_ sender: Any?) { + UIPasteboard.general.string = text + } + + override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + return action == #selector(copy(_:)) + } + + override var canBecomeFirstResponder : Bool { + return true + } +} diff --git a/Antidote/EnterPinController.swift b/Antidote/EnterPinController.swift new file mode 100644 index 0000000..19ab64e --- /dev/null +++ b/Antidote/EnterPinController.swift @@ -0,0 +1,148 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import SnapKit + +private struct Constants { + static let PinLength = 4 +} + +protocol EnterPinControllerDelegate: class { + func enterPinController(_ controller: EnterPinController, successWithPin pin: String) + + // This method may be called only for ValidatePin state. + func enterPinControllerFailure(_ controller: EnterPinController) +} + +class EnterPinController: UIViewController { + enum State { + case validatePin(validPin: String) + case setPin + } + + weak var delegate: EnterPinControllerDelegate? + + let state: State + + var topText: String { + get { + return pinInputView.topText + } + set { + customLoadView() + pinInputView.topText = newValue + } + } + + var descriptionText: String? { + get { + return pinInputView.descriptionText + } + set { + customLoadView() + pinInputView.descriptionText = newValue + } + } + + fileprivate let theme: Theme + + fileprivate var pinInputView: PinInputView! + + fileprivate var enteredString: String = "" + + init(theme: Theme, state: State) { + self.theme = theme + self.state = state + + super.init(nibName: nil, bundle: nil) + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + createViews() + installConstraints() + + pinInputView.applyColors() + } + + override var shouldAutorotate : Bool { + return false + } + + override var supportedInterfaceOrientations : UIInterfaceOrientationMask { + return UIInterfaceOrientationMask.portrait + } + + func resetEnteredPin() { + enteredString = "" + pinInputView.enteredNumbersCount = enteredString.count + } +} + +extension EnterPinController: PinInputViewDelegate { + func pinInputView(_ view: PinInputView, numericButtonPressed i: Int) { + guard enteredString.count < Constants.PinLength else { + return + } + + enteredString += "\(i)" + pinInputView.enteredNumbersCount = enteredString.count + + if enteredString.count == Constants.PinLength { + switch state { + case .validatePin(let validPin): + if enteredString == validPin { + delegate?.enterPinController(self, successWithPin: enteredString) + } + else { + delegate?.enterPinControllerFailure(self) + } + case .setPin: + delegate?.enterPinController(self, successWithPin: enteredString) + } + } + } + + func pinInputViewDeleteButtonPressed(_ view: PinInputView) { + guard enteredString.count > 0 else { + return + } + + enteredString = String(enteredString.dropLast()) + view.enteredNumbersCount = enteredString.count + } +} + +private extension EnterPinController { + func createViews() { + pinInputView = PinInputView(pinLength: Constants.PinLength, + topColor: theme.colorForType(.LockGradientTop), + bottomColor: theme.colorForType(.LockGradientBottom)) + pinInputView.delegate = self + view.addSubview(pinInputView) + } + + func installConstraints() { + pinInputView.snp.makeConstraints { + $0.center.equalTo(view) + } + } + + func customLoadView() { + if #available(iOS 9.0, *) { + loadViewIfNeeded() + } else { + if !isViewLoaded { + // creating view + view.setNeedsDisplay() + } + } + } +} diff --git a/Antidote/ErrorHandling.swift b/Antidote/ErrorHandling.swift new file mode 100644 index 0000000..f4b199e --- /dev/null +++ b/Antidote/ErrorHandling.swift @@ -0,0 +1,337 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +enum ErrorHandlerType { + case cannotLoadHTML + case createOCTManager + case toxSetInfoCodeName + case toxSetInfoCodeStatusMessage + case toxAddFriend + case removeFriend + case callToChat + case exportProfile + case deleteProfile + case passwordIsEmpty + case wrongOldPassword + case passwordsDoNotMatch + case answerCall + case routeAudioToSpeaker + case enableVideoSending + case callSwitchCamera + case convertImageToPNG + case changeAvatar + case sendFileToFriend + case acceptIncomingFile + case cancelFileTransfer + case pauseFileTransfer + case pinLogOut +} + + +/** + Show alert for given error. + + - Parameters: + - type: Type of error to handle. + - error: Optional erro to get code from. + - retryBlock: If set user will be asked to retry request once again. + */ +func handleErrorWithType(_ type: ErrorHandlerType, error: NSError? = nil, retryBlock: (() -> Void)? = nil) { + switch type { + case .cannotLoadHTML: + UIAlertController.showErrorWithMessage(String(localized: "error_internal_message"), retryBlock: retryBlock) + case .createOCTManager: + let (title, message) = OCTManagerInitError(rawValue: error!.code)!.strings() + UIAlertController.showWithTitle(title, message: message, retryBlock: retryBlock) + case .toxSetInfoCodeName: + let (title, message) = OCTToxErrorSetInfoCode(rawValue: error!.code)!.nameStrings() + UIAlertController.showWithTitle(title, message: message, retryBlock: retryBlock) + case .toxSetInfoCodeStatusMessage: + let (title, message) = OCTToxErrorSetInfoCode(rawValue: error!.code)!.statusMessageStrings() + UIAlertController.showWithTitle(title, message: message, retryBlock: retryBlock) + case .toxAddFriend: + let (title, message) = OCTToxErrorFriendAdd(rawValue: error!.code)!.strings() + UIAlertController.showWithTitle(title, message: message, retryBlock: retryBlock) + case .removeFriend: + let (title, message) = OCTToxErrorFriendDelete(rawValue: error!.code)!.strings() + UIAlertController.showWithTitle(title, message: message, retryBlock: retryBlock) + case .callToChat: + let (title, message) = OCTToxAVErrorCall(rawValue: error!.code)!.strings() + UIAlertController.showWithTitle(title, message: message, retryBlock: retryBlock) + case .exportProfile: + UIAlertController.showWithTitle(String(localized: "error_title"), message: error!.localizedDescription, retryBlock: retryBlock) + case .deleteProfile: + UIAlertController.showWithTitle(String(localized: "error_title"), message: error!.localizedDescription, retryBlock: retryBlock) + case .passwordIsEmpty: + UIAlertController.showWithTitle(String(localized: "password_is_empty_error"), retryBlock: retryBlock) + case .wrongOldPassword: + UIAlertController.showWithTitle(String(localized: "wrong_old_password"), retryBlock: retryBlock) + case .passwordsDoNotMatch: + UIAlertController.showWithTitle(String(localized: "passwords_do_not_match"), retryBlock: retryBlock) + case .answerCall: + let (title, message) = OCTToxAVErrorAnswer(rawValue: error!.code)!.strings() + UIAlertController.showWithTitle(title, message: message, retryBlock: retryBlock) + case .routeAudioToSpeaker: + UIAlertController.showWithTitle(String(localized: "error_title"), message: String(localized: "error_internal_message"), retryBlock: retryBlock) + case .enableVideoSending: + UIAlertController.showWithTitle(String(localized: "error_title"), message: String(localized: "error_internal_message"), retryBlock: retryBlock) + case .callSwitchCamera: + UIAlertController.showWithTitle(String(localized: "error_title"), message: String(localized: "error_internal_message"), retryBlock: retryBlock) + case .convertImageToPNG: + UIAlertController.showWithTitle(String(localized: "error_title"), message: String(localized: "change_avatar_error_convert_image"), retryBlock: retryBlock) + case .changeAvatar: + let (title, message) = OCTSetUserAvatarError(rawValue: error!.code)!.strings() + UIAlertController.showWithTitle(title, message: message, retryBlock: retryBlock) + case .sendFileToFriend: + let (title, message) = OCTSendFileError(rawValue: error!.code)!.strings() + UIAlertController.showWithTitle(title, message: message, retryBlock: retryBlock) + case .acceptIncomingFile: + let (title, message) = OCTAcceptFileError(rawValue: error!.code)!.strings() + UIAlertController.showWithTitle(title, message: message, retryBlock: retryBlock) + case .cancelFileTransfer: + let (title, message) = OCTFileTransferError(rawValue: error!.code)!.strings() + UIAlertController.showWithTitle(title, message: message, retryBlock: retryBlock) + case .pauseFileTransfer: + let (title, message) = OCTFileTransferError(rawValue: error!.code)!.strings() + UIAlertController.showWithTitle(title, message: message, retryBlock: retryBlock) + case .pinLogOut: + UIAlertController.showWithTitle(String(localized: "error_title"), message: String(localized: "pin_logout_message"), retryBlock: retryBlock) + } +} + +extension OCTManagerInitError { + func strings() -> (title: String, message: String) { + switch self { + case .passphraseFailed: + return (String(localized: "error_wrong_password_title"), + String(localized: "error_wrong_password_message")) + case .cannotImportToxSave: + return (String(localized: "error_import_not_exist_title"), + String(localized: "error_import_not_exist_message")) + case .databaseKeyCannotCreateKey: + fallthrough + case .databaseKeyCannotReadKey: + fallthrough + case .databaseKeyMigrationToEncryptedFailed: + return (String(localized: "error_title"), + String(localized: "error_internal_message")) + case .toxFileDecryptNull: + fallthrough + case .databaseKeyDecryptNull: + return (String(localized: "error_decrypt_title"), + String(localized: "error_decrypt_empty_data_message")) + case .toxFileDecryptBadFormat: + fallthrough + case .databaseKeyDecryptBadFormat: + return (String(localized: "error_decrypt_title"), + String(localized: "error_decrypt_bad_format_message")) + case .toxFileDecryptFailed: + fallthrough + case .databaseKeyDecryptFailed: + return (String(localized: "error_decrypt_title"), + String(localized: "error_decrypt_wrong_password_message")) + case .createToxUnknown: + return (String(localized: "error_title"), + String(localized: "error_general_unknown_message")) + case .createToxMemoryError: + return (String(localized: "error_title"), + String(localized: "error_general_no_memory_message")) + case .createToxPortAlloc: + return (String(localized: "error_title"), + String(localized: "error_general_bind_port_message")) + case .createToxProxyBadType: + return (String(localized: "error_proxy_title"), + String(localized: "error_internal_message")) + case .createToxProxyBadHost: + return (String(localized: "error_proxy_title"), + String(localized: "error_proxy_invalid_address_message")) + case .createToxProxyBadPort: + return (String(localized: "error_proxy_title"), + String(localized: "error_proxy_invalid_port_message")) + case .createToxProxyNotFound: + return (String(localized: "error_proxy_title"), + String(localized: "error_proxy_host_not_resolved_message")) + case .createToxEncrypted: + return (String(localized: "error_title"), + String(localized: "error_general_profile_encrypted_message")) + case .createToxBadFormat: + return (String(localized: "error_title"), + String(localized: "error_general_bad_format_message")) + } + } +} + +extension OCTToxErrorSetInfoCode { + func nameStrings() -> (title: String, message: String) { + switch self { + case .unknow: + return (String(localized: "error_title"), + String(localized: "error_internal_message")) + case .tooLong: + return (String(localized: "error_title"), + String(localized: "error_name_too_long")) + } + } + + func statusMessageStrings() -> (title: String, message: String) { + switch self { + case .unknow: + return (String(localized: "error_title"), + String(localized: "error_internal_message")) + case .tooLong: + return (String(localized: "error_title"), + String(localized: "error_status_message_too_long")) + } + } +} + +extension OCTToxErrorFriendAdd { + func strings() -> (title: String, message: String) { + switch self { + case .tooLong: + return (String(localized: "error_title"), + String(localized: "error_contact_request_too_long")) + case .noMessage: + return (String(localized: "error_title"), + String(localized: "error_contact_request_no_message")) + case .ownKey: + return (String(localized: "error_title"), + String(localized: "error_contact_request_own_key")) + case .alreadySent: + return (String(localized: "error_title"), + String(localized: "error_contact_request_already_sent")) + case .badChecksum: + return (String(localized: "error_title"), + String(localized: "error_contact_request_bad_checksum")) + case .setNewNospam: + return (String(localized: "error_title"), + String(localized: "error_contact_request_new_nospam")) + case .malloc: + fallthrough + case .unknown: + return (String(localized: "error_title"), + String(localized: "error_internal_message")) + } + } +} + +extension OCTToxErrorFriendDelete { + func strings() -> (title: String, message: String) { + switch self { + case .notFound: + return (String(localized: "error_title"), + String(localized: "error_internal_message")) + } + } +} + +extension OCTToxAVErrorCall { + func strings() -> (title: String, message: String) { + switch self { + case .alreadyInCall: + return (String(localized: "error_title"), + String(localized: "call_error_already_in_call")) + case .friendNotConnected: + return (String(localized: "error_title"), + String(localized: "call_error_contact_is_offline")) + case .friendNotFound: + fallthrough + case .invalidBitRate: + fallthrough + case .malloc: + fallthrough + case .sync: + fallthrough + case .unknown: + return (String(localized: "error_title"), + String(localized: "error_internal_message")) + } + } +} + +extension OCTToxAVErrorAnswer { + func strings() -> (title: String, message: String) { + switch self { + case .friendNotCalling: + return (String(localized: "error_title"), + String(localized: "call_error_no_active_call")) + case .codecInitialization: + fallthrough + case .sync: + fallthrough + case .invalidBitRate: + fallthrough + case .unknown: + fallthrough + case .friendNotFound: + return (String(localized: "error_title"), + String(localized: "error_internal_message")) + } + } +} + +extension OCTSetUserAvatarError { + func strings() -> (title: String, message: String) { + switch self { + case .tooBig: + return (String(localized: "error_title"), + String(localized: "error_internal_message")) + } + } +} + +extension OCTSendFileError { + func strings() -> (title: String, message: String) { + switch self { + case .internalError: + fallthrough + case .cannotReadFile: + fallthrough + case .cannotSaveFileToUploads: + fallthrough + case .nameTooLong: + fallthrough + case .friendNotFound: + return (String(localized: "error_title"), + String(localized: "error_internal_message")) + case .friendNotConnected: + return (String(localized: "error_title"), + String(localized: "error_contact_not_connected")) + case .tooMany: + return (String(localized: "error_title"), + String(localized: "error_too_many_files")) + } + } +} + +extension OCTAcceptFileError { + func strings() -> (title: String, message: String) { + switch self { + case .internalError: + fallthrough + case .cannotWriteToFile: + fallthrough + case .friendNotFound: + fallthrough + case .wrongMessage: + return (String(localized: "error_title"), + String(localized: "error_internal_message")) + case .friendNotConnected: + return (String(localized: "error_title"), + String(localized: "error_contact_not_connected")) + } + } +} + +extension OCTFileTransferError { + func strings() -> (title: String, message: String) { + switch self { + case .wrongMessage: + return (String(localized: "error_title"), + String(localized: "error_internal_message")) + } + } +} diff --git a/Antidote/ExceptionHandling.h b/Antidote/ExceptionHandling.h new file mode 100644 index 0000000..721d2f9 --- /dev/null +++ b/Antidote/ExceptionHandling.h @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@interface ExceptionHandling : NSObject + ++ (void)tryWithBlock:(nonnull void (^)(void))tryBlock catch:(nonnull void (^)(NSException *__nonnull exception))catchBlock; + +@end diff --git a/Antidote/ExceptionHandling.m b/Antidote/ExceptionHandling.m new file mode 100644 index 0000000..3782afd --- /dev/null +++ b/Antidote/ExceptionHandling.m @@ -0,0 +1,19 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "ExceptionHandling.h" + +@implementation ExceptionHandling + ++ (void)tryWithBlock:(nonnull void (^)(void))tryBlock catch:(nonnull void (^)(NSException *exception))catchBlock +{ + @try { + tryBlock(); + } + @catch (NSException *exception) { + catchBlock(exception); + } +} + +@end diff --git a/Antidote/ExtendedTextField.swift b/Antidote/ExtendedTextField.swift new file mode 100644 index 0000000..e4841b5 --- /dev/null +++ b/Antidote/ExtendedTextField.swift @@ -0,0 +1,224 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let TextFieldHeight = 40.0 + static let VerticalOffset = 5.0 +} + +protocol ExtendedTextFieldDelegate: class { + func loginExtendedTextFieldReturnKeyPressed(_ field: ExtendedTextField) +} + +class ExtendedTextField: UIView { + enum FieldType { + case login + case normal + } + + weak var delegate: ExtendedTextFieldDelegate? + + var maxTextUTF8Length: Int = Int.max + + var title: String? { + get { + return titleLabel.text + } + set { + titleLabel.text = newValue + } + } + + var placeholder: String? { + get { + return textField.placeholder + } + set { + textField.placeholder = newValue + } + } + + var text: String? { + get { + return textField.text + } + set { + textField.text = newValue + } + } + + var hint: String? { + get { + return hintLabel.text + } + set { + hintLabel.text = newValue + } + } + + var secureTextEntry: Bool { + get { + return textField.isSecureTextEntry + } + set { + textField.isSecureTextEntry = newValue + } + } + + var returnKeyType: UIReturnKeyType { + get { + return textField.returnKeyType + } + set { + textField.returnKeyType = newValue + } + } + + fileprivate var titleLabel: UILabel! + fileprivate var textField: UITextField! + fileprivate var hintLabel: UILabel! + + init(theme: Theme, type: FieldType) { + super.init(frame: CGRect.zero) + + createViews(theme: theme, type: type) + installConstraints() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func becomeFirstResponder() -> Bool { + return textField.becomeFirstResponder() + } +} + +extension ExtendedTextField: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + delegate?.loginExtendedTextFieldReturnKeyPressed(self) + return false + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + let resultText = (textField.text! as NSString).replacingCharacters(in: range, with: string) + + if resultText.lengthOfBytes(using: String.Encoding.utf8) <= maxTextUTF8Length { + return true + } + + textField.text = resultText.substringToByteLength(maxTextUTF8Length, encoding: String.Encoding.utf8) + return false + } +} + +// Accessibility +extension ExtendedTextField { + override var isAccessibilityElement: Bool { + get { + return true + } + set {} + } + + override var accessibilityLabel: String? { + get { + return placeholder ?? title + } + set {} + } + + override var accessibilityHint: String? { + get { + var result: String? + + if placeholder != nil { + // If there is a placeholder also read title as part of the hint. + result = title + } + + switch (result, hint) { + case (.none, _): + return hint + case (.some, .none): + return result + case (.some(let r), .some(let s)): + return "\(r), \(s)" + } + } + set {} + } + + override var accessibilityValue: String? { + get { + return text + } + set {} + } + + override var accessibilityTraits: UIAccessibilityTraits { + get { + return textField.accessibilityTraits + } + set {} + } +} + +private extension ExtendedTextField { + func createViews(theme: Theme, type: FieldType) { + textField = UITextField() + textField.delegate = self + textField.borderStyle = .roundedRect + textField.autocapitalizationType = .sentences + textField.enablesReturnKeyAutomatically = true + addSubview(textField) + + let textColor: UIColor + + switch type { + case .login: + textColor = theme.colorForType(.NormalText) + + textField.layer.borderColor = theme.colorForType(.LoginButtonBackground).cgColor + textField.layer.borderWidth = 0.5 + textField.layer.masksToBounds = true + textField.layer.cornerRadius = 6.0 + case .normal: + textColor = theme.colorForType(.NormalText) + } + + titleLabel = UILabel() + titleLabel.textColor = textColor + titleLabel.font = UIFont.systemFont(ofSize: 18.0) + titleLabel.backgroundColor = .clear + addSubview(titleLabel) + + hintLabel = UILabel() + hintLabel.textColor = textColor + hintLabel.font = UIFont.antidoteFontWithSize(14.0, weight: .light) + hintLabel.numberOfLines = 0 + hintLabel.backgroundColor = .clear + addSubview(hintLabel) + } + + func installConstraints() { + titleLabel.snp.makeConstraints { + $0.top.leading.trailing.equalTo(self) + } + + textField.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(Constants.VerticalOffset) + $0.leading.trailing.equalTo(self) + $0.height.equalTo(Constants.TextFieldHeight) + } + + hintLabel.snp.makeConstraints { + $0.top.equalTo(textField.snp.bottom).offset(Constants.VerticalOffset) + $0.leading.trailing.equalTo(self) + $0.bottom.equalTo(self) + } + } +} diff --git a/Antidote/FAQController.swift b/Antidote/FAQController.swift new file mode 100644 index 0000000..a4f2840 --- /dev/null +++ b/Antidote/FAQController.swift @@ -0,0 +1,79 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import WebKit +import SnapKit + +private struct Constants { + static let FAQURL = "https://github.com/Zoxcore/Antidote/blob/develop/FAQ/en.md" +} + +class FAQController: UIViewController { + fileprivate let theme: Theme + + fileprivate var webView: WKWebView! + fileprivate var spinner: UIActivityIndicatorView! + + init(theme: Theme) { + self.theme = theme + + super.init(nibName: nil, bundle: nil) + + hidesBottomBarWhenPushed = true + title = String(localized: "settings_faq") + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + createViews() + installConstraints() + } + + override func viewDidLoad() { + super.viewDidLoad() + + let request = URLRequest(url: URL(string: Constants.FAQURL)!) + webView.load(request) + } +} + +extension FAQController: WKNavigationDelegate { + func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { + spinner.startAnimating() + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + spinner.stopAnimating() + } +} + +private extension FAQController { + func createViews() { + let configuration = WKWebViewConfiguration() + + webView = WKWebView(frame: CGRect.zero, configuration: configuration) + webView.navigationDelegate = self + view.addSubview(webView) + + spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray) + spinner.hidesWhenStopped = true + view.addSubview(spinner) + } + + func installConstraints() { + webView.snp.makeConstraints { + $0.edges.equalTo(view) + } + + spinner.snp.makeConstraints { + $0.center.equalTo(view) + } + } +} diff --git a/Antidote/FilePreviewControllerDataSource.swift b/Antidote/FilePreviewControllerDataSource.swift new file mode 100644 index 0000000..2836d9e --- /dev/null +++ b/Antidote/FilePreviewControllerDataSource.swift @@ -0,0 +1,71 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import QuickLook + +private class FilePreviewItem: NSObject, QLPreviewItem { + @objc var previewItemURL: URL? + @objc var previewItemTitle: String? + + init(url: URL, title: String?) { + self.previewItemURL = url + self.previewItemTitle = title + } +} + +class FilePreviewControllerDataSource: NSObject , QuickLookPreviewControllerDataSource { + weak var previewController: QuickLookPreviewController? + + let messages: Results + var messagesToken: RLMNotificationToken? + + init(chat: OCTChat, submanagerObjects: OCTSubmanagerObjects) { + let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [ + NSPredicate(format: "chatUniqueIdentifier == %@ AND messageFile != nil", chat.uniqueIdentifier), + + NSCompoundPredicate(orPredicateWithSubpredicates: [ + NSPredicate(format: "messageFile.fileType == \(OCTMessageFileType.ready.rawValue)"), + NSPredicate(format: "senderUniqueIdentifier == nil AND messageFile.fileType == \(OCTMessageFileType.canceled.rawValue)"), + ]), + ]) + + self.messages = submanagerObjects.messages(predicate: predicate).sortedResultsUsingProperty("dateInterval", ascending: true) + + super.init() + + messagesToken = messages.addNotificationBlock { [unowned self] change in + switch change { + case .initial: + break + case .update: + self.previewController?.reloadData() + case .error(let error): + fatalError("\(error)") + } + } + } + + deinit { + messagesToken?.invalidate() + } + + func indexOfMessage(_ message: OCTMessageAbstract) -> Int? { + return messages.indexOfObject(message) + } +} + +extension FilePreviewControllerDataSource: QLPreviewControllerDataSource { + func numberOfPreviewItems(in controller: QLPreviewController) -> Int { + return messages.count + } + + func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { + let message = messages[index] + + let url = URL(fileURLWithPath: message.messageFile!.filePath()!) + + return FilePreviewItem(url: url, title: message.messageFile!.fileName) + } +} diff --git a/Antidote/FriendCardController.swift b/Antidote/FriendCardController.swift new file mode 100644 index 0000000..203e06f --- /dev/null +++ b/Antidote/FriendCardController.swift @@ -0,0 +1,186 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +protocol FriendCardControllerDelegate: class { + func friendCardControllerChangeNickname(_ controller: FriendCardController, forFriend friend: OCTFriend) + func friendCardControllerOpenChat(_ controller: FriendCardController, forFriend friend: OCTFriend) + func friendCardControllerCall(_ controller: FriendCardController, toFriend friend: OCTFriend) + func friendCardControllerVideoCall(_ controller: FriendCardController, toFriend friend: OCTFriend) +} + +class FriendCardController: StaticTableController { + weak var delegate: FriendCardControllerDelegate? + + fileprivate weak var submanagerObjects: OCTSubmanagerObjects! + + fileprivate let friend: OCTFriend + + fileprivate let avatarManager: AvatarManager + fileprivate var friendToken: RLMNotificationToken? + + fileprivate let avatarModel: StaticTableAvatarCellModel + fileprivate let chatButtonsModel: StaticTableChatButtonsCellModel + fileprivate let nicknameModel: StaticTableDefaultCellModel + fileprivate let nameModel: StaticTableDefaultCellModel + fileprivate let statusMessageModel: StaticTableDefaultCellModel + fileprivate let publicKeyModel: StaticTableDefaultCellModel + fileprivate let capabilitiesModel: StaticTableDefaultCellModel + fileprivate let pushurlModel: StaticTableDefaultCellModel + + init(theme: Theme, friend: OCTFriend, submanagerObjects: OCTSubmanagerObjects) { + self.submanagerObjects = submanagerObjects + self.friend = friend + + self.avatarManager = AvatarManager(theme: theme) + + avatarModel = StaticTableAvatarCellModel() + chatButtonsModel = StaticTableChatButtonsCellModel() + nicknameModel = StaticTableDefaultCellModel() + nameModel = StaticTableDefaultCellModel() + statusMessageModel = StaticTableDefaultCellModel() + publicKeyModel = StaticTableDefaultCellModel() + capabilitiesModel = StaticTableDefaultCellModel() + pushurlModel = StaticTableDefaultCellModel() + + super.init(theme: theme, style: .plain, model: [ + [ + avatarModel, + chatButtonsModel, + ], + [ + nicknameModel, + nameModel, + statusMessageModel, + ], + [ + publicKeyModel, + ], + [ + capabilitiesModel, + ], + [ + pushurlModel, + ], + ]) + + updateModels() + } + + deinit { + friendToken?.invalidate() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + let predicate = NSPredicate(format: "uniqueIdentifier == %@", friend.uniqueIdentifier) + let results = submanagerObjects.friends(predicate: predicate) + friendToken = results.addNotificationBlock { [unowned self] change in + switch change { + case .initial: + break + case .update: + self.updateModels() + self.reloadTableView() + case .error(let error): + fatalError("\(error)") + } + } + } +} + +private extension FriendCardController { + func updateModels() { + title = friend.nickname + + if let data = friend.avatarData { + avatarModel.avatar = UIImage(data: data) + } + else { + avatarModel.avatar = avatarManager.avatarFromString( + friend.nickname, + diameter: StaticTableAvatarCellModel.Constants.AvatarImageSize) + } + avatarModel.userInteractionEnabled = false + + chatButtonsModel.chatButtonHandler = { [unowned self] in + self.delegate?.friendCardControllerOpenChat(self, forFriend: self.friend) + } + chatButtonsModel.callButtonHandler = { [unowned self] in + self.delegate?.friendCardControllerCall(self, toFriend: self.friend) + } + chatButtonsModel.videoButtonHandler = { [unowned self] in + self.delegate?.friendCardControllerVideoCall(self, toFriend: self.friend) + } + chatButtonsModel.chatButtonEnabled = true + chatButtonsModel.callButtonEnabled = friend.isConnected + chatButtonsModel.videoButtonEnabled = friend.isConnected + + nicknameModel.title = String(localized: "nickname") + nicknameModel.value = friend.nickname + nicknameModel.rightImageType = .arrow + nicknameModel.didSelectHandler = { [unowned self] _ -> Void in + self.delegate?.friendCardControllerChangeNickname(self, forFriend: self.friend) + } + + nameModel.title = String(localized: "name") + nameModel.value = friend.name + nameModel.userInteractionEnabled = false + + statusMessageModel.title = String(localized: "status_message") + statusMessageModel.value = friend.statusMessage + statusMessageModel.userInteractionEnabled = false + + publicKeyModel.title = String(localized: "public_key") + publicKeyModel.value = friend.publicKey + publicKeyModel.userInteractionEnabled = false + publicKeyModel.canCopyValue = true + + capabilitiesModel.title = "Tox Capabilities" + let capabilities = friend.capabilities2 ?? "" + if (capabilities.count > 0) { + let caps = NSNumber(value: UInt64(capabilities) ?? 0) + capabilitiesModel.value = capabilitiesToString(caps) + } else { + capabilitiesModel.value = "BASIC" + } + capabilitiesModel.userInteractionEnabled = false + + pushurlModel.title = "Push URL" + let pushtoken = friend.pushToken ?? "" + if (pushtoken.count > 0) { + pushurlModel.value = pushtoken + } else { + pushurlModel.value = "" + } + pushurlModel.userInteractionEnabled = false + } + + func capabilitiesToString(_ cap: NSNumber) -> String { + var ret: String = "BASIC" + if ((UInt(cap) & 1) > 0) { + ret = ret + " CAPABILITIES" + } + if ((UInt(cap) & 2) > 0) { + ret = ret + " MSGV2" + } + if ((UInt(cap) & 4) > 0) { + ret = ret + " H264" + } + if ((UInt(cap) & 8) > 0) { + ret = ret + " MSGV3" + } + if ((UInt(cap) & 16) > 0) { + ret = ret + " FTV2" + } + return ret; + } + +} diff --git a/Antidote/FriendListCell.swift b/Antidote/FriendListCell.swift new file mode 100644 index 0000000..d8ff9c9 --- /dev/null +++ b/Antidote/FriendListCell.swift @@ -0,0 +1,121 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +class FriendListCell: BaseCell { + struct Constants { + static let AvatarSize = 30.0 + static let AvatarLeftOffset = 10.0 + static let AvatarRightOffset = 16.0 + + static let TopLabelHeight = 22.0 + static let MinimumBottomLabelHeight = 15.0 + + static let VerticalOffset = 3.0 + } + + fileprivate var avatarView: ImageViewWithStatus! + fileprivate var topLabel: UILabel! + fileprivate var bottomLabel: UILabel! + fileprivate var arrowImageView: UIImageView! + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let friendModel = model as? FriendListCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + separatorInset.left = CGFloat(Constants.AvatarLeftOffset + Constants.AvatarSize + Constants.AvatarRightOffset) + + avatarView.imageView.image = friendModel.avatar + avatarView.userStatusView.theme = theme + avatarView.userStatusView.userStatus = friendModel.status + avatarView.userStatusView.connectionStatus = friendModel.connectionstatus + avatarView.userStatusView.isHidden = friendModel.hideStatus + + topLabel.text = friendModel.topText + topLabel.textColor = theme.colorForType(.NormalText) + + bottomLabel.text = friendModel.bottomText + bottomLabel.textColor = theme.colorForType(.FriendCellStatus) + bottomLabel.numberOfLines = friendModel.multilineBottomtext ? 0 : 1 + + accessibilityLabel = friendModel.accessibilityLabel + accessibilityValue = friendModel.accessibilityValue + } + + override func createViews() { + super.createViews() + + avatarView = ImageViewWithStatus() + contentView.addSubview(avatarView) + + topLabel = UILabel() + topLabel.font = UIFont.systemFont(ofSize: 18.0) + contentView.addSubview(topLabel) + + bottomLabel = UILabel() + bottomLabel.font = UIFont.antidoteFontWithSize(12.0, weight: .light) + contentView.addSubview(bottomLabel) + + let image = UIImage(named: "right-arrow")!.flippedToCorrectLayout() + arrowImageView = UIImageView(image: image) + arrowImageView.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) + contentView.addSubview(arrowImageView) + } + + override func installConstraints() { + super.installConstraints() + + avatarView.snp.makeConstraints { + $0.leading.equalTo(contentView).offset(Constants.AvatarLeftOffset) + $0.centerY.equalTo(contentView) + $0.size.equalTo(Constants.AvatarSize) + } + + topLabel.snp.makeConstraints { + $0.leading.equalTo(avatarView.snp.trailing).offset(Constants.AvatarRightOffset) + $0.top.equalTo(contentView).offset(Constants.VerticalOffset) + $0.height.equalTo(Constants.TopLabelHeight) + } + + bottomLabel.snp.makeConstraints { + $0.leading.trailing.equalTo(topLabel) + $0.top.equalTo(topLabel.snp.bottom) + $0.bottom.equalTo(contentView).offset(-Constants.VerticalOffset) + $0.height.greaterThanOrEqualTo(Constants.MinimumBottomLabelHeight) + } + + arrowImageView.snp.makeConstraints { + $0.centerY.equalTo(contentView) + $0.leading.greaterThanOrEqualTo(topLabel.snp.trailing) + $0.trailing.equalTo(contentView) + } + } +} + +// Accessibility +extension FriendListCell { + override var isAccessibilityElement: Bool { + get { + return true + } + set {} + } + + // Label and value are set in setupWithTheme:model: method + // var accessibilityLabel: String? + // var accessibilityValue: String? + + override var accessibilityTraits: UIAccessibilityTraits { + get { + return UIAccessibilityTraitButton + } + set {} + } +} diff --git a/Antidote/FriendListCellModel.swift b/Antidote/FriendListCellModel.swift new file mode 100644 index 0000000..faf40d9 --- /dev/null +++ b/Antidote/FriendListCellModel.swift @@ -0,0 +1,20 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class FriendListCellModel: BaseCellModel { + var avatar: UIImage? + + var topText: String = "" + var bottomText: String = "" + var multilineBottomtext: Bool = false + + var accessibilityLabel = "" + var accessibilityValue = "" + + var status: UserStatus = .offline + var connectionstatus: ConnectionStatus = .none + var hideStatus: Bool = false +} diff --git a/Antidote/FriendListController.swift b/Antidote/FriendListController.swift new file mode 100644 index 0000000..cdfb26c --- /dev/null +++ b/Antidote/FriendListController.swift @@ -0,0 +1,315 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +protocol FriendListControllerDelegate: class { + func friendListController(_ controller: FriendListController, didSelectFriend friend: OCTFriend) + func friendListController(_ controller: FriendListController, didSelectRequest request: OCTFriendRequest) + func friendListControllerAddFriend(_ controller: FriendListController) + func friendListController(_ controller: FriendListController, showQRCodeWithText text: String) +} + +class FriendListController: UIViewController { + weak var delegate: FriendListControllerDelegate? + + fileprivate let theme: Theme + + fileprivate var dataSource: FriendListDataSource! + + fileprivate weak var submanagerObjects: OCTSubmanagerObjects! + fileprivate weak var submanagerFriends: OCTSubmanagerFriends! + fileprivate weak var submanagerChats: OCTSubmanagerChats! + fileprivate weak var submanagerUser: OCTSubmanagerUser! + + fileprivate var placeholderView: UITextView! + fileprivate var tableView: UITableView! + + init(theme: Theme, submanagerObjects: OCTSubmanagerObjects, submanagerFriends: OCTSubmanagerFriends, submanagerChats: OCTSubmanagerChats, submanagerUser: OCTSubmanagerUser) { + self.theme = theme + + self.submanagerObjects = submanagerObjects + self.submanagerFriends = submanagerFriends + self.submanagerChats = submanagerChats + self.submanagerUser = submanagerUser + + super.init(nibName: nil, bundle: nil) + + addNavigationButtons() + + edgesForExtendedLayout = UIRectEdge() + title = String(localized: "contacts_title") + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + createTableView() + createPlaceholderView() + installConstraints() + } + + override func viewDidLoad() { + super.viewDidLoad() + + let friends = submanagerObjects.friends() + let requests = submanagerObjects.friendRequests() + dataSource = FriendListDataSource(theme: theme, friends: friends, requests: requests) + dataSource.delegate = self + + // removing separators on empty lines + tableView.tableFooterView = UIView() + + updateViewsVisibility() + + let message = "Antidote is Tox" + let echobotid = "76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6" + + print("EchobotAdded=\(UserDefaultsManager().EchobotAdded)") + + if (UserDefaultsManager().EchobotAdded == false) { + do { + try self.submanagerFriends.sendFriendRequest(toAddress: echobotid, message: message) + UserDefaultsManager().EchobotAdded = true + } + catch let error as NSError { + return + } + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + updateViewsVisibility() + } + + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + + tableView.setEditing(editing, animated: animated) + } +} + +extension FriendListController { + @objc func addFriendButtonPressed() { + delegate?.friendListControllerAddFriend(self) + } +} + +extension FriendListController: FriendListDataSourceDelegate { + func friendListDataSourceBeginUpdates() { + tableView.beginUpdates() + } + + func friendListDataSourceEndUpdates() { + tableView.endUpdates() + updateViewsVisibility() + } + + func friendListDataSourceInsertRowsAtIndexPaths(_ indexPaths: [IndexPath]) { + tableView.insertRows(at: indexPaths, with: .automatic) + } + + func friendListDataSourceDeleteRowsAtIndexPaths(_ indexPaths: [IndexPath]) { + tableView.deleteRows(at: indexPaths, with: .automatic) + } + + func friendListDataSourceReloadRowsAtIndexPaths(_ indexPaths: [IndexPath]) { + tableView.reloadRows(at: indexPaths, with: .automatic) + } + + func friendListDataSourceInsertSections(_ sections: IndexSet) { + tableView.insertSections(sections, with: .automatic) + } + + func friendListDataSourceDeleteSections(_ sections: IndexSet) { + tableView.deleteSections(sections, with: .automatic) + } + + func friendListDataSourceReloadSections(_ sections: IndexSet) { + tableView.reloadSections(sections, with: .automatic) + } + + func friendListDataSourceReloadTable() { + tableView.reloadData() + } +} + +extension FriendListController: UITableViewDataSource { + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: FriendListCell.staticReuseIdentifier) as! FriendListCell + let model = dataSource.modelAtIndexPath(indexPath) + + cell.setupWithTheme(theme, model: model) + + return cell + } + + func numberOfSections(in tableView: UITableView) -> Int { + return dataSource.numberOfSections() + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return dataSource.numberOfRowsInSection(section) + } + + func sectionIndexTitles(for tableView: UITableView) -> [String]? { + return dataSource.sectionIndexTitles() + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return dataSource.titleForHeaderInSection(section) + } + + func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + let title: String + + switch dataSource.objectAtIndexPath(indexPath) { + case .request: + title = String(localized:"delete_contact_request_title") + case .friend: + title = String(localized:"delete_contact_title") + } + + let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: String(localized: "alert_cancel"), style: .default, handler: nil)) + alert.addAction(UIAlertAction(title: String(localized: "alert_delete"), style: .destructive) { [unowned self] _ -> Void in + switch self.dataSource.objectAtIndexPath(indexPath) { + case .request(let request): + self.submanagerFriends.remove(request) + case .friend(let friend): + do { + let chat = self.submanagerChats.getOrCreateChat(with: friend) + + try self.submanagerFriends.remove(friend) + + self.submanagerChats.removeAllMessages(in: chat, removeChat: true) + } + catch let error as NSError { + handleErrorWithType(.removeFriend, error: error) + } + } + }) + + present(alert, animated: true, completion: nil) + } + } +} + +extension FriendListController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + switch dataSource.objectAtIndexPath(indexPath) { + case .request(let request): + didSelectFriendRequest(request) + case .friend(let friend): + delegate?.friendListController(self, didSelectFriend: friend) + } + } +} + +extension FriendListController : UITextViewDelegate { + func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { + if textView === placeholderView { + let toxId = submanagerUser.userAddress + let alert = UIAlertController(title: String(localized: "my_tox_id"), message: toxId, preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: String(localized: "copy"), style: .default) { _ -> Void in + UIPasteboard.general.string = toxId + }) + + alert.addAction(UIAlertAction(title: String(localized: "show_qr_code"), style: .default) { [weak self] _ -> Void in + self?.delegate?.friendListController(self!, showQRCodeWithText: toxId) + }) + + alert.addAction(UIAlertAction(title: String(localized: "alert_cancel"), style: .cancel, handler: nil)) + + present(alert, animated: true, completion: nil) + } + + return false + } +} + +private extension FriendListController { + func addNavigationButtons() { + navigationItem.rightBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .add, + target: self, + action: #selector(FriendListController.addFriendButtonPressed)) + } + + func updateViewsVisibility() { + var isEmpty = true + + for section in 0.. 0 { + isEmpty = false + break + } + } + + navigationItem.leftBarButtonItem = isEmpty ? nil : editButtonItem + placeholderView.isHidden = !isEmpty + } + + func createTableView() { + tableView = UITableView() + tableView.estimatedRowHeight = 44.0 + tableView.dataSource = self + tableView.delegate = self + tableView.backgroundColor = theme.colorForType(.NormalBackground) + tableView.sectionIndexColor = theme.colorForType(.LinkText) + + view.addSubview(tableView) + + tableView.register(FriendListCell.self, forCellReuseIdentifier: FriendListCell.staticReuseIdentifier) + } + + func createPlaceholderView() { + let top = String(localized: "contact_no_contacts_add_contact") + let bottom = String(localized: "contact_no_contacts_share_tox_id") + + let text = NSMutableAttributedString(string: "\(top)\(bottom)") + let linkRange = NSRange(location: top.count, length: bottom.count) + let fullRange = NSRange(location: 0, length: text.length) + + text.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.colorForType(.EmptyScreenPlaceholderText), range: fullRange) + text.addAttribute(NSAttributedStringKey.font, value: UIFont.antidoteFontWithSize(26.0, weight: .light), range: fullRange) + text.addAttribute(NSAttributedStringKey.link, value: "", range: linkRange) + + placeholderView = UITextView() + placeholderView.delegate = self + placeholderView.attributedText = text + placeholderView.isEditable = false + placeholderView.isScrollEnabled = false + placeholderView.textAlignment = .center + placeholderView.linkTextAttributes = [NSAttributedStringKey.foregroundColor.rawValue : theme.colorForType(.LinkText)] + view.addSubview(placeholderView) + } + + func installConstraints() { + tableView.snp.makeConstraints { + $0.edges.equalTo(view) + } + + placeholderView.snp.makeConstraints { + $0.center.equalTo(view) + $0.size.equalTo(placeholderView.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))) + } + } + + func didSelectFriendRequest(_ request: OCTFriendRequest) { + delegate?.friendListController(self, didSelectRequest: request) + } +} diff --git a/Antidote/FriendListDataSource.swift b/Antidote/FriendListDataSource.swift new file mode 100644 index 0000000..4796ce6 --- /dev/null +++ b/Antidote/FriendListDataSource.swift @@ -0,0 +1,230 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +private struct Constants { + static let FriendRequestsSection = 0 +} + +protocol FriendListDataSourceDelegate: class { + func friendListDataSourceBeginUpdates() + func friendListDataSourceEndUpdates() + + func friendListDataSourceInsertRowsAtIndexPaths(_ indexPaths: [IndexPath]) + func friendListDataSourceDeleteRowsAtIndexPaths(_ indexPaths: [IndexPath]) + func friendListDataSourceReloadRowsAtIndexPaths(_ indexPaths: [IndexPath]) + + func friendListDataSourceInsertSections(_ sections: IndexSet) + func friendListDataSourceDeleteSections(_ sections: IndexSet) + func friendListDataSourceReloadSections(_ sections: IndexSet) + + func friendListDataSourceReloadTable() +} + +enum FriendListObject { + case request(OCTFriendRequest) + case friend(OCTFriend) +} + +class FriendListDataSource: NSObject { + weak var delegate: FriendListDataSourceDelegate? + + fileprivate let avatarManager: AvatarManager + fileprivate let dateFormatter: DateFormatter + + fileprivate let requests: Results? + fileprivate let friends: Results + + fileprivate var requestsToken: RLMNotificationToken? + fileprivate var friendsToken: RLMNotificationToken? + + /// In case if requests is nil friend requests won't be shown. + init(theme: Theme, friends: Results, requests: Results? = nil) { + self.avatarManager = AvatarManager(theme: theme) + self.dateFormatter = DateFormatter(type: .relativeDateAndTime) + + self.requests = requests + self.friends = friends + + super.init() + + addNotificationBlocks() + } + + deinit { + requestsToken?.invalidate() + friendsToken?.invalidate() + } + + func numberOfSections() -> Int { + if isRequestsSectionVisible() { + return 2 + } + + // friends only + return 1 + } + + func numberOfRowsInSection(_ section: Int) -> Int { + if section == Constants.FriendRequestsSection && isRequestsSectionVisible() { + return requests!.count + } + else { + return friends.count + } + } + + func modelAtIndexPath(_ indexPath: IndexPath) -> FriendListCellModel { + let model = FriendListCellModel() + + switch objectAtIndexPath(indexPath) { + case .request(let request): + model.avatar = avatarManager.avatarFromString("", diameter: CGFloat(FriendListCell.Constants.AvatarSize)) + model.topText = request.publicKey + model.bottomText = request.message ?? "" + model.multilineBottomtext = true + model.hideStatus = true + + model.accessibilityLabel = String(localized: "contact_request") + model.accessibilityValue = "" + + if let message = request.message { + model.accessibilityValue += String(localized: "add_contact_default_message_title") + ": " + message + ", " + } + model.accessibilityValue += String(localized: "public_key") + ": " + request.publicKey + + case .friend(let friend): + if let data = friend.avatarData { + model.avatar = UIImage(data: data) + } + else { + model.avatar = avatarManager.avatarFromString(friend.nickname, diameter: CGFloat(FriendListCell.Constants.AvatarSize)) + } + model.topText = friend.nickname + + model.status = UserStatus(connectionStatus: friend.connectionStatus, userStatus: friend.status) + model.connectionstatus = ConnectionStatus(connectionStatus: friend.connectionStatus) + + model.accessibilityLabel = friend.nickname + model.accessibilityValue = model.status.toString() + + if friend.isConnected { + model.bottomText = friend.statusMessage ?? "" + model.accessibilityValue += ", Status: \(model.bottomText)" + } + else if let date = friend.lastSeenOnline() { + model.bottomText = String(localized: "contact_last_seen", dateFormatter.string(from: date)) + model.accessibilityValue += ", " + model.bottomText + } + } + + return model + } + + func objectAtIndexPath(_ indexPath: IndexPath) -> FriendListObject { + if indexPath.section == Constants.FriendRequestsSection && isRequestsSectionVisible() { + return .request(requests![indexPath.row]) + } + else { + return .friend(friends[indexPath.row]) + } + } + + func sectionIndexTitles() -> [String] { + // TODO fix when Realm will support sections + let array = [String]() + return array + } + + func titleForHeaderInSection(_ section: Int) -> String? { + if !isRequestsSectionVisible() { + return nil + } + + if section == Constants.FriendRequestsSection { + return String(localized: "contact_requests_section") + } + else { + return String(localized: "contacts_title") + } + } +} + +private extension FriendListDataSource { + func addNotificationBlocks() { + requestsToken = requests?.addNotificationBlock { [unowned self] change in + switch change { + case .initial: + break + case .update(let requests, let deletions, let insertions, let modifications): + guard let requests = requests else { + return + } + + if deletions.count > 0 { + // reloading data on request removal/friend insertion to synchronize requests/friends + self.delegate?.friendListDataSourceReloadTable() + return + } + + self.delegate?.friendListDataSourceBeginUpdates() + + let countAfter = requests.count + let countBefore = countAfter - insertions.count + deletions.count + + if countBefore == 0 && countAfter > 0 { + self.delegate?.friendListDataSourceInsertSections(IndexSet(integer: 0)) + } + else if countBefore > 0 && countAfter == 0 { + self.delegate?.friendListDataSourceDeleteSections(IndexSet(integer: 0)) + } + else { + self.delegate?.friendListDataSourceDeleteRowsAtIndexPaths(deletions.map { IndexPath(row: $0, section: 0)} ) + self.delegate?.friendListDataSourceInsertRowsAtIndexPaths(insertions.map { IndexPath(row: $0, section: 0)} ) + self.delegate?.friendListDataSourceReloadRowsAtIndexPaths(modifications.map { IndexPath(row: $0, section: 0)} ) + } + + self.delegate?.friendListDataSourceEndUpdates() + case .error(let error): + fatalError("\(error)") + } + } + + friendsToken = friends.addNotificationBlock { [unowned self] change in + switch change { + case .initial: + break + case .update(_, let deletions, let insertions, let modifications): + if insertions.count > 0 { + // reloading data on request removal/friend insertion to synchronize requests/friends + self.delegate?.friendListDataSourceReloadTable() + return + } + + let section = self.isRequestsSectionVisible() ? 1 : 0 + + let deletions = deletions.map { IndexPath(row: $0, section: section) } + let insertions = insertions.map { IndexPath(row: $0, section: section) } + let modifications = modifications.map { IndexPath(row: $0, section: section) } + + self.delegate?.friendListDataSourceBeginUpdates() + self.delegate?.friendListDataSourceDeleteRowsAtIndexPaths(deletions) + self.delegate?.friendListDataSourceInsertRowsAtIndexPaths(insertions) + self.delegate?.friendListDataSourceReloadRowsAtIndexPaths(modifications) + self.delegate?.friendListDataSourceEndUpdates() + case .error(let error): + fatalError("\(error)") + } + } + } + + func isRequestsSectionVisible() -> Bool { + guard let requests = requests else { + return false + } + + return requests.count > 0 + } +} diff --git a/Antidote/FriendRequestController.swift b/Antidote/FriendRequestController.swift new file mode 100644 index 0000000..1053904 --- /dev/null +++ b/Antidote/FriendRequestController.swift @@ -0,0 +1,90 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +protocol FriendRequestControllerDelegate: class { + func friendRequestControllerDidFinish(_ controller: FriendRequestController) +} + +class FriendRequestController: StaticTableController { + weak var delegate: FriendRequestControllerDelegate? + + fileprivate let request: OCTFriendRequest + + fileprivate weak var submanagerFriends: OCTSubmanagerFriends! + + fileprivate let publicKeyModel: StaticTableDefaultCellModel + fileprivate let messageModel: StaticTableDefaultCellModel + fileprivate let buttonsModel: StaticTableMultiChoiceButtonCellModel + + init(theme: Theme, request: OCTFriendRequest, submanagerFriends: OCTSubmanagerFriends) { + self.request = request + + self.submanagerFriends = submanagerFriends + + publicKeyModel = StaticTableDefaultCellModel() + messageModel = StaticTableDefaultCellModel() + buttonsModel = StaticTableMultiChoiceButtonCellModel() + + super.init(theme: theme, style: .plain, model: [ + [ + publicKeyModel, + messageModel, + ], + [ + buttonsModel, + ], + ]) + + updateModels() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private extension FriendRequestController { + func updateModels() { + title = String(localized: "contact_request") + + publicKeyModel.title = String(localized: "public_key") + publicKeyModel.value = request.publicKey + publicKeyModel.userInteractionEnabled = false + + messageModel.title = String(localized: "status_message") + messageModel.value = request.message + messageModel.userInteractionEnabled = false + + buttonsModel.buttons = [ + StaticTableMultiChoiceButtonCellModel.ButtonModel(title: String(localized: "contact_request_decline"), style: .negative, target: self, action: #selector(FriendRequestController.declineButtonPressed)), + StaticTableMultiChoiceButtonCellModel.ButtonModel(title: String(localized: "contact_request_accept"), style: .positive, target: self, action: #selector(FriendRequestController.acceptButtonPressed)), + ] + } +} + +extension FriendRequestController { + @objc func declineButtonPressed() { + let alert = UIAlertController(title: String(localized: "contact_request_delete_title"), message: nil, preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: String(localized: "alert_cancel"), style: .default, handler: nil)) + alert.addAction(UIAlertAction(title: String(localized: "alert_delete"), style: .destructive) { [unowned self] _ -> Void in + self.submanagerFriends.remove(self.request) + self.delegate?.friendRequestControllerDidFinish(self) + }) + + present(alert, animated: true, completion: nil) + } + + @objc func acceptButtonPressed() { + do { + try submanagerFriends.approve(request) + delegate?.friendRequestControllerDidFinish(self) + } + catch let error as NSError { + handleErrorWithType(.toxAddFriend, error: error) + } + } +} diff --git a/Antidote/FriendSelectController.swift b/Antidote/FriendSelectController.swift new file mode 100644 index 0000000..273bf8f --- /dev/null +++ b/Antidote/FriendSelectController.swift @@ -0,0 +1,197 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +protocol FriendSelectControllerDelegate: class { + func friendSelectController(_ controller: FriendSelectController, didSelectFriend friend: OCTFriend) + func friendSelectControllerCancel(_ controller: FriendSelectController) +} + +class FriendSelectController: UIViewController { + weak var delegate: FriendSelectControllerDelegate? + + var userInfo: AnyObject? + + fileprivate let theme: Theme + + fileprivate let dataSource: FriendListDataSource + + fileprivate var placeholderView: UITextView! + fileprivate var tableView: UITableView! + + init(theme: Theme, submanagerObjects: OCTSubmanagerObjects, userInfo: AnyObject? = nil) { + self.theme = theme + self.userInfo = userInfo + + let friends = submanagerObjects.friends() + self.dataSource = FriendListDataSource(theme: theme, friends: friends) + + super.init(nibName: nil, bundle: nil) + + dataSource.delegate = self + + addNavigationButtons() + + edgesForExtendedLayout = UIRectEdge() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + createTableView() + createPlaceholderView() + installConstraints() + + updateViewsVisibility() + } +} + +extension FriendSelectController { + @objc func cancelButtonPressed() { + delegate?.friendSelectControllerCancel(self) + } +} + +extension FriendSelectController: FriendListDataSourceDelegate { + func friendListDataSourceBeginUpdates() { + tableView.beginUpdates() + } + + func friendListDataSourceEndUpdates() { + self.tableView.endUpdates() + updateViewsVisibility() + } + + func friendListDataSourceInsertRowsAtIndexPaths(_ indexPaths: [IndexPath]) { + tableView.insertRows(at: indexPaths, with: .automatic) + } + + func friendListDataSourceDeleteRowsAtIndexPaths(_ indexPaths: [IndexPath]) { + tableView.deleteRows(at: indexPaths, with: .automatic) + } + + func friendListDataSourceReloadRowsAtIndexPaths(_ indexPaths: [IndexPath]) { + tableView.reloadRows(at: indexPaths, with: .automatic) + } + + func friendListDataSourceInsertSections(_ sections: IndexSet) { + tableView.insertSections(sections, with: .automatic) + } + + func friendListDataSourceDeleteSections(_ sections: IndexSet) { + tableView.deleteSections(sections, with: .automatic) + } + + func friendListDataSourceReloadSections(_ sections: IndexSet) { + tableView.reloadSections(sections, with: .automatic) + } + + func friendListDataSourceReloadTable() { + tableView.reloadData() + } +} + +extension FriendSelectController: UITableViewDataSource { + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: FriendListCell.staticReuseIdentifier) as! FriendListCell + let model = dataSource.modelAtIndexPath(indexPath) + + cell.setupWithTheme(theme, model: model) + + return cell + } + + func numberOfSections(in tableView: UITableView) -> Int { + return dataSource.numberOfSections() + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return dataSource.numberOfRowsInSection(section) + } + + func sectionIndexTitles(for tableView: UITableView) -> [String]? { + return dataSource.sectionIndexTitles() + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return dataSource.titleForHeaderInSection(section) + } +} + +extension FriendSelectController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + switch dataSource.objectAtIndexPath(indexPath) { + case .request: + // nop + break + case .friend(let friend): + delegate?.friendSelectController(self, didSelectFriend: friend) + } + } +} + +private extension FriendSelectController { + func addNavigationButtons() { + navigationItem.leftBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .cancel, + target: self, + action: #selector(FriendSelectController.cancelButtonPressed)) + } + + func updateViewsVisibility() { + var isEmpty = true + + for section in 0.. 0 { + isEmpty = false + break + } + } + + placeholderView.isHidden = !isEmpty + } + + func createTableView() { + tableView = UITableView() + tableView.estimatedRowHeight = 44.0 + tableView.dataSource = self + tableView.delegate = self + tableView.backgroundColor = theme.colorForType(.NormalBackground) + tableView.sectionIndexColor = theme.colorForType(.LinkText) + // removing separators on empty lines + tableView.tableFooterView = UIView() + + view.addSubview(tableView) + + tableView.register(FriendListCell.self, forCellReuseIdentifier: FriendListCell.staticReuseIdentifier) + } + + func createPlaceholderView() { + placeholderView = UITextView() + placeholderView.text = String(localized: "contact_no_contacts") + placeholderView.isEditable = false + placeholderView.isScrollEnabled = false + placeholderView.textAlignment = .center + view.addSubview(placeholderView) + } + + func installConstraints() { + tableView.snp.makeConstraints { + $0.edges.equalTo(view) + } + + placeholderView.snp.makeConstraints { + $0.center.equalTo(view) + $0.size.equalTo(placeholderView.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))) + } + } +} diff --git a/Antidote/FriendsTabCoordinator.swift b/Antidote/FriendsTabCoordinator.swift new file mode 100644 index 0000000..7e7cae8 --- /dev/null +++ b/Antidote/FriendsTabCoordinator.swift @@ -0,0 +1,152 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +protocol FriendsTabCoordinatorDelegate: class { + func friendsTabCoordinatorOpenChat(_ coordinator: FriendsTabCoordinator, forFriend friend: OCTFriend) + func friendsTabCoordinatorCall(_ coordinator: FriendsTabCoordinator, toFriend friend: OCTFriend) + func friendsTabCoordinatorVideoCall(_ coordinator: FriendsTabCoordinator, toFriend friend: OCTFriend) +} + +class FriendsTabCoordinator: ActiveSessionNavigationCoordinator { + weak var delegate: FriendsTabCoordinatorDelegate? + + fileprivate weak var toxManager: OCTManager! + + init(theme: Theme, toxManager: OCTManager) { + self.toxManager = toxManager + + super.init(theme: theme) + } + + override func startWithOptions(_ options: CoordinatorOptions?) { + let controller = FriendListController(theme: theme, submanagerObjects: toxManager.objects, submanagerFriends: toxManager.friends, submanagerChats: toxManager.chats, submanagerUser: toxManager.user) + controller.delegate = self + + navigationController.pushViewController(controller, animated: false) + } + + func showRequest(_ request: OCTFriendRequest, animated: Bool) { + navigationController.popToRootViewController(animated: false) + + let controller = FriendRequestController(theme: theme, request: request, submanagerFriends: toxManager.friends) + controller.delegate = self + + navigationController.pushViewController(controller, animated: animated) + } +} + +extension FriendsTabCoordinator: FriendListControllerDelegate { + func friendListController(_ controller: FriendListController, didSelectFriend friend: OCTFriend) { + let controller = FriendCardController(theme: theme, friend: friend, submanagerObjects: toxManager.objects) + controller.delegate = self + + navigationController.pushViewController(controller, animated: true) + } + + func friendListController(_ controller: FriendListController, didSelectRequest request: OCTFriendRequest) { + showRequest(request, animated: true) + } + + func friendListControllerAddFriend(_ controller: FriendListController) { + let controller = AddFriendController(theme: theme, submanagerFriends: toxManager.friends) + controller.delegate = self + + navigationController.pushViewController(controller, animated: true) + } + + func friendListController(_ controller: FriendListController, showQRCodeWithText text: String) { + let controller = QRViewerController(theme: theme, text: text) + controller.delegate = self + + let toPresent = UINavigationController(rootViewController: controller) + + navigationController.present(toPresent, animated: true, completion: nil) + } +} + +extension FriendsTabCoordinator: QRViewerControllerDelegate { + func qrViewerControllerDidFinishPresenting() { + navigationController.dismiss(animated: true, completion: nil) + } +} + +extension FriendsTabCoordinator: FriendCardControllerDelegate { + func friendCardControllerChangeNickname(_ controller: FriendCardController, forFriend friend: OCTFriend) { + let title = String(localized: "nickname") + let defaultValue = friend.nickname + + let textController = TextEditController(theme: theme, title: title, defaultValue: defaultValue, changeTextHandler: { + [unowned self] newValue -> Void in + self.toxManager.objects.change(friend, nickname: newValue) + + }, userFinishedEditing: { [unowned self] in + self.navigationController.popViewController(animated: true) + }) + + navigationController.pushViewController(textController, animated: true) + } + + func friendCardControllerOpenChat(_ controller: FriendCardController, forFriend friend: OCTFriend) { + delegate?.friendsTabCoordinatorOpenChat(self, forFriend: friend) + } + + func friendCardControllerCall(_ controller: FriendCardController, toFriend friend: OCTFriend) { + delegate?.friendsTabCoordinatorCall(self, toFriend: friend) + } + + func friendCardControllerVideoCall(_ controller: FriendCardController, toFriend friend: OCTFriend) { + delegate?.friendsTabCoordinatorVideoCall(self, toFriend: friend) + } +} + +extension FriendsTabCoordinator: FriendRequestControllerDelegate { + func friendRequestControllerDidFinish(_ controller: FriendRequestController) { + navigationController.popViewController(animated: true) + } +} + +extension FriendsTabCoordinator: AddFriendControllerDelegate { + func addFriendControllerScanQRCode(_ controller: AddFriendController, + validateCodeHandler: @escaping (String) -> Bool, + didScanHander: @escaping (String) -> Void) { + let scanner = QRScannerController(theme: theme) + + scanner.didScanStringsBlock = { [unowned self, scanner] in + let qrCode = $0.filter { validateCodeHandler($0) }.first + + if let code = qrCode { + didScanHander(code) + self.navigationController.dismiss(animated: true, completion: nil) + } + else { + scanner.pauseScanning = true + + let title = String(localized:"error_title") + let message = String(localized:"add_contact_wrong_qr") + let button = String(localized:"error_ok_button") + + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: button, style: .default) { [unowned scanner ] _ -> Void in + scanner.pauseScanning = false + }) + + scanner.present(alert, animated: true, completion: nil) + } + } + + scanner.cancelBlock = { [unowned self] in + self.navigationController.dismiss(animated: true, completion: nil) + } + + let scannerNavCon = UINavigationController(rootViewController: scanner) + navigationController.present(scannerNavCon, animated: true, completion: nil) + } + + func addFriendControllerDidFinish(_ controller: AddFriendController) { + navigationController.popViewController(animated: true) + } +} diff --git a/Antidote/FullscreenPicker.swift b/Antidote/FullscreenPicker.swift new file mode 100644 index 0000000..508ea70 --- /dev/null +++ b/Antidote/FullscreenPicker.swift @@ -0,0 +1,158 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let AnimationDuration = 0.3 + static let ToolbarHeight: CGFloat = 44.0 +} + +protocol FullscreenPickerDelegate: class { + func fullscreenPicker(_ picker: FullscreenPicker, willDismissWithSelectedIndex index: Int) +} + +class FullscreenPicker: UIView { + weak var delegate: FullscreenPickerDelegate? + + fileprivate var theme: Theme + + fileprivate var blackoutButton: UIButton! + fileprivate var toolbar: UIToolbar! + fileprivate var picker: UIPickerView! + + fileprivate var pickerBottomConstraint: Constraint! + + fileprivate let stringsArray: [String] + + init(theme: Theme, strings: [String], selectedIndex: Int) { + self.theme = theme + self.stringsArray = strings + + super.init(frame: CGRect.zero) + + configureSelf() + createSubviews() + installConstraints() + + picker.selectRow(selectedIndex, inComponent: 0, animated: false) + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func showAnimatedInView(_ view: UIView) { + view.addSubview(self) + + snp.makeConstraints { + $0.edges.equalTo(view) + } + + show() + } +} + +// MARK: Actions +extension FullscreenPicker { + @objc func doneButtonPressed() { + delegate?.fullscreenPicker(self, willDismissWithSelectedIndex: picker.selectedRow(inComponent: 0)) + hide() + } +} + +extension FullscreenPicker: UIPickerViewDataSource { + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return stringsArray.count + } +} + +extension FullscreenPicker: UIPickerViewDelegate { + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + return stringsArray[row] + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + picker.reloadAllComponents() + } +} + +private extension FullscreenPicker { + func configureSelf() { + backgroundColor = .clear + } + + func createSubviews() { + blackoutButton = UIButton() + blackoutButton.backgroundColor = theme.colorForType(.TranslucentBackground) + blackoutButton.addTarget(self, action: #selector(FullscreenPicker.doneButtonPressed), for:.touchUpInside) + blackoutButton.accessibilityElementsHidden = true + blackoutButton.isAccessibilityElement = false + addSubview(blackoutButton) + + toolbar = UIToolbar() + toolbar.tintColor = theme.colorForType(.LoginButtonText) + toolbar.barTintColor = theme.loginNavigationBarColor + toolbar.items = [ + UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil), + UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(FullscreenPicker.doneButtonPressed)) + ] + addSubview(toolbar) + + picker = UIPickerView() + // Picker is always white, despite choosen theme + picker.backgroundColor = .white + picker.delegate = self + picker.dataSource = self + addSubview(picker) + } + + func installConstraints() { + blackoutButton.snp.makeConstraints { + $0.edges.equalTo(self) + } + + toolbar.snp.makeConstraints { + $0.bottom.equalTo(self.picker.snp.top) + $0.height.equalTo(Constants.ToolbarHeight) + $0.width.equalTo(self) + } + + picker.snp.makeConstraints { + $0.width.equalTo(self) + pickerBottomConstraint = $0.bottom.equalTo(self).constraint + } + } + + func show() { + blackoutButton.alpha = 0.0 + pickerBottomConstraint.update(offset: picker.frame.size.height + Constants.ToolbarHeight) + + layoutIfNeeded() + + UIView.animate(withDuration: Constants.AnimationDuration, animations: { + self.blackoutButton.alpha = 1.0 + self.pickerBottomConstraint.update(offset: 0.0) + + self.layoutIfNeeded() + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, self.picker); + }) + } + + func hide() { + UIView.animate(withDuration: Constants.AnimationDuration, animations: { + self.blackoutButton.alpha = 0.0 + self.pickerBottomConstraint.update(offset: self.picker.frame.size.height + Constants.ToolbarHeight) + + self.layoutIfNeeded() + }, completion: { finished in + self.removeFromSuperview() + }) + } +} diff --git a/Antidote/HelperFunctions.swift b/Antidote/HelperFunctions.swift new file mode 100644 index 0000000..fc9d1c6 --- /dev/null +++ b/Antidote/HelperFunctions.swift @@ -0,0 +1,19 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +func isAddressString(_ string: String) -> Bool { + let nsstring = string as NSString + + if nsstring.length != Int(kOCTToxAddressLength) { + return false + } + + let validChars = CharacterSet(charactersIn: "1234567890abcdefABCDEF") + let components = nsstring.components(separatedBy: validChars) + let leftChars = components.joined(separator: "") + + return leftChars.isEmpty +} diff --git a/Antidote/ImageViewWithStatus.swift b/Antidote/ImageViewWithStatus.swift new file mode 100644 index 0000000..5fc96a3 --- /dev/null +++ b/Antidote/ImageViewWithStatus.swift @@ -0,0 +1,61 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let Sqrt2: CGFloat = 1.4142135623731 +} + +class ImageViewWithStatus: UIView { + var imageView: UIImageView! + var userStatusView: UserStatusView! + + fileprivate var userStatusViewCenterConstrant: Constraint! + + init() { + super.init(frame: CGRect.zero) + + createViews() + installConstraints() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + imageView.layer.cornerRadius = frame.size.width / 2 + + let offset = bounds.size.width / (2 * Constants.Sqrt2) + userStatusViewCenterConstrant.update(offset: offset) + } +} + +private extension ImageViewWithStatus { + func createViews() { + imageView = UIImageView() + imageView.backgroundColor = UIColor.clear + imageView.layer.masksToBounds = true + // imageView.contentMode = .ScaleAspectFit + addSubview(imageView) + + userStatusView = UserStatusView() + addSubview(userStatusView) + } + + func installConstraints() { + imageView.snp.makeConstraints { + $0.edges.equalTo(self) + } + + userStatusView.snp.makeConstraints { + userStatusViewCenterConstrant = $0.center.equalTo(self).constraint + $0.size.equalTo(UserStatusView.Constants.DefaultSize) + } + } +} diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Contents.json b/Antidote/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..1e2d708 --- /dev/null +++ b/Antidote/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "filename" : "Icon-App-20x20@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-20x20@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-29x29@1x.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-40x40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-40x40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-60x60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "Icon-App-60x60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "Icon-App-20x20@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-20x20@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-29x29@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-40x40@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-40x40@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-76x76@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "Icon-App-76x76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "Icon-App-83.5x83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "ItunesArtwork@2x.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..e020aab Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..a19b88b Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..d884c9f Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..7cf5f9d Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..cfa18de Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..124f559 Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..a19b88b Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..d08db3a Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..be45a31 Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..be45a31 Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..206019e Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..bae0c87 Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..1b50349 Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..321f9d6 Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/Antidote/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/Antidote/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png new file mode 100644 index 0000000..e419b2f Binary files /dev/null and b/Antidote/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png differ diff --git a/Antidote/Images.xcassets/Contents.json b/Antidote/Images.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Antidote/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/Contents.json b/Antidote/Images.xcassets/Controllers/Call/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/end-call.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/end-call.imageset/Contents.json new file mode 100644 index 0000000..c78909a --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/end-call.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "end-call.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "end-call@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/end-call.imageset/end-call.png b/Antidote/Images.xcassets/Controllers/Call/end-call.imageset/end-call.png new file mode 100644 index 0000000..4e1eeba Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/end-call.imageset/end-call.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/end-call.imageset/end-call@2x.png b/Antidote/Images.xcassets/Controllers/Call/end-call.imageset/end-call@2x.png new file mode 100644 index 0000000..aa73e48 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/end-call.imageset/end-call@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/location-call-activated-medium.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/location-call-activated-medium.imageset/Contents.json new file mode 100644 index 0000000..941d0d3 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/location-call-activated-medium.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "location-call-activated-medium.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "location-call-activated-medium@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/Antidote/Images.xcassets/Controllers/Call/location-call-activated-medium.imageset/location-call-activated-medium.png b/Antidote/Images.xcassets/Controllers/Call/location-call-activated-medium.imageset/location-call-activated-medium.png new file mode 100644 index 0000000..47f1eac Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/location-call-activated-medium.imageset/location-call-activated-medium.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/location-call-activated-medium.imageset/location-call-activated-medium@2x.png b/Antidote/Images.xcassets/Controllers/Call/location-call-activated-medium.imageset/location-call-activated-medium@2x.png new file mode 100644 index 0000000..1caff37 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/location-call-activated-medium.imageset/location-call-activated-medium@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/location-call-medium.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/location-call-medium.imageset/Contents.json new file mode 100644 index 0000000..9d5ade5 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/location-call-medium.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "location-call-medium.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "location-call-medium@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/Antidote/Images.xcassets/Controllers/Call/location-call-medium.imageset/location-call-medium.png b/Antidote/Images.xcassets/Controllers/Call/location-call-medium.imageset/location-call-medium.png new file mode 100644 index 0000000..056bbd4 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/location-call-medium.imageset/location-call-medium.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/location-call-medium.imageset/location-call-medium@2x.png b/Antidote/Images.xcassets/Controllers/Call/location-call-medium.imageset/location-call-medium@2x.png new file mode 100644 index 0000000..5c76e06 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/location-call-medium.imageset/location-call-medium@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/mute-selected.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/mute-selected.imageset/Contents.json new file mode 100644 index 0000000..06eb2ff --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/mute-selected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "mute-selected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "mute-selected@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Antidote/Images.xcassets/Controllers/Call/mute-selected.imageset/mute-selected.png b/Antidote/Images.xcassets/Controllers/Call/mute-selected.imageset/mute-selected.png new file mode 100644 index 0000000..27ff964 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/mute-selected.imageset/mute-selected.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/mute-selected.imageset/mute-selected@2x.png b/Antidote/Images.xcassets/Controllers/Call/mute-selected.imageset/mute-selected@2x.png new file mode 100644 index 0000000..6785f1f Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/mute-selected.imageset/mute-selected@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/mute.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/mute.imageset/Contents.json new file mode 100644 index 0000000..46ba9a2 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/mute.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "mute.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "mute@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/mute.imageset/mute.png b/Antidote/Images.xcassets/Controllers/Call/mute.imageset/mute.png new file mode 100644 index 0000000..5331e14 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/mute.imageset/mute.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/mute.imageset/mute@2x.png b/Antidote/Images.xcassets/Controllers/Call/mute.imageset/mute@2x.png new file mode 100644 index 0000000..0044b8d Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/mute.imageset/mute@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/speaker.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/speaker.imageset/Contents.json new file mode 100644 index 0000000..bbdd92a --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/speaker.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "speaker.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "speaker@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/speaker.imageset/speaker.png b/Antidote/Images.xcassets/Controllers/Call/speaker.imageset/speaker.png new file mode 100644 index 0000000..5f729c9 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/speaker.imageset/speaker.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/speaker.imageset/speaker@2x.png b/Antidote/Images.xcassets/Controllers/Call/speaker.imageset/speaker@2x.png new file mode 100644 index 0000000..74c9eb3 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/speaker.imageset/speaker@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/start-call-30.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/start-call-30.imageset/Contents.json new file mode 100644 index 0000000..d3bb9f0 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/start-call-30.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "start-call-30.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "start-call-30@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/start-call-30.imageset/start-call-30.png b/Antidote/Images.xcassets/Controllers/Call/start-call-30.imageset/start-call-30.png new file mode 100644 index 0000000..b212cb1 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/start-call-30.imageset/start-call-30.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/start-call-30.imageset/start-call-30@2x.png b/Antidote/Images.xcassets/Controllers/Call/start-call-30.imageset/start-call-30@2x.png new file mode 100644 index 0000000..55f10aa Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/start-call-30.imageset/start-call-30@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/start-call-medium.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/start-call-medium.imageset/Contents.json new file mode 100644 index 0000000..ead60a1 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/start-call-medium.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "start-call-medium.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "start-call-medium@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/start-call-medium.imageset/start-call-medium.png b/Antidote/Images.xcassets/Controllers/Call/start-call-medium.imageset/start-call-medium.png new file mode 100644 index 0000000..8c199e2 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/start-call-medium.imageset/start-call-medium.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/start-call-medium.imageset/start-call-medium@2x.png b/Antidote/Images.xcassets/Controllers/Call/start-call-medium.imageset/start-call-medium@2x.png new file mode 100644 index 0000000..5e9b1fe Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/start-call-medium.imageset/start-call-medium@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/start-call-small.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/start-call-small.imageset/Contents.json new file mode 100644 index 0000000..4b572d6 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/start-call-small.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "start-call-small.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "start-call-small@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/start-call-small.imageset/start-call-small.png b/Antidote/Images.xcassets/Controllers/Call/start-call-small.imageset/start-call-small.png new file mode 100644 index 0000000..a1b8e6c Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/start-call-small.imageset/start-call-small.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/start-call-small.imageset/start-call-small@2x.png b/Antidote/Images.xcassets/Controllers/Call/start-call-small.imageset/start-call-small@2x.png new file mode 100644 index 0000000..0fc6e39 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/start-call-small.imageset/start-call-small@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/start-call.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/start-call.imageset/Contents.json new file mode 100644 index 0000000..f9487b6 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/start-call.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "start-call.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "start-call@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/start-call.imageset/start-call.png b/Antidote/Images.xcassets/Controllers/Call/start-call.imageset/start-call.png new file mode 100644 index 0000000..5e9b1fe Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/start-call.imageset/start-call.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/start-call.imageset/start-call@2x.png b/Antidote/Images.xcassets/Controllers/Call/start-call.imageset/start-call@2x.png new file mode 100644 index 0000000..cbcc46e Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/start-call.imageset/start-call@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/switch-camera.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/switch-camera.imageset/Contents.json new file mode 100644 index 0000000..db3d119 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/switch-camera.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "switch-camera.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "switch-camera@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/switch-camera.imageset/switch-camera.png b/Antidote/Images.xcassets/Controllers/Call/switch-camera.imageset/switch-camera.png new file mode 100644 index 0000000..0df2d83 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/switch-camera.imageset/switch-camera.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/switch-camera.imageset/switch-camera@2x.png b/Antidote/Images.xcassets/Controllers/Call/switch-camera.imageset/switch-camera@2x.png new file mode 100644 index 0000000..6430ba5 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/switch-camera.imageset/switch-camera@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/video-call-30.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/video-call-30.imageset/Contents.json new file mode 100644 index 0000000..eaed03b --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/video-call-30.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "video-call-30.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "video-call-30@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/video-call-30.imageset/video-call-30.png b/Antidote/Images.xcassets/Controllers/Call/video-call-30.imageset/video-call-30.png new file mode 100644 index 0000000..2e9f952 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/video-call-30.imageset/video-call-30.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/video-call-30.imageset/video-call-30@2x.png b/Antidote/Images.xcassets/Controllers/Call/video-call-30.imageset/video-call-30@2x.png new file mode 100644 index 0000000..b1293d2 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/video-call-30.imageset/video-call-30@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/video-call-medium.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/video-call-medium.imageset/Contents.json new file mode 100644 index 0000000..7bbb6e9 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/video-call-medium.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "video-call-medium.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "video-call-medium@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/video-call-medium.imageset/video-call-medium.png b/Antidote/Images.xcassets/Controllers/Call/video-call-medium.imageset/video-call-medium.png new file mode 100644 index 0000000..2b54444 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/video-call-medium.imageset/video-call-medium.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/video-call-medium.imageset/video-call-medium@2x.png b/Antidote/Images.xcassets/Controllers/Call/video-call-medium.imageset/video-call-medium@2x.png new file mode 100644 index 0000000..28c9a53 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/video-call-medium.imageset/video-call-medium@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/video-call.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Call/video-call.imageset/Contents.json new file mode 100644 index 0000000..2c0b182 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Call/video-call.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "video-call.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "video-call@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Call/video-call.imageset/video-call.png b/Antidote/Images.xcassets/Controllers/Call/video-call.imageset/video-call.png new file mode 100644 index 0000000..28c9a53 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/video-call.imageset/video-call.png differ diff --git a/Antidote/Images.xcassets/Controllers/Call/video-call.imageset/video-call@2x.png b/Antidote/Images.xcassets/Controllers/Call/video-call.imageset/video-call@2x.png new file mode 100644 index 0000000..49cf9ef Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Call/video-call.imageset/video-call@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-camera.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-camera.imageset/Contents.json new file mode 100644 index 0000000..dd672cc --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-camera.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-camera.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-camera@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-camera.imageset/chat-camera.png b/Antidote/Images.xcassets/Controllers/Chat/chat-camera.imageset/chat-camera.png new file mode 100644 index 0000000..38f64f2 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-camera.imageset/chat-camera.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-camera.imageset/chat-camera@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-camera.imageset/chat-camera@2x.png new file mode 100644 index 0000000..b9dc678 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-camera.imageset/chat-camera@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-delivered-checkmark.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-delivered-checkmark.imageset/Contents.json new file mode 100644 index 0000000..5f301cc --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-delivered-checkmark.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-delivered-checkmark.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-delivered-checkmark@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-delivered-checkmark.imageset/chat-delivered-checkmark.png b/Antidote/Images.xcassets/Controllers/Chat/chat-delivered-checkmark.imageset/chat-delivered-checkmark.png new file mode 100644 index 0000000..731de1b Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-delivered-checkmark.imageset/chat-delivered-checkmark.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-delivered-checkmark.imageset/chat-delivered-checkmark@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-delivered-checkmark.imageset/chat-delivered-checkmark@2x.png new file mode 100644 index 0000000..a5ef6fa Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-delivered-checkmark.imageset/chat-delivered-checkmark@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-cancel.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-cancel.imageset/Contents.json new file mode 100644 index 0000000..7168d6c --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-cancel.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-cancel.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-cancel@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-cancel.imageset/chat-file-cancel.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-cancel.imageset/chat-file-cancel.png new file mode 100644 index 0000000..5029a46 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-cancel.imageset/chat-file-cancel.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-cancel.imageset/chat-file-cancel@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-cancel.imageset/chat-file-cancel@2x.png new file mode 100644 index 0000000..f3faa17 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-cancel.imageset/chat-file-cancel@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-download-big.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-download-big.imageset/Contents.json new file mode 100644 index 0000000..51aee17 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-download-big.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-download-big.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-download-big@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-download-big.imageset/chat-file-download-big.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-download-big.imageset/chat-file-download-big.png new file mode 100644 index 0000000..717aa6a Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-download-big.imageset/chat-file-download-big.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-download-big.imageset/chat-file-download-big@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-download-big.imageset/chat-file-download-big@2x.png new file mode 100644 index 0000000..065c39f Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-download-big.imageset/chat-file-download-big@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-download.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-download.imageset/Contents.json new file mode 100644 index 0000000..c00be9f --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-download.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-download.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-download@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-download.imageset/chat-file-download.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-download.imageset/chat-file-download.png new file mode 100644 index 0000000..d300550 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-download.imageset/chat-file-download.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-download.imageset/chat-file-download@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-download.imageset/chat-file-download@2x.png new file mode 100644 index 0000000..4b58f3c Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-download.imageset/chat-file-download@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause-big.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause-big.imageset/Contents.json new file mode 100644 index 0000000..462f9d7 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause-big.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-pause-big.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-pause-big@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause-big.imageset/chat-file-pause-big.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause-big.imageset/chat-file-pause-big.png new file mode 100644 index 0000000..60c149b Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause-big.imageset/chat-file-pause-big.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause-big.imageset/chat-file-pause-big@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause-big.imageset/chat-file-pause-big@2x.png new file mode 100644 index 0000000..592305d Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause-big.imageset/chat-file-pause-big@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause.imageset/Contents.json new file mode 100644 index 0000000..36c3cfd --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-pause.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-pause@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause.imageset/chat-file-pause.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause.imageset/chat-file-pause.png new file mode 100644 index 0000000..719e54f Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause.imageset/chat-file-pause.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause.imageset/chat-file-pause@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause.imageset/chat-file-pause@2x.png new file mode 100644 index 0000000..60c149b Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-pause.imageset/chat-file-pause@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-play-big.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-play-big.imageset/Contents.json new file mode 100644 index 0000000..813c2a9 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-play-big.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-play-big.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-play-big@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-play-big.imageset/chat-file-play-big.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-play-big.imageset/chat-file-play-big.png new file mode 100644 index 0000000..190b3fb Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-play-big.imageset/chat-file-play-big.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-play-big.imageset/chat-file-play-big@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-play-big.imageset/chat-file-play-big@2x.png new file mode 100644 index 0000000..afa50ea Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-play-big.imageset/chat-file-play-big@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-play.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-play.imageset/Contents.json new file mode 100644 index 0000000..35d6ca4 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-play.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-play.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-play@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-play.imageset/chat-file-play.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-play.imageset/chat-file-play.png new file mode 100644 index 0000000..71ea45a Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-play.imageset/chat-file-play.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-play.imageset/chat-file-play@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-play.imageset/chat-file-play@2x.png new file mode 100644 index 0000000..190b3fb Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-play.imageset/chat-file-play@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-retry.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-retry.imageset/Contents.json new file mode 100644 index 0000000..0c4890f --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-retry.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-retry.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-retry@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-retry.imageset/chat-file-retry.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-retry.imageset/chat-file-retry.png new file mode 100644 index 0000000..18b07b3 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-retry.imageset/chat-file-retry.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-retry.imageset/chat-file-retry@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-retry.imageset/chat-file-retry@2x.png new file mode 100644 index 0000000..c033cf0 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-retry.imageset/chat-file-retry@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-7zip.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-7zip.imageset/Contents.json new file mode 100644 index 0000000..63b8a02 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-7zip.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-7zip.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-7zip@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-7zip.imageset/chat-file-type-7zip.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-7zip.imageset/chat-file-type-7zip.png new file mode 100644 index 0000000..a0ea62e Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-7zip.imageset/chat-file-type-7zip.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-7zip.imageset/chat-file-type-7zip@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-7zip.imageset/chat-file-type-7zip@2x.png new file mode 100644 index 0000000..2a82a52 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-7zip.imageset/chat-file-type-7zip@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-aac.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-aac.imageset/Contents.json new file mode 100644 index 0000000..3a7789d --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-aac.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-aac.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-aac@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-aac.imageset/chat-file-type-aac.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-aac.imageset/chat-file-type-aac.png new file mode 100644 index 0000000..6b6cd60 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-aac.imageset/chat-file-type-aac.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-aac.imageset/chat-file-type-aac@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-aac.imageset/chat-file-type-aac@2x.png new file mode 100644 index 0000000..656cb3f Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-aac.imageset/chat-file-type-aac@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-avi.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-avi.imageset/Contents.json new file mode 100644 index 0000000..c416e56 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-avi.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-avi.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-avi@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-avi.imageset/chat-file-type-avi.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-avi.imageset/chat-file-type-avi.png new file mode 100644 index 0000000..fd1e080 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-avi.imageset/chat-file-type-avi.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-avi.imageset/chat-file-type-avi@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-avi.imageset/chat-file-type-avi@2x.png new file mode 100644 index 0000000..3c6e6a2 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-avi.imageset/chat-file-type-avi@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-basic.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-basic.imageset/Contents.json new file mode 100644 index 0000000..26ac8a1 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-basic.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-basic.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-basic@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-basic.imageset/chat-file-type-basic.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-basic.imageset/chat-file-type-basic.png new file mode 100644 index 0000000..1d6e233 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-basic.imageset/chat-file-type-basic.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-basic.imageset/chat-file-type-basic@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-basic.imageset/chat-file-type-basic@2x.png new file mode 100644 index 0000000..cbab0c0 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-basic.imageset/chat-file-type-basic@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-canceled.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-canceled.imageset/Contents.json new file mode 100644 index 0000000..f78cd4a --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-canceled.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-canceled.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-canceled@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-canceled.imageset/chat-file-type-canceled.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-canceled.imageset/chat-file-type-canceled.png new file mode 100644 index 0000000..eb5b078 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-canceled.imageset/chat-file-type-canceled.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-canceled.imageset/chat-file-type-canceled@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-canceled.imageset/chat-file-type-canceled@2x.png new file mode 100644 index 0000000..5d9954e Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-canceled.imageset/chat-file-type-canceled@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-css.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-css.imageset/Contents.json new file mode 100644 index 0000000..ec0d8f7 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-css.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-css.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-css@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-css.imageset/chat-file-type-css.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-css.imageset/chat-file-type-css.png new file mode 100644 index 0000000..864e72e Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-css.imageset/chat-file-type-css.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-css.imageset/chat-file-type-css@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-css.imageset/chat-file-type-css@2x.png new file mode 100644 index 0000000..a5ed9eb Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-css.imageset/chat-file-type-css@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-csv.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-csv.imageset/Contents.json new file mode 100644 index 0000000..d2cc70b --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-csv.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-csv.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-csv@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-csv.imageset/chat-file-type-csv.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-csv.imageset/chat-file-type-csv.png new file mode 100644 index 0000000..43c7707 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-csv.imageset/chat-file-type-csv.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-csv.imageset/chat-file-type-csv@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-csv.imageset/chat-file-type-csv@2x.png new file mode 100644 index 0000000..d0d056c Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-csv.imageset/chat-file-type-csv@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-doc.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-doc.imageset/Contents.json new file mode 100644 index 0000000..9843dc7 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-doc.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-doc.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-doc@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-doc.imageset/chat-file-type-doc.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-doc.imageset/chat-file-type-doc.png new file mode 100644 index 0000000..3725503 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-doc.imageset/chat-file-type-doc.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-doc.imageset/chat-file-type-doc@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-doc.imageset/chat-file-type-doc@2x.png new file mode 100644 index 0000000..b7d7c17 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-doc.imageset/chat-file-type-doc@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ebup.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ebup.imageset/Contents.json new file mode 100644 index 0000000..fb84337 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ebup.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-ebup.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-ebup@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ebup.imageset/chat-file-type-ebup.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ebup.imageset/chat-file-type-ebup.png new file mode 100644 index 0000000..fe07310 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ebup.imageset/chat-file-type-ebup.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ebup.imageset/chat-file-type-ebup@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ebup.imageset/chat-file-type-ebup@2x.png new file mode 100644 index 0000000..2b4ec3d Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ebup.imageset/chat-file-type-ebup@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-exe.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-exe.imageset/Contents.json new file mode 100644 index 0000000..63ff7e0 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-exe.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-exe.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-exe@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-exe.imageset/chat-file-type-exe.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-exe.imageset/chat-file-type-exe.png new file mode 100644 index 0000000..e2d3378 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-exe.imageset/chat-file-type-exe.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-exe.imageset/chat-file-type-exe@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-exe.imageset/chat-file-type-exe@2x.png new file mode 100644 index 0000000..fa3b0f8 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-exe.imageset/chat-file-type-exe@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-fb2.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-fb2.imageset/Contents.json new file mode 100644 index 0000000..21ad19d --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-fb2.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-fb2.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-fb2@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-fb2.imageset/chat-file-type-fb2.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-fb2.imageset/chat-file-type-fb2.png new file mode 100644 index 0000000..34d0038 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-fb2.imageset/chat-file-type-fb2.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-fb2.imageset/chat-file-type-fb2@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-fb2.imageset/chat-file-type-fb2@2x.png new file mode 100644 index 0000000..a9a1bcd Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-fb2.imageset/chat-file-type-fb2@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-flv.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-flv.imageset/Contents.json new file mode 100644 index 0000000..0f196f5 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-flv.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-flv.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-flv@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-flv.imageset/chat-file-type-flv.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-flv.imageset/chat-file-type-flv.png new file mode 100644 index 0000000..7678ac2 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-flv.imageset/chat-file-type-flv.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-flv.imageset/chat-file-type-flv@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-flv.imageset/chat-file-type-flv@2x.png new file mode 100644 index 0000000..f324aed Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-flv.imageset/chat-file-type-flv@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-gif.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-gif.imageset/Contents.json new file mode 100644 index 0000000..28bc957 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-gif.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-gif.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-gif@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-gif.imageset/chat-file-type-gif.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-gif.imageset/chat-file-type-gif.png new file mode 100644 index 0000000..8771231 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-gif.imageset/chat-file-type-gif.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-gif.imageset/chat-file-type-gif@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-gif.imageset/chat-file-type-gif@2x.png new file mode 100644 index 0000000..ce811de Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-gif.imageset/chat-file-type-gif@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-html.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-html.imageset/Contents.json new file mode 100644 index 0000000..106986e --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-html.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-html.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-html@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-html.imageset/chat-file-type-html.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-html.imageset/chat-file-type-html.png new file mode 100644 index 0000000..d9e7b39 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-html.imageset/chat-file-type-html.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-html.imageset/chat-file-type-html@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-html.imageset/chat-file-type-html@2x.png new file mode 100644 index 0000000..4a49101 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-html.imageset/chat-file-type-html@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-jpg.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-jpg.imageset/Contents.json new file mode 100644 index 0000000..6975865 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-jpg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-jpg.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-jpg@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-jpg.imageset/chat-file-type-jpg.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-jpg.imageset/chat-file-type-jpg.png new file mode 100644 index 0000000..f3f6b34 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-jpg.imageset/chat-file-type-jpg.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-jpg.imageset/chat-file-type-jpg@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-jpg.imageset/chat-file-type-jpg@2x.png new file mode 100644 index 0000000..fc5c436 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-jpg.imageset/chat-file-type-jpg@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mov.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mov.imageset/Contents.json new file mode 100644 index 0000000..cfea380 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mov.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-mov.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-mov@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mov.imageset/chat-file-type-mov.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mov.imageset/chat-file-type-mov.png new file mode 100644 index 0000000..f640ac2 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mov.imageset/chat-file-type-mov.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mov.imageset/chat-file-type-mov@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mov.imageset/chat-file-type-mov@2x.png new file mode 100644 index 0000000..3a6088d Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mov.imageset/chat-file-type-mov@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mp3.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mp3.imageset/Contents.json new file mode 100644 index 0000000..c58d075 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mp3.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-mp3.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-mp3@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mp3.imageset/chat-file-type-mp3.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mp3.imageset/chat-file-type-mp3.png new file mode 100644 index 0000000..96d73a8 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mp3.imageset/chat-file-type-mp3.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mp3.imageset/chat-file-type-mp3@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mp3.imageset/chat-file-type-mp3@2x.png new file mode 100644 index 0000000..33c6bb8 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mp3.imageset/chat-file-type-mp3@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mpg.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mpg.imageset/Contents.json new file mode 100644 index 0000000..5162000 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mpg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-mpg.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-mpg@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mpg.imageset/chat-file-type-mpg.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mpg.imageset/chat-file-type-mpg.png new file mode 100644 index 0000000..e9bdc7f Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mpg.imageset/chat-file-type-mpg.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mpg.imageset/chat-file-type-mpg@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mpg.imageset/chat-file-type-mpg@2x.png new file mode 100644 index 0000000..81eba60 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-mpg.imageset/chat-file-type-mpg@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ogg.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ogg.imageset/Contents.json new file mode 100644 index 0000000..e539122 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ogg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-ogg.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-ogg@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ogg.imageset/chat-file-type-ogg.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ogg.imageset/chat-file-type-ogg.png new file mode 100644 index 0000000..ce0b629 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ogg.imageset/chat-file-type-ogg.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ogg.imageset/chat-file-type-ogg@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ogg.imageset/chat-file-type-ogg@2x.png new file mode 100644 index 0000000..0f2c7c0 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ogg.imageset/chat-file-type-ogg@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-otf.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-otf.imageset/Contents.json new file mode 100644 index 0000000..427bb19 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-otf.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-otf.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-otf@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-otf.imageset/chat-file-type-otf.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-otf.imageset/chat-file-type-otf.png new file mode 100644 index 0000000..1c9f669 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-otf.imageset/chat-file-type-otf.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-otf.imageset/chat-file-type-otf@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-otf.imageset/chat-file-type-otf@2x.png new file mode 100644 index 0000000..3970c38 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-otf.imageset/chat-file-type-otf@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-pdf.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-pdf.imageset/Contents.json new file mode 100644 index 0000000..55258d1 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-pdf.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-pdf.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-pdf@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-pdf.imageset/chat-file-type-pdf.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-pdf.imageset/chat-file-type-pdf.png new file mode 100644 index 0000000..77e2d23 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-pdf.imageset/chat-file-type-pdf.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-pdf.imageset/chat-file-type-pdf@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-pdf.imageset/chat-file-type-pdf@2x.png new file mode 100644 index 0000000..bc0a147 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-pdf.imageset/chat-file-type-pdf@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-png.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-png.imageset/Contents.json new file mode 100644 index 0000000..9500234 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-png.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-png.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-png@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-png.imageset/chat-file-type-png.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-png.imageset/chat-file-type-png.png new file mode 100644 index 0000000..136e37c Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-png.imageset/chat-file-type-png.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-png.imageset/chat-file-type-png@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-png.imageset/chat-file-type-png@2x.png new file mode 100644 index 0000000..c85b021 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-png.imageset/chat-file-type-png@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ppt.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ppt.imageset/Contents.json new file mode 100644 index 0000000..a4fd53f --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ppt.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-ppt.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-ppt@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ppt.imageset/chat-file-type-ppt.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ppt.imageset/chat-file-type-ppt.png new file mode 100644 index 0000000..82ac784 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ppt.imageset/chat-file-type-ppt.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ppt.imageset/chat-file-type-ppt@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ppt.imageset/chat-file-type-ppt@2x.png new file mode 100644 index 0000000..4b46996 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ppt.imageset/chat-file-type-ppt@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-psd.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-psd.imageset/Contents.json new file mode 100644 index 0000000..d0be5aa --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-psd.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-psd.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-psd@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-psd.imageset/chat-file-type-psd.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-psd.imageset/chat-file-type-psd.png new file mode 100644 index 0000000..74362aa Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-psd.imageset/chat-file-type-psd.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-psd.imageset/chat-file-type-psd@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-psd.imageset/chat-file-type-psd@2x.png new file mode 100644 index 0000000..a627ef1 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-psd.imageset/chat-file-type-psd@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-rar.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-rar.imageset/Contents.json new file mode 100644 index 0000000..b7e3d4d --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-rar.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-rar.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-rar@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-rar.imageset/chat-file-type-rar.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-rar.imageset/chat-file-type-rar.png new file mode 100644 index 0000000..70eb1c6 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-rar.imageset/chat-file-type-rar.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-rar.imageset/chat-file-type-rar@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-rar.imageset/chat-file-type-rar@2x.png new file mode 100644 index 0000000..c6ec965 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-rar.imageset/chat-file-type-rar@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tar.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tar.imageset/Contents.json new file mode 100644 index 0000000..44a0a2a --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tar.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-tar.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-tar@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tar.imageset/chat-file-type-tar.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tar.imageset/chat-file-type-tar.png new file mode 100644 index 0000000..e89e298 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tar.imageset/chat-file-type-tar.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tar.imageset/chat-file-type-tar@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tar.imageset/chat-file-type-tar@2x.png new file mode 100644 index 0000000..9469597 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tar.imageset/chat-file-type-tar@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tif.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tif.imageset/Contents.json new file mode 100644 index 0000000..bd4d7b0 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tif.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-tif.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-tif@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tif.imageset/chat-file-type-tif.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tif.imageset/chat-file-type-tif.png new file mode 100644 index 0000000..e784c13 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tif.imageset/chat-file-type-tif.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tif.imageset/chat-file-type-tif@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tif.imageset/chat-file-type-tif@2x.png new file mode 100644 index 0000000..6230a1f Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-tif.imageset/chat-file-type-tif@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ttf.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ttf.imageset/Contents.json new file mode 100644 index 0000000..bc7ac8c --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ttf.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-ttf.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-ttf@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ttf.imageset/chat-file-type-ttf.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ttf.imageset/chat-file-type-ttf.png new file mode 100644 index 0000000..e6ba5df Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ttf.imageset/chat-file-type-ttf.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ttf.imageset/chat-file-type-ttf@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ttf.imageset/chat-file-type-ttf@2x.png new file mode 100644 index 0000000..a264bcb Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-ttf.imageset/chat-file-type-ttf@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-txt.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-txt.imageset/Contents.json new file mode 100644 index 0000000..6af2113 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-txt.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-txt.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-txt@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-txt.imageset/chat-file-type-txt.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-txt.imageset/chat-file-type-txt.png new file mode 100644 index 0000000..06d5078 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-txt.imageset/chat-file-type-txt.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-txt.imageset/chat-file-type-txt@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-txt.imageset/chat-file-type-txt@2x.png new file mode 100644 index 0000000..282eafd Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-txt.imageset/chat-file-type-txt@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wav.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wav.imageset/Contents.json new file mode 100644 index 0000000..b5a34dc --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wav.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-wav.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-wav@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wav.imageset/chat-file-type-wav.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wav.imageset/chat-file-type-wav.png new file mode 100644 index 0000000..b83f8f7 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wav.imageset/chat-file-type-wav.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wav.imageset/chat-file-type-wav@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wav.imageset/chat-file-type-wav@2x.png new file mode 100644 index 0000000..903cb2d Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wav.imageset/chat-file-type-wav@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wma.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wma.imageset/Contents.json new file mode 100644 index 0000000..39136fe --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wma.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-wma.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-wma@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wma.imageset/chat-file-type-wma.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wma.imageset/chat-file-type-wma.png new file mode 100644 index 0000000..691fe3d Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wma.imageset/chat-file-type-wma.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wma.imageset/chat-file-type-wma@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wma.imageset/chat-file-type-wma@2x.png new file mode 100644 index 0000000..16671c2 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-wma.imageset/chat-file-type-wma@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-xls.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-xls.imageset/Contents.json new file mode 100644 index 0000000..4ecaf84 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-xls.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-xls.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-xls@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-xls.imageset/chat-file-type-xls.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-xls.imageset/chat-file-type-xls.png new file mode 100644 index 0000000..98814f3 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-xls.imageset/chat-file-type-xls.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-xls.imageset/chat-file-type-xls@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-xls.imageset/chat-file-type-xls@2x.png new file mode 100644 index 0000000..63d4da1 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-xls.imageset/chat-file-type-xls@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-zip.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-zip.imageset/Contents.json new file mode 100644 index 0000000..1dce0e8 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-zip.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat-file-type-zip.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "chat-file-type-zip@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-zip.imageset/chat-file-type-zip.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-zip.imageset/chat-file-type-zip.png new file mode 100644 index 0000000..5f7d0c9 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-zip.imageset/chat-file-type-zip.png differ diff --git a/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-zip.imageset/chat-file-type-zip@2x.png b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-zip.imageset/chat-file-type-zip@2x.png new file mode 100644 index 0000000..77be3c5 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Chat/chat-file-type-zip.imageset/chat-file-type-zip@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Contents.json b/Antidote/Images.xcassets/Controllers/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/FriendCard/friend-card-chat.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/FriendCard/friend-card-chat.imageset/Contents.json new file mode 100644 index 0000000..905b96c --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/FriendCard/friend-card-chat.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "friend-card-chat.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "friend-card-chat@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/FriendCard/friend-card-chat.imageset/friend-card-chat.png b/Antidote/Images.xcassets/Controllers/FriendCard/friend-card-chat.imageset/friend-card-chat.png new file mode 100644 index 0000000..535ab44 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/FriendCard/friend-card-chat.imageset/friend-card-chat.png differ diff --git a/Antidote/Images.xcassets/Controllers/FriendCard/friend-card-chat.imageset/friend-card-chat@2x.png b/Antidote/Images.xcassets/Controllers/FriendCard/friend-card-chat.imageset/friend-card-chat@2x.png new file mode 100644 index 0000000..1c48e87 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/FriendCard/friend-card-chat.imageset/friend-card-chat@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Login/login-logo.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Login/login-logo.imageset/Contents.json new file mode 100644 index 0000000..2cad525 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Login/login-logo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "login-logo.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "login-logo@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "login-logo@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Login/login-logo.imageset/login-logo.png b/Antidote/Images.xcassets/Controllers/Login/login-logo.imageset/login-logo.png new file mode 100644 index 0000000..f09ce02 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Login/login-logo.imageset/login-logo.png differ diff --git a/Antidote/Images.xcassets/Controllers/Login/login-logo.imageset/login-logo@2x.png b/Antidote/Images.xcassets/Controllers/Login/login-logo.imageset/login-logo@2x.png new file mode 100644 index 0000000..19f05cb Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Login/login-logo.imageset/login-logo@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Login/login-logo.imageset/login-logo@3x.png b/Antidote/Images.xcassets/Controllers/Login/login-logo.imageset/login-logo@3x.png new file mode 100644 index 0000000..254ceaa Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Login/login-logo.imageset/login-logo@3x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Login/login-password-icon.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Login/login-password-icon.imageset/Contents.json new file mode 100644 index 0000000..a3ba964 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Login/login-password-icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "login-password-icon.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "login-password-icon@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Login/login-password-icon.imageset/login-password-icon.png b/Antidote/Images.xcassets/Controllers/Login/login-password-icon.imageset/login-password-icon.png new file mode 100644 index 0000000..d169fbf Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Login/login-password-icon.imageset/login-password-icon.png differ diff --git a/Antidote/Images.xcassets/Controllers/Login/login-password-icon.imageset/login-password-icon@2x.png b/Antidote/Images.xcassets/Controllers/Login/login-password-icon.imageset/login-password-icon@2x.png new file mode 100644 index 0000000..be01707 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Login/login-password-icon.imageset/login-password-icon@2x.png differ diff --git a/Antidote/Images.xcassets/Controllers/Login/login-profile-icon.imageset/Contents.json b/Antidote/Images.xcassets/Controllers/Login/login-profile-icon.imageset/Contents.json new file mode 100644 index 0000000..f794cc0 --- /dev/null +++ b/Antidote/Images.xcassets/Controllers/Login/login-profile-icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "login-profile-icon.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "login-profile-icon@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Controllers/Login/login-profile-icon.imageset/login-profile-icon.png b/Antidote/Images.xcassets/Controllers/Login/login-profile-icon.imageset/login-profile-icon.png new file mode 100644 index 0000000..24bcc31 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Login/login-profile-icon.imageset/login-profile-icon.png differ diff --git a/Antidote/Images.xcassets/Controllers/Login/login-profile-icon.imageset/login-profile-icon@2x.png b/Antidote/Images.xcassets/Controllers/Login/login-profile-icon.imageset/login-profile-icon@2x.png new file mode 100644 index 0000000..7038944 Binary files /dev/null and b/Antidote/Images.xcassets/Controllers/Login/login-profile-icon.imageset/login-profile-icon@2x.png differ diff --git a/Antidote/Images.xcassets/Mocks/Contents.json b/Antidote/Images.xcassets/Mocks/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Antidote/Images.xcassets/Mocks/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Mocks/female-1.imageset/Contents.json b/Antidote/Images.xcassets/Mocks/female-1.imageset/Contents.json new file mode 100644 index 0000000..39363b0 --- /dev/null +++ b/Antidote/Images.xcassets/Mocks/female-1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "female-1.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Mocks/female-1.imageset/female-1.jpg b/Antidote/Images.xcassets/Mocks/female-1.imageset/female-1.jpg new file mode 100644 index 0000000..f657eae Binary files /dev/null and b/Antidote/Images.xcassets/Mocks/female-1.imageset/female-1.jpg differ diff --git a/Antidote/Images.xcassets/Mocks/female-2.imageset/Contents.json b/Antidote/Images.xcassets/Mocks/female-2.imageset/Contents.json new file mode 100644 index 0000000..e1c88a5 --- /dev/null +++ b/Antidote/Images.xcassets/Mocks/female-2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "female-2.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Mocks/female-2.imageset/female-2.jpg b/Antidote/Images.xcassets/Mocks/female-2.imageset/female-2.jpg new file mode 100644 index 0000000..51757c2 Binary files /dev/null and b/Antidote/Images.xcassets/Mocks/female-2.imageset/female-2.jpg differ diff --git a/Antidote/Images.xcassets/Mocks/female-3.imageset/Contents.json b/Antidote/Images.xcassets/Mocks/female-3.imageset/Contents.json new file mode 100644 index 0000000..a40894a --- /dev/null +++ b/Antidote/Images.xcassets/Mocks/female-3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "female-3.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Mocks/female-3.imageset/female-3.jpg b/Antidote/Images.xcassets/Mocks/female-3.imageset/female-3.jpg new file mode 100644 index 0000000..f112175 Binary files /dev/null and b/Antidote/Images.xcassets/Mocks/female-3.imageset/female-3.jpg differ diff --git a/Antidote/Images.xcassets/Mocks/female-4.imageset/Contents.json b/Antidote/Images.xcassets/Mocks/female-4.imageset/Contents.json new file mode 100644 index 0000000..79886d2 --- /dev/null +++ b/Antidote/Images.xcassets/Mocks/female-4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "female-4.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Mocks/female-4.imageset/female-4.jpg b/Antidote/Images.xcassets/Mocks/female-4.imageset/female-4.jpg new file mode 100644 index 0000000..48eba8b Binary files /dev/null and b/Antidote/Images.xcassets/Mocks/female-4.imageset/female-4.jpg differ diff --git a/Antidote/Images.xcassets/Mocks/female-5.imageset/Contents.json b/Antidote/Images.xcassets/Mocks/female-5.imageset/Contents.json new file mode 100644 index 0000000..ad602cc --- /dev/null +++ b/Antidote/Images.xcassets/Mocks/female-5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "female-5.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Mocks/female-5.imageset/female-5.jpg b/Antidote/Images.xcassets/Mocks/female-5.imageset/female-5.jpg new file mode 100644 index 0000000..0eb2c1e Binary files /dev/null and b/Antidote/Images.xcassets/Mocks/female-5.imageset/female-5.jpg differ diff --git a/Antidote/Images.xcassets/Mocks/male-1.imageset/Contents.json b/Antidote/Images.xcassets/Mocks/male-1.imageset/Contents.json new file mode 100644 index 0000000..e6a6668 --- /dev/null +++ b/Antidote/Images.xcassets/Mocks/male-1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "male-1.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Mocks/male-1.imageset/male-1.jpg b/Antidote/Images.xcassets/Mocks/male-1.imageset/male-1.jpg new file mode 100644 index 0000000..652902e Binary files /dev/null and b/Antidote/Images.xcassets/Mocks/male-1.imageset/male-1.jpg differ diff --git a/Antidote/Images.xcassets/Mocks/male-2.imageset/Contents.json b/Antidote/Images.xcassets/Mocks/male-2.imageset/Contents.json new file mode 100644 index 0000000..7788b6a --- /dev/null +++ b/Antidote/Images.xcassets/Mocks/male-2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "male-2.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Mocks/male-2.imageset/male-2.jpg b/Antidote/Images.xcassets/Mocks/male-2.imageset/male-2.jpg new file mode 100644 index 0000000..bcde0fc Binary files /dev/null and b/Antidote/Images.xcassets/Mocks/male-2.imageset/male-2.jpg differ diff --git a/Antidote/Images.xcassets/Mocks/male-3.imageset/Contents.json b/Antidote/Images.xcassets/Mocks/male-3.imageset/Contents.json new file mode 100644 index 0000000..acc9dce --- /dev/null +++ b/Antidote/Images.xcassets/Mocks/male-3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "male-3.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Mocks/male-3.imageset/male-3.jpg b/Antidote/Images.xcassets/Mocks/male-3.imageset/male-3.jpg new file mode 100644 index 0000000..b831e1a Binary files /dev/null and b/Antidote/Images.xcassets/Mocks/male-3.imageset/male-3.jpg differ diff --git a/Antidote/Images.xcassets/Mocks/male-4.imageset/Contents.json b/Antidote/Images.xcassets/Mocks/male-4.imageset/Contents.json new file mode 100644 index 0000000..080c894 --- /dev/null +++ b/Antidote/Images.xcassets/Mocks/male-4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "male-4.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Mocks/male-4.imageset/male-4.jpg b/Antidote/Images.xcassets/Mocks/male-4.imageset/male-4.jpg new file mode 100644 index 0000000..3eda450 Binary files /dev/null and b/Antidote/Images.xcassets/Mocks/male-4.imageset/male-4.jpg differ diff --git a/Antidote/Images.xcassets/Mocks/male-5.imageset/Contents.json b/Antidote/Images.xcassets/Mocks/male-5.imageset/Contents.json new file mode 100644 index 0000000..2cd3019 --- /dev/null +++ b/Antidote/Images.xcassets/Mocks/male-5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "male-5.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Mocks/male-5.imageset/male-5.jpg b/Antidote/Images.xcassets/Mocks/male-5.imageset/male-5.jpg new file mode 100644 index 0000000..5ee9788 Binary files /dev/null and b/Antidote/Images.xcassets/Mocks/male-5.imageset/male-5.jpg differ diff --git a/Antidote/Images.xcassets/Other/Contents.json b/Antidote/Images.xcassets/Other/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Antidote/Images.xcassets/Other/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Other/checkmark.imageset/Contents.json b/Antidote/Images.xcassets/Other/checkmark.imageset/Contents.json new file mode 100644 index 0000000..d928987 --- /dev/null +++ b/Antidote/Images.xcassets/Other/checkmark.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "checkmark.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "checkmark@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Other/checkmark.imageset/checkmark.png b/Antidote/Images.xcassets/Other/checkmark.imageset/checkmark.png new file mode 100644 index 0000000..6497e2f Binary files /dev/null and b/Antidote/Images.xcassets/Other/checkmark.imageset/checkmark.png differ diff --git a/Antidote/Images.xcassets/Other/checkmark.imageset/checkmark@2x.png b/Antidote/Images.xcassets/Other/checkmark.imageset/checkmark@2x.png new file mode 100644 index 0000000..374eb6f Binary files /dev/null and b/Antidote/Images.xcassets/Other/checkmark.imageset/checkmark@2x.png differ diff --git a/Antidote/Images.xcassets/Other/notification-app-icon.imageset/Contents.json b/Antidote/Images.xcassets/Other/notification-app-icon.imageset/Contents.json new file mode 100644 index 0000000..f2114f6 --- /dev/null +++ b/Antidote/Images.xcassets/Other/notification-app-icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "icon-20.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon-40.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon-80.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Antidote/Images.xcassets/Other/notification-app-icon.imageset/icon-20.png b/Antidote/Images.xcassets/Other/notification-app-icon.imageset/icon-20.png new file mode 100644 index 0000000..58bc7a3 Binary files /dev/null and b/Antidote/Images.xcassets/Other/notification-app-icon.imageset/icon-20.png differ diff --git a/Antidote/Images.xcassets/Other/notification-app-icon.imageset/icon-40.png b/Antidote/Images.xcassets/Other/notification-app-icon.imageset/icon-40.png new file mode 100644 index 0000000..a19b88b Binary files /dev/null and b/Antidote/Images.xcassets/Other/notification-app-icon.imageset/icon-40.png differ diff --git a/Antidote/Images.xcassets/Other/notification-app-icon.imageset/icon-80.png b/Antidote/Images.xcassets/Other/notification-app-icon.imageset/icon-80.png new file mode 100644 index 0000000..d08db3a Binary files /dev/null and b/Antidote/Images.xcassets/Other/notification-app-icon.imageset/icon-80.png differ diff --git a/Antidote/Images.xcassets/Other/notification-close.imageset/Contents.json b/Antidote/Images.xcassets/Other/notification-close.imageset/Contents.json new file mode 100644 index 0000000..e7f48a0 --- /dev/null +++ b/Antidote/Images.xcassets/Other/notification-close.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "notification-close.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "notification-close@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Other/notification-close.imageset/notification-close.png b/Antidote/Images.xcassets/Other/notification-close.imageset/notification-close.png new file mode 100644 index 0000000..3df705c Binary files /dev/null and b/Antidote/Images.xcassets/Other/notification-close.imageset/notification-close.png differ diff --git a/Antidote/Images.xcassets/Other/notification-close.imageset/notification-close@2x.png b/Antidote/Images.xcassets/Other/notification-close.imageset/notification-close@2x.png new file mode 100644 index 0000000..21fca40 Binary files /dev/null and b/Antidote/Images.xcassets/Other/notification-close.imageset/notification-close@2x.png differ diff --git a/Antidote/Images.xcassets/Other/right-arrow.imageset/Contents.json b/Antidote/Images.xcassets/Other/right-arrow.imageset/Contents.json new file mode 100644 index 0000000..c966dd7 --- /dev/null +++ b/Antidote/Images.xcassets/Other/right-arrow.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "right-arrow.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "right-arrow@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/Other/right-arrow.imageset/right-arrow.png b/Antidote/Images.xcassets/Other/right-arrow.imageset/right-arrow.png new file mode 100644 index 0000000..4819169 Binary files /dev/null and b/Antidote/Images.xcassets/Other/right-arrow.imageset/right-arrow.png differ diff --git a/Antidote/Images.xcassets/Other/right-arrow.imageset/right-arrow@2x.png b/Antidote/Images.xcassets/Other/right-arrow.imageset/right-arrow@2x.png new file mode 100644 index 0000000..d19a971 Binary files /dev/null and b/Antidote/Images.xcassets/Other/right-arrow.imageset/right-arrow@2x.png differ diff --git a/Antidote/Images.xcassets/TabBar/tab-bar-chats.imageset/Contents.json b/Antidote/Images.xcassets/TabBar/tab-bar-chats.imageset/Contents.json new file mode 100644 index 0000000..cba6439 --- /dev/null +++ b/Antidote/Images.xcassets/TabBar/tab-bar-chats.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "tab-bar-chats.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "tab-bar-chats@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/TabBar/tab-bar-chats.imageset/tab-bar-chats.png b/Antidote/Images.xcassets/TabBar/tab-bar-chats.imageset/tab-bar-chats.png new file mode 100644 index 0000000..e4f5fe8 Binary files /dev/null and b/Antidote/Images.xcassets/TabBar/tab-bar-chats.imageset/tab-bar-chats.png differ diff --git a/Antidote/Images.xcassets/TabBar/tab-bar-chats.imageset/tab-bar-chats@2x.png b/Antidote/Images.xcassets/TabBar/tab-bar-chats.imageset/tab-bar-chats@2x.png new file mode 100644 index 0000000..bf7b912 Binary files /dev/null and b/Antidote/Images.xcassets/TabBar/tab-bar-chats.imageset/tab-bar-chats@2x.png differ diff --git a/Antidote/Images.xcassets/TabBar/tab-bar-friends.imageset/Contents.json b/Antidote/Images.xcassets/TabBar/tab-bar-friends.imageset/Contents.json new file mode 100644 index 0000000..c5a6ae1 --- /dev/null +++ b/Antidote/Images.xcassets/TabBar/tab-bar-friends.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "tab-bar-friends.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "tab-bar-friends@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/TabBar/tab-bar-friends.imageset/tab-bar-friends.png b/Antidote/Images.xcassets/TabBar/tab-bar-friends.imageset/tab-bar-friends.png new file mode 100644 index 0000000..63a383f Binary files /dev/null and b/Antidote/Images.xcassets/TabBar/tab-bar-friends.imageset/tab-bar-friends.png differ diff --git a/Antidote/Images.xcassets/TabBar/tab-bar-friends.imageset/tab-bar-friends@2x.png b/Antidote/Images.xcassets/TabBar/tab-bar-friends.imageset/tab-bar-friends@2x.png new file mode 100644 index 0000000..bdc5434 Binary files /dev/null and b/Antidote/Images.xcassets/TabBar/tab-bar-friends.imageset/tab-bar-friends@2x.png differ diff --git a/Antidote/Images.xcassets/TabBar/tab-bar-profile.imageset/Contents.json b/Antidote/Images.xcassets/TabBar/tab-bar-profile.imageset/Contents.json new file mode 100644 index 0000000..84e3cc3 --- /dev/null +++ b/Antidote/Images.xcassets/TabBar/tab-bar-profile.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "tab-bar-profile.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "tab-bar-profile@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/TabBar/tab-bar-profile.imageset/tab-bar-profile.png b/Antidote/Images.xcassets/TabBar/tab-bar-profile.imageset/tab-bar-profile.png new file mode 100644 index 0000000..5cc0e33 Binary files /dev/null and b/Antidote/Images.xcassets/TabBar/tab-bar-profile.imageset/tab-bar-profile.png differ diff --git a/Antidote/Images.xcassets/TabBar/tab-bar-profile.imageset/tab-bar-profile@2x.png b/Antidote/Images.xcassets/TabBar/tab-bar-profile.imageset/tab-bar-profile@2x.png new file mode 100644 index 0000000..a3834c9 Binary files /dev/null and b/Antidote/Images.xcassets/TabBar/tab-bar-profile.imageset/tab-bar-profile@2x.png differ diff --git a/Antidote/Images.xcassets/TabBar/tab-bar-settings.imageset/Contents.json b/Antidote/Images.xcassets/TabBar/tab-bar-settings.imageset/Contents.json new file mode 100644 index 0000000..7b550bd --- /dev/null +++ b/Antidote/Images.xcassets/TabBar/tab-bar-settings.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "tab-bar-settings.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "tab-bar-settings@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Antidote/Images.xcassets/TabBar/tab-bar-settings.imageset/tab-bar-settings.png b/Antidote/Images.xcassets/TabBar/tab-bar-settings.imageset/tab-bar-settings.png new file mode 100644 index 0000000..6e21c01 Binary files /dev/null and b/Antidote/Images.xcassets/TabBar/tab-bar-settings.imageset/tab-bar-settings.png differ diff --git a/Antidote/Images.xcassets/TabBar/tab-bar-settings.imageset/tab-bar-settings@2x.png b/Antidote/Images.xcassets/TabBar/tab-bar-settings.imageset/tab-bar-settings@2x.png new file mode 100644 index 0000000..c7d65cd Binary files /dev/null and b/Antidote/Images.xcassets/TabBar/tab-bar-settings.imageset/tab-bar-settings@2x.png differ diff --git a/Antidote/IncompressibleView.swift b/Antidote/IncompressibleView.swift new file mode 100644 index 0000000..d56567f --- /dev/null +++ b/Antidote/IncompressibleView.swift @@ -0,0 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class IncompressibleView: UIView { + var customIntrinsicContentSize: CGSize = CGSize.zero + + override var intrinsicContentSize : CGSize { + return customIntrinsicContentSize + } +} diff --git a/Antidote/InterfaceIdiom.swift b/Antidote/InterfaceIdiom.swift new file mode 100644 index 0000000..577dcd3 --- /dev/null +++ b/Antidote/InterfaceIdiom.swift @@ -0,0 +1,21 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +enum InterfaceIdiom { + case iPhone + case iPad + + static func current() -> InterfaceIdiom { + if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad { + return .iPad + } + else { + // assume that we are on iPhone + return .iPhone + } + } +} + diff --git a/Antidote/KeyboardNotificationController.swift b/Antidote/KeyboardNotificationController.swift new file mode 100644 index 0000000..8997447 --- /dev/null +++ b/Antidote/KeyboardNotificationController.swift @@ -0,0 +1,70 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +class KeyboardNotificationController: UIViewController { + init() { + super.init(nibName: nil, bundle: nil) + + let center = NotificationCenter.default + center.addObserver(self, selector: #selector(KeyboardNotificationController.keyboardWillShowNotification(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil) + center.addObserver(self, selector: #selector(KeyboardNotificationController.keyboardWillHideNotification(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil) + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + let center = NotificationCenter.default + center.removeObserver(self) + } + + func keyboardWillShowAnimated(keyboardFrame frame: CGRect) { + // nop + } + + func keyboardWillHideAnimated(keyboardFrame frame: CGRect) { + // nop + } + + @objc func keyboardWillShowNotification(_ notification: Notification) { + handleNotification(notification, willShow: true) + } + + @objc func keyboardWillHideNotification(_ notification: Notification) { + handleNotification(notification, willShow: false) + } +} + +private extension KeyboardNotificationController { + func handleNotification(_ notification: Notification, willShow: Bool) { + let userInfo = notification.userInfo! + + let frame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue + let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue + let curve = UIViewAnimationCurve(rawValue: (userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).intValue)! + + let options: UIViewAnimationOptions + + switch curve { + case .easeInOut: + options = UIViewAnimationOptions() + case .easeIn: + options = .curveEaseIn + case .easeOut: + options = .curveEaseOut + case .linear: + options = .curveLinear + default: + options = .curveLinear + + } + + UIView.animate(withDuration: duration, delay: 0.0, options: options, animations: { [unowned self] in + willShow ? self.keyboardWillShowAnimated(keyboardFrame: frame) : self.keyboardWillHideAnimated(keyboardFrame: frame) + }, completion: nil) + } +} diff --git a/Antidote/KeyboardObserver.swift b/Antidote/KeyboardObserver.swift new file mode 100644 index 0000000..eba027d --- /dev/null +++ b/Antidote/KeyboardObserver.swift @@ -0,0 +1,36 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// +// KeyboardObserver.swift +// Antidote +// +// Created by Dmytro Vorobiov on 25/11/16. +// Copyright © 2016 dvor. All rights reserved. +// + +import Foundation + +class KeyboardObserver { + private(set) var keyboardVisible = false + + init() { + let center = NotificationCenter.default + center.addObserver(self, selector: #selector(KeyboardObserver.keyboardWillShowNotification(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil) + center.addObserver(self, selector: #selector(KeyboardObserver.keyboardWillHideNotification(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil) + } + + deinit { + let center = NotificationCenter.default + center.removeObserver(self) + } + + @objc func keyboardWillShowNotification(_ notification: Notification) { + keyboardVisible = true + } + + @objc func keyboardWillHideNotification(_ notification: Notification) { + keyboardVisible = false + } +} diff --git a/Antidote/KeychainManager.swift b/Antidote/KeychainManager.swift new file mode 100644 index 0000000..677ef3c --- /dev/null +++ b/Antidote/KeychainManager.swift @@ -0,0 +1,182 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +private struct Constants { + static let ActiveAccountDataService = "org.zoxcore.Antidote.KeychainManager.ActiveAccountDataService" + + static let toxPasswordForActiveAccount = "toxPasswordForActiveAccount" + static let failedPinAttemptsNumber = "failedPinAttemptsNumber" +} + +class KeychainManager { + /// Tox password used to encrypt/decrypt active account. + var toxPasswordForActiveAccount: String? { + get { + return getStringForKey(Constants.toxPasswordForActiveAccount) + } + set { + setString(newValue, forKey: Constants.toxPasswordForActiveAccount) + } + } + + /// Number of failed enters of pin by user. + var failedPinAttemptsNumber: Int? { + get { + return getIntForKey(Constants.failedPinAttemptsNumber) + } + set { + setInt(newValue, forKey: Constants.failedPinAttemptsNumber) + } + } + + /// Removes all data related to active account. + func deleteActiveAccountData() { + toxPasswordForActiveAccount = nil + failedPinAttemptsNumber = nil + } +} + +private extension KeychainManager { + func getIntForKey(_ key: String) -> Int? { + guard let data = getDataForKey(key) else { + return nil + } + + guard let number = NSKeyedUnarchiver.unarchiveObject(with: data) as? NSNumber else { + return nil + } + + return number.intValue + } + + func setInt(_ value: Int?, forKey key: String) { + guard let value = value else { + setData(nil, forKey: key) + return + } + + let number = NSNumber(value: value) + + let data = NSKeyedArchiver.archivedData(withRootObject: number) + setData(data, forKey: key) + } + + func getStringForKey(_ key: String) -> String? { + guard let data = getDataForKey(key) else { + return nil + } + + return NSString(data: data, encoding: String.Encoding.utf8.rawValue) as String? + } + + func setString(_ string: String?, forKey key: String) { + let data = string?.data(using: String.Encoding.utf8) + setData(data, forKey: key) + } + + func getBoolForKey(_ key: String) -> Bool? { + guard let data = getDataForKey(key) else { + return nil + } + + return (data as NSData).bytes.bindMemory(to: Int.self, capacity: data.count).pointee == 1 + } + + func setBool(_ value: Bool?, forKey key: String) { + var data: Data? = nil + + if let value = value { + var bytes = value ? 1 : 0 + withUnsafePointer(to: &bytes) { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { + data = Data(bytes: $0, count: MemoryLayout.size) + } + } + } + + setData(data, forKey: key) + } + + func getDataForKey(_ key: String) -> Data? { + var query = genericQueryWithKey(key) + query[kSecMatchLimit as String] = kSecMatchLimitOne + query[kSecReturnData as String] = kCFBooleanTrue + + var queryResult: AnyObject? + let status = withUnsafeMutablePointer(to: &queryResult) { + SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) + } + + if status == errSecItemNotFound { + return nil + } + + guard status == noErr else { + log("Error when getting keychain data for key \(key), status \(status)") + return nil + } + + guard let data = queryResult as? Data else { + log("Unexpected data for key \(key)") + return nil + } + + return data + } + + func setData(_ newData: Data?, forKey key: String) { + let oldData = getDataForKey(key) + + switch (oldData, newData) { + case (.some(_), .some(let data)): + // Update + let query = genericQueryWithKey(key) + + var attributesToUpdate = [String : AnyObject]() + attributesToUpdate[kSecValueData as String] = data as AnyObject? + + let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary) + guard status == noErr else { + log("Error when updating keychain data for key \(key), status \(status)") + return + } + + case (.some(_), .none): + // Delete + let query = genericQueryWithKey(key) + let status = SecItemDelete(query as CFDictionary) + guard status == noErr else { + log("Error when updating keychain data for key \(key), status \(status)") + return + } + + case (.none, .some(let data)): + // Add + var query = genericQueryWithKey(key) + query[kSecValueData as String] = data as AnyObject? + + let status = SecItemAdd(query as CFDictionary, nil) + guard status == noErr else { + log("Error when setting keychain data for key \(key), status \(status)") + return + } + + case (.none, .none): + // Nothing to do here, no changes + break + } + } + + func genericQueryWithKey(_ key: String) -> [String : AnyObject] { + var query = [String : AnyObject]() + query[kSecClass as String] = kSecClassGenericPassword + query[kSecAttrService as String] = Constants.ActiveAccountDataService as AnyObject? + query[kSecAttrAccount as String] = key as AnyObject? + query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock + + return query + } +} diff --git a/Antidote/Launch Screen.storyboard b/Antidote/Launch Screen.storyboard new file mode 100644 index 0000000..34872e2 --- /dev/null +++ b/Antidote/Launch Screen.storyboard @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Antidote/LaunchPlaceholderBoard.storyboard b/Antidote/LaunchPlaceholderBoard.storyboard new file mode 100644 index 0000000..37e54bb --- /dev/null +++ b/Antidote/LaunchPlaceholderBoard.storyboard @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Antidote/LaunchPlaceholderController.swift b/Antidote/LaunchPlaceholderController.swift new file mode 100644 index 0000000..7c52a66 --- /dev/null +++ b/Antidote/LaunchPlaceholderController.swift @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +class LaunchPlaceholderController: UIViewController { +} diff --git a/Antidote/LinearProgressBar.swift b/Antidote/LinearProgressBar.swift new file mode 100644 index 0000000..c0add79 --- /dev/null +++ b/Antidote/LinearProgressBar.swift @@ -0,0 +1,163 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// +// LinearProgressBar.swift +// CookMinute +// +// Created by Philippe Boisney on 18/11/2015. +// Copyright © 2015 CookMinute. All rights reserved. +// +// Google Guidelines: https://www.google.com/design/spec/components/progress-activity.html#progress-activity-types-of-indicators +// + +import UIKit + +open class LinearProgressBar: UIView { + + //FOR DATA + fileprivate var screenSize: CGRect = UIScreen.main.bounds + fileprivate var isAnimationRunning = false + + //FOR DESIGN + fileprivate var progressBarIndicator: UIView! + + //PUBLIC VARS + open var backgroundProgressBarColor: UIColor = UIColor(red:0.73, green:0.87, blue:0.98, alpha:1.0) + open var progressBarColor: UIColor = UIColor(red:0.12, green:0.53, blue:0.90, alpha:1.0) + open var heightForLinearBar: CGFloat = 5 + open var widthForLinearBar: CGFloat = 0 + + public init () { + super.init(frame: CGRect(origin: CGPoint(x: 0,y :20), size: CGSize(width: screenSize.width, height: 0))) + self.progressBarIndicator = UIView(frame: CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: 0, height: heightForLinearBar))) + } + + override public init(frame: CGRect) { + super.init(frame: frame) + self.progressBarIndicator = UIView(frame: CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: 0, height: heightForLinearBar))) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //MARK: LIFE OF VIEW + override open func layoutSubviews() { + super.layoutSubviews() + self.screenSize = UIScreen.main.bounds + + if widthForLinearBar == 0 || widthForLinearBar == self.screenSize.height { + widthForLinearBar = self.screenSize.width + } + + if (UIDeviceOrientationIsLandscape(UIDevice.current.orientation)) { + self.frame = CGRect(origin: CGPoint(x: self.frame.origin.x,y :self.frame.origin.y), size: CGSize(width: widthForLinearBar, height: self.frame.height)) + } + + if (UIDeviceOrientationIsPortrait(UIDevice.current.orientation)) { + self.frame = CGRect(origin: CGPoint(x: self.frame.origin.x,y :self.frame.origin.y), size: CGSize(width: widthForLinearBar, height: self.frame.height)) + } + } + + //MARK: PUBLIC FUNCTIONS ------------------------------------------------------------------------------------------ + + //Start the animation + open func startAnimation(viewToAddto: UIView, viewToAlignToBottomOf: UIView, bottom_margin: Int ){ + + self.configureColors() + + self.show(viewToAddto: viewToAddto, viewToAlignToBottomOf: viewToAlignToBottomOf, bottom_margin: bottom_margin) + + if !isAnimationRunning { + self.isAnimationRunning = true + + UIView.animate(withDuration: 0.5, delay:0, options: [], animations: { + self.frame = CGRect(x: 0, y: self.frame.origin.y, width: self.widthForLinearBar, height: self.heightForLinearBar) + }, completion: { animationFinished in + self.addSubview(self.progressBarIndicator) + self.configureAnimation() + }) + } + } + + //Start the animation + open func stopAnimation() { + + self.isAnimationRunning = false + + UIView.animate(withDuration: 0.5, animations: { + self.progressBarIndicator.frame = CGRect(x: 0, y: 0, width: self.widthForLinearBar, height: 0) + self.frame = CGRect(x: 0, y: self.frame.origin.y, width: self.widthForLinearBar, height: 0) + }) + } + + //MARK: PRIVATE FUNCTIONS ------------------------------------------------------------------------------------------ + + fileprivate func show(viewToAddto: UIView, viewToAlignToBottomOf: UIView, bottom_margin: Int ) { + + // Only show once + if self.superview != nil { + return + } + + // Find current top viewcontroller + if let topController = getTopViewController() { + // let superView: UIView = topController.view + viewToAddto.addSubview(self) + + // update: show progressbar in the vertical center of the screen + self.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview() + make.top.equalTo(viewToAlignToBottomOf.snp.bottom).offset(bottom_margin) + } + } + } + + fileprivate func configureColors(){ + + self.backgroundColor = self.backgroundProgressBarColor + self.progressBarIndicator.backgroundColor = self.progressBarColor + self.layoutIfNeeded() + } + + fileprivate func configureAnimation() { + + guard let superview = self.superview else { + stopAnimation() + return + } + + self.progressBarIndicator.frame = CGRect(origin: CGPoint(x: 0, y :0), size: CGSize(width: 0, height: heightForLinearBar)) + + UIView.animateKeyframes(withDuration: 1.0, delay: 0, options: [], animations: { + + UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5, animations: { + self.progressBarIndicator.frame = CGRect(x: 0, y: 0, width: self.widthForLinearBar*0.7, height: self.heightForLinearBar) + }) + + UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: { + self.progressBarIndicator.frame = CGRect(x: superview.frame.width, y: 0, width: 0, height: self.heightForLinearBar) + + }) + + }) { (completed) in + if (self.isAnimationRunning){ + self.configureAnimation() + } + } + } + + // ----------------------------------------------------- + //MARK: UTILS --------------------------------------- + // ----------------------------------------------------- + + fileprivate func getTopViewController() -> UIViewController? { + var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController + while topController?.presentedViewController != nil { + topController = topController?.presentedViewController + } + return topController + } +} diff --git a/Antidote/LoadingImageView.swift b/Antidote/LoadingImageView.swift new file mode 100644 index 0000000..0e05276 --- /dev/null +++ b/Antidote/LoadingImageView.swift @@ -0,0 +1,199 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import MobileCoreServices + +class LoadingImageView: UIView { + struct Constants { + static let ImageButtonSize: CGFloat = 180.0 + static let LabelHorizontalOffset = 12.0 + static let LabelBottomOffset = -6.0 + static let CenterImageSize = 50.0 + static let ProgressViewSize = 70.0 + } + + var imageButton: UIButton! + var progressView: ProgressCircleView! + var centerImageView: UIImageView! + var topLabel: UILabel! + var bottomLabel: UILabel! + + var pressedHandle: (() -> Void)? + + override init(frame: CGRect) { + super.init(frame: frame) + + backgroundColor = .clear + createViews() + installConstraints() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setCancelledImage() { + centerImageView.image = UIImage.templateNamed("chat-file-type-canceled") + } + + func setImageWithUti(_ uti: String?, fileExtension: String?) { + let imageName = imageNameWithUti(uti, fileExtension: fileExtension) + centerImageView.image = UIImage.templateNamed(imageName) + } +} + +extension LoadingImageView { + @objc func imageButtonPressed() { + pressedHandle?() + } +} + +private extension LoadingImageView { + func createViews() { + imageButton = UIButton() + imageButton.layer.cornerRadius = 12.0 + imageButton.clipsToBounds = true + imageButton.addTarget(self, action: #selector(LoadingImageView.imageButtonPressed), for: .touchUpInside) + addSubview(imageButton) + + centerImageView = UIImageView() + centerImageView.contentMode = .center + addSubview(centerImageView) + + progressView = ProgressCircleView() + progressView.isUserInteractionEnabled = false + addSubview(progressView) + + topLabel = UILabel() + topLabel.font = UIFont.systemFont(ofSize: 14.0) + addSubview(topLabel) + + bottomLabel = UILabel() + bottomLabel.font = UIFont.systemFont(ofSize: 14.0) + addSubview(bottomLabel) + } + + func installConstraints() { + snp.makeConstraints { + $0.size.equalTo(Constants.ImageButtonSize) + } + + imageButton.snp.makeConstraints { + $0.edges.equalTo(self) + } + + centerImageView.snp.makeConstraints { + $0.center.equalTo(self) + $0.size.equalTo(Constants.CenterImageSize) + } + + progressView.snp.makeConstraints { + $0.center.equalTo(self) + $0.size.equalTo(Constants.ProgressViewSize) + } + + topLabel.snp.makeConstraints { + $0.leading.equalTo(self).offset(Constants.LabelHorizontalOffset) + $0.trailing.lessThanOrEqualTo(self).offset(-Constants.LabelHorizontalOffset) + $0.bottom.equalTo(bottomLabel.snp.top) + } + + bottomLabel.snp.makeConstraints { + $0.leading.equalTo(self).offset(Constants.LabelHorizontalOffset) + $0.trailing.lessThanOrEqualTo(self).offset(-Constants.LabelHorizontalOffset) + $0.bottom.equalTo(self).offset(Constants.LabelBottomOffset) + } + } + + func imageNameWithUti(_ uti: String?, fileExtension: String?) -> String { + guard let uti = uti else { + return "chat-file-type-basic" + } + + if UTTypeEqual(uti as CFString, kUTTypeGIF) { + return "chat-file-type-gif" + } + else if UTTypeEqual(uti as CFString, kUTTypeHTML) { + return "chat-file-type-html" + } + else if UTTypeEqual(uti as CFString, kUTTypeJPEG) { + return "chat-file-type-jpg" + } + else if UTTypeEqual(uti as CFString, kUTTypeMP3) { + return "chat-file-type-mp3" + } + else if UTTypeEqual(uti as CFString, kUTTypeMPEG) { + return "chat-file-type-mpg" + } + else if UTTypeEqual(uti as CFString, kUTTypeMPEG4) { + return "chat-file-type-mpg" + } + else if UTTypeEqual(uti as CFString, kUTTypePDF) { + return "chat-file-type-pdf" + } + else if UTTypeEqual(uti as CFString, kUTTypePNG) { + return "chat-file-type-png" + } + else if UTTypeEqual(uti as CFString, kUTTypeTIFF) { + return "chat-file-type-tif" + } + else if UTTypeEqual(uti as CFString, kUTTypePlainText) { + return "chat-file-type-txt" + } + + guard let fileExtension = fileExtension else { + return "chat-file-type-basic" + } + + switch fileExtension { + case "7z": + return "chat-file-type-7zip" + case "aac": + return "chat-file-type-aac" + case "avi": + return "chat-file-type-avi" + case "css": + return "chat-file-type-css" + case "csv": + return "chat-file-type-csv" + case "doc": + return "chat-file-type-doc" + case "ebup": + return "chat-file-type-ebup" + case "exe": + return "chat-file-type-exe" + case "fb2": + return "chat-file-type-fb2" + case "flv": + return "chat-file-type-flv" + case "mov": + return "chat-file-type-mov" + case "ogg": + return "chat-file-type-ogg" + case "otf": + return "chat-file-type-otf" + case "ppt": + return "chat-file-type-ppt" + case "psd": + return "chat-file-type-psd" + case "rar": + return "chat-file-type-rar" + case "tar": + return "chat-file-type-tar" + case "ttf": + return "chat-file-type-ttf" + case "wav": + return "chat-file-type-wav" + case "wma": + return "chat-file-type-wma" + case "xls": + return "chat-file-type-xls" + case "zip": + return "chat-file-type-zip" + default: + return "chat-file-type-basic" + } + } +} diff --git a/Antidote/LocationManager.swift b/Antidote/LocationManager.swift new file mode 100644 index 0000000..722d3fa --- /dev/null +++ b/Antidote/LocationManager.swift @@ -0,0 +1,193 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// +// LocationManager.swift +// CLBackgroundAccess +// +// Created by Samer Murad on 10.04.21. +// + +import Foundation +import CoreLocation +import UIKit + +// MARK: Declarations +class LocationManager: NSObject { + /// Alias to CLAuthorizationStatus, makes accessable throughout the project + /// without importing the CoreLocation kit. + typealias LocationAuthStatus = CLAuthorizationStatus + enum State { + case Idle, Monitoring + } + + /// Singleton Object + static let shared = LocationManager() + + private var _state: State = .Idle { + didSet { + /// Disptach State Change event + Bus.shared.post(event: .LocationManagerStateChange, userInfo: ["state": _state ]) + } + } + public var state: State { + get { return _state } + } + private var manager: CLLocationManager! + + private override init() { + super.init() + self.setup() + } + + // cleanup + deinit { + self.teardown() + print("Location Manager Killed") + } +} + +// MARK: - Life Cycle Setup +private extension LocationManager { + func setup() { + manager = CLLocationManager() + if #available(iOS 14.0, *) { + manager.desiredAccuracy = kCLLocationAccuracyReduced + } else { + // Fallback on earlier versions + manager.desiredAccuracy = kCLLocationAccuracyBest + } + manager?.delegate = self + manager?.allowsBackgroundLocationUpdates = true + manager?.pausesLocationUpdatesAutomatically = false + manager?.distanceFilter = kCLDistanceFilterNone + } + + func teardown() { + self.stopMonitoring() + self.manager.delegate = nil + self.manager = nil + } +} +// MARK: - Main BL +extension LocationManager { + + func isHasAccess() -> Bool { + var isHas = true + if #available(iOS 14.0, *) { + if let authStatus = self.manager?.authorizationStatus { + if authStatus == .notDetermined || authStatus == .denied || authStatus == .restricted { + isHas = false + } + return isHas + } + } else { + // Fallback on earlier versions + } + return false + } + + func requestAccess() { + // manager?.requestAlwaysAuthorization() + manager?.requestWhenInUseAuthorization() + } + + func startMonitoring() { + guard self.isHasAccess() else { + print("WARN: App Doesnt have access to CoreLocation, please call LocationManager.shared.isHasAccess() first") + return + } + guard self.state == .Idle else { + print("WARN: LocationManager already running") + return + } + + // sned to global queue + DispatchQueue.global().async { + // Guard has location services + guard CLLocationManager.locationServicesEnabled() else { + DispatchQueue.main.async { + AppDelegate + .shared + .alert("Error", "Location Services Must be enbaled, got to Settings -> Privacy -> Location Services to enable") + } + return + } + self._state = .Monitoring + self.manager?.startUpdatingLocation() + /// Optional: + /// Only work if app has .authorizedAlways access, + /// shows the blue indicator in the status bar, + /// if app has .authorizedWhenInUse, the blue indicator on by default + /// so we manually turn it on in any case to inform + /// the user that we are working in the background + self.manager.showsBackgroundLocationIndicator = true + } + } + + func stopMonitoring() { + guard self.state != .Idle else { + print("WARN: LocationManager already stopped") + return + } + self.manager?.stopUpdatingLocation() + self._state = .Idle + /// turn off blue indicator + self.manager.showsBackgroundLocationIndicator = false + } +} + +// MARK: - CLLocationManagerDelegate +extension LocationManager: CLLocationManagerDelegate { + func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { + if #available(iOS 14.0, *) { + print("locationManagerDidChangeAuthorization" , manager.authorizationStatus) + } else { + // Fallback on earlier versions + } + if #available(iOS 14.0, *) { + Bus.shared.post(event: .LocationAuthUpdate, userInfo: ["status": manager.authorizationStatus, "state": self.state]) + } else { + // Fallback on earlier versions + } + } + + + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + Bus.shared.post(event: .LocationUpdate, userInfo: ["locations": locations, "state": self.state]) + } + + func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + if let clError = error as? CLError { + switch clError.code { + case CLError.Code.denied: + + fallthrough + default: + print("locationManager: didFailWithError", clError) + } + // reset state + self._state = .Idle + } + } +} + + +// MARK: CLAutorizationStatus pretty print +extension LocationManager.LocationAuthStatus: CustomStringConvertible { + public var description: String { + get { + switch self { + case .notDetermined: return "NotDetermined" + case .denied: return "Denied" + case .restricted: return "Restricted" + case .authorizedAlways: return "AuthorizedAlways" + case .authorizedWhenInUse: return "AuthorizedWhenInUse" + default: return "CLAuthorizationStatus" + } + } + } + +} + diff --git a/Antidote/Logger.swift b/Antidote/Logger.swift new file mode 100644 index 0000000..c8a1c59 --- /dev/null +++ b/Antidote/Logger.swift @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +func log (_ string: String, filename: NSString = #file) { + NSLog("\(filename.lastPathComponent): \(string)") +} + diff --git a/Antidote/LoginBaseController.swift b/Antidote/LoginBaseController.swift new file mode 100644 index 0000000..f398c46 --- /dev/null +++ b/Antidote/LoginBaseController.swift @@ -0,0 +1,63 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +class LoginBaseController: KeyboardNotificationController { + struct Constants { + static let HorizontalOffset = 40.0 + static let VerticalOffset = 40.0 + static let SmallVerticalOffset = 8.0 + + static let TextFieldHeight: CGFloat = 40.0 + + static let MaxFormWidth = 350.0 + + static let GradientHeightPercentage: CGFloat = 0.4 + } + + let theme: Theme + + fileprivate var gradientLayer: CAGradientLayer! + + init(theme: Theme) { + self.theme = theme + + super.init() + + edgesForExtendedLayout = UIRectEdge() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.LoginBackground)) + + createGradientLayer() + } + + override var prefersStatusBarHidden : Bool { + return true + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + gradientLayer.frame.size.width = view.frame.size.width + gradientLayer.frame.size.height = view.frame.size.height * Constants.GradientHeightPercentage + gradientLayer.frame.origin.x = 0 + gradientLayer.frame.origin.y = view.frame.size.height - gradientLayer.frame.size.height + } +} + +private extension LoginBaseController { + func createGradientLayer() { + gradientLayer = CAGradientLayer() + gradientLayer.colors = [theme.colorForType(.LoginBackground).cgColor, theme.colorForType(.LoginGradient).cgColor] + view.layer.insertSublayer(gradientLayer, at: 0) + } +} diff --git a/Antidote/LoginChoiceController.swift b/Antidote/LoginChoiceController.swift new file mode 100644 index 0000000..f4896a7 --- /dev/null +++ b/Antidote/LoginChoiceController.swift @@ -0,0 +1,95 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +protocol LoginChoiceControllerDelegate: class { + func loginChoiceControllerCreateAccount(_ controller: LoginChoiceController) + func loginChoiceControllerImportProfile(_ controller: LoginChoiceController) +} + +class LoginChoiceController: LoginLogoController { + weak var delegate: LoginChoiceControllerDelegate? + + fileprivate var incompressibleContainer: IncompressibleView! + fileprivate var createAccountButton: RoundedButton! + fileprivate var importProfileButton: RoundedButton! + + override func loadView() { + super.loadView() + + createContainer() + createButtons() + + installConstraints() + } +} + +// MARK: Actions +extension LoginChoiceController { + @objc func createAccountButtonPressed() { + delegate?.loginChoiceControllerCreateAccount(self) + } + + @objc func importProfileButtonPressed() { + delegate?.loginChoiceControllerImportProfile(self) + } +} + +private extension LoginChoiceController { + func createContainer() { + incompressibleContainer = IncompressibleView() + incompressibleContainer.backgroundColor = .clear + contentContainerView.addSubview(incompressibleContainer) + } + + func createButtons() { + createAccountButton = createButtonWithTitle(String(localized:"create_account"), action: #selector(LoginChoiceController.createAccountButtonPressed)) + importProfileButton = createButtonWithTitle(String(localized:"import_profile"), action: #selector(LoginChoiceController.importProfileButtonPressed)) + } + + func installConstraints() { + incompressibleContainer.customIntrinsicContentSize.width = CGFloat(Constants.MaxFormWidth) + incompressibleContainer.snp.makeConstraints { + $0.top.equalTo(contentContainerView) + $0.centerX.equalTo(contentContainerView) + $0.width.lessThanOrEqualTo(Constants.MaxFormWidth) + $0.width.lessThanOrEqualTo(contentContainerView).offset(-2 * Constants.HorizontalOffset) + $0.height.equalTo(contentContainerView) + } + + createAccountButton.snp.makeConstraints { + $0.top.equalTo(incompressibleContainer).offset(Constants.VerticalOffset) + $0.leading.trailing.equalTo(incompressibleContainer) + } + + importProfileButton.snp.makeConstraints { + $0.top.equalTo(createAccountButton.snp.bottom).offset(Constants.VerticalOffset) + $0.leading.trailing.equalTo(incompressibleContainer) + } + } + + func createLabelWithText(_ text: String) -> UILabel { + let label = UILabel() + label.text = text + label.textColor = theme.colorForType(.LoginDescriptionLabel) + label.textAlignment = .center + label.backgroundColor = .clear + + incompressibleContainer.addSubview(label) + + return label + } + + func createButtonWithTitle(_ title: String, action: Selector) -> RoundedButton { + let button = RoundedButton(theme: theme, type: .login) + button.setTitle(title, for: UIControlState()) + button.addTarget(self, action: action, for: .touchUpInside) + + incompressibleContainer.addSubview(button) + + return button + } +} diff --git a/Antidote/LoginCoordinator.swift b/Antidote/LoginCoordinator.swift new file mode 100644 index 0000000..a37c072 --- /dev/null +++ b/Antidote/LoginCoordinator.swift @@ -0,0 +1,313 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +protocol LoginCoordinatorDelegate: class { + func loginCoordinatorDidLogin(_ coordinator: LoginCoordinator, manager: OCTManager, password: String) +} + +private enum UserInfoKey: String { + case ImportURL + case LoginProfile + case LoginConfigurationClosure + case LoginErrorClosure +} + +class LoginCoordinator { + weak var delegate: LoginCoordinatorDelegate? + + fileprivate let window: UIWindow + fileprivate let theme: Theme + fileprivate let navigationController: UINavigationController + + fileprivate var createAccountCoordinator: LoginCreateAccountCoordinator? + + init(theme: Theme, window: UIWindow) { + self.window = window + self.theme = theme + + switch InterfaceIdiom.current() { + case .iPhone: + self.navigationController = PortraitNavigationController() + case .iPad: + self.navigationController = UINavigationController() + } + + navigationController.navigationBar.tintColor = theme.colorForType(.LoginButtonBackground) + navigationController.navigationBar.titleTextAttributes = [ + NSAttributedStringKey.foregroundColor: theme.colorForType(.LoginButtonText) + ] + } +} + +// MARK: TopCoordinatorProtocol +extension LoginCoordinator: TopCoordinatorProtocol { + func startWithOptions(_ options: CoordinatorOptions?) { + let profileNames = ProfileManager().allProfileNames + + let controller: UIViewController = (profileNames.count > 0) ? createFormController() : createChoiceController() + + navigationController.pushViewController(controller, animated: false) + window.rootViewController = navigationController + } + + func handleLocalNotification(_ notification: UILocalNotification) { + // nop + } + + func handleInboxURL(_ url: URL) { + guard url.isToxURL() else { + return + } + + let fileName = url.lastPathComponent + let style: UIAlertControllerStyle + + switch InterfaceIdiom.current() { + case .iPhone: + style = .actionSheet + case .iPad: + style = .alert + } + + let alert = UIAlertController(title: nil, message: fileName, preferredStyle: style) + + alert.addAction(UIAlertAction(title: String(localized: "create_profile"), style: .default) { [unowned self] _ -> Void in + self.importToxProfileFromURL(url) + }) + + alert.addAction(UIAlertAction(title: String(localized: "alert_cancel"), style: .cancel, handler: nil)) + + navigationController.present(alert, animated: true, completion: nil) + } +} + +extension LoginCoordinator: LoginFormControllerDelegate { + func loginFormControllerLogin(_ controller: LoginFormController, profileName: String, password: String?) { + loginWithProfile(profileName, password: password, errorClosure: { error in + handleErrorWithType(.createOCTManager, error: error) + }) + } + + func loginFormControllerCreateAccount(_ controller: LoginFormController) { + showCreateAccountController() + } + + func loginFormControllerImportProfile(_ controller: LoginFormController) { + showImportProfileController() + } + + func loginFormController(_ controller: LoginFormController, isProfileEncrypted profile: String) -> Bool { + return isProfileEncrypted(profile) + } +} + +extension LoginCoordinator: LoginChoiceControllerDelegate { + func loginChoiceControllerCreateAccount(_ controller: LoginChoiceController) { + showCreateAccountController() + } + + func loginChoiceControllerImportProfile(_ controller: LoginChoiceController) { + showImportProfileController() + } +} + +extension LoginCoordinator: LoginCreateAccountCoordinatorDelegate { + func loginCreateAccountCoordinator(_ coordinator: LoginCreateAccountCoordinator, + didCreateAccountWithProfileName profileName: String, + username: String, + password: String) { + createProfileWithProfileName(profileName, username: username, copyFromURL: nil, password: password) + } + + func loginCreateAccountCoordinator(_ coordinator: LoginCreateAccountCoordinator, + didImportProfileWithProfileName profileName: String) { + guard let url = coordinator.userInfo[UserInfoKey.ImportURL.rawValue] as? URL else { + fatalError("URL should be non-nil when importing profile") + } + + createProfileWithProfileName(profileName, username: nil, copyFromURL: url, password: nil) + } + + func loginCreateAccountCoordinator(_ coordinator: LoginCreateAccountCoordinator, didCreatePassword password: String) { + guard let profile = coordinator.userInfo[UserInfoKey.LoginProfile.rawValue] as? String else { + fatalError("Profile should be non-nil on login") + } + + let configurationClosure = coordinator.userInfo[UserInfoKey.LoginConfigurationClosure.rawValue] as? ((_ manager: OCTManager) -> Void) + let errorClosure = coordinator.userInfo[UserInfoKey.LoginErrorClosure.rawValue] as? ((NSError) -> Void) + + loginWithProfile(profile, password: password, configurationClosure: configurationClosure, errorClosure: errorClosure) + } +} + +private extension LoginCoordinator { + func createFormController() -> LoginFormController { + let profileNames = ProfileManager().allProfileNames + var selectedIndex = 0 + + if let activeProfile = UserDefaultsManager().lastActiveProfile { + selectedIndex = profileNames.index(of: activeProfile) ?? 0 + } + + let controller = LoginFormController(theme: theme, profileNames: profileNames, selectedIndex: selectedIndex) + controller.delegate = self + + return controller + } + + func createChoiceController() -> LoginChoiceController { + let controller = LoginChoiceController(theme: theme) + controller.delegate = self + + return controller + } + + func showCreateAccountController() { + let coordinator = LoginCreateAccountCoordinator(theme: theme, + navigationController: navigationController, + type: .createAccountAndPassword) + coordinator.delegate = self + coordinator.startWithOptions(nil) + + createAccountCoordinator = coordinator + } + + func showImportProfileController() { + let controller = TextViewController( + resourceName: "import-profile", + backgroundColor: theme.colorForType(.LoginBackground), + titleColor: theme.colorForType(.NormalText), + textColor: theme.colorForType(.NormalText)) + + navigationController.pushViewController(controller, animated: true) + } + + /** + * @param profile The name of profile. + * @param password Password to decrypt profile. + * @param configurationClosure Closure called after login to configure profile. + * @param errorClosure Closure called if any error occured during login. + */ + func loginWithProfile( + _ profile: String, + password: String?, + configurationClosure: ((_ manager: OCTManager) -> Void)? = nil, + errorClosure: ((NSError) -> Void)? = nil) + { + guard let password = password else { + if isProfileEncrypted(profile) { + // Profile is encrypted, password is required. No error is needed, password placeholder + // should be quite obvious. + + // However we should show error message for accessibility users. + if UIAccessibilityIsVoiceOverRunning() { + handleErrorWithType(.passwordIsEmpty) + } + return + } + + let coordinator = LoginCreateAccountCoordinator(theme: theme, + navigationController: navigationController, + type: .createPassword) + coordinator.delegate = self + coordinator.userInfo[UserInfoKey.LoginProfile.rawValue] = profile + coordinator.userInfo[UserInfoKey.LoginConfigurationClosure.rawValue] = configurationClosure + coordinator.userInfo[UserInfoKey.LoginErrorClosure.rawValue] = errorClosure + coordinator.startWithOptions(nil) + + createAccountCoordinator = coordinator + return + } + + let path = ProfileManager().pathForProfileWithName(profile) + let configuration = OCTManagerConfiguration.configurationWithBaseDirectory(path)! + + let hud = JGProgressHUD(style: .dark)! + hud.show(in: self.navigationController.view) + + ToxFactory.createToxWithConfiguration(configuration, encryptPassword: password, successBlock: { [weak self] manager -> Void in + hud.dismiss() + + configurationClosure?(manager) + + let userDefaults = UserDefaultsManager() + userDefaults.lastActiveProfile = profile + + self?.delegate?.loginCoordinatorDidLogin(self!, manager: manager, password: password) + + }, failureBlock: { error -> Void in + hud.dismiss() + errorClosure?(error as NSError) + }) + } + + func importToxProfileFromURL(_ url: URL) { + let coordinator = LoginCreateAccountCoordinator(theme: theme, + navigationController: navigationController, + type: .importProfile) + coordinator.userInfo[UserInfoKey.ImportURL.rawValue] = url + coordinator.delegate = self + coordinator.startWithOptions(nil) + + createAccountCoordinator = coordinator + } + + func createProfileWithProfileName(_ profileName: String, username: String?, copyFromURL: URL?, password: String?) { + if profileName.isEmpty { + UIAlertController.showWithTitle("", message: String(localized: "login_enter_username_and_profile"), retryBlock: nil) + return + } + + let profileManager = ProfileManager() + + do { + try profileManager.createProfileWithName(profileName, copyFromURL: copyFromURL) + } + catch let error as NSError { + UIAlertController.showErrorWithMessage(String(localized: error.localizedDescription), retryBlock: nil) + return + } + + if isProfileEncrypted(profileName) && password == nil { + // Cannot login without password, just opening login screen. + UserDefaultsManager().lastActiveProfile = profileName + + let controller = self.createFormController() + let root = self.navigationController.viewControllers[0] + + self.navigationController.setViewControllers([root, controller], animated: true) + + return + } + + loginWithProfile(profileName, password: password, configurationClosure: { + if let name = username { + _ = try? $0.user.setUserName(name) + _ = try? $0.user.setUserStatusMessage("Antidote is Tox") + } + }, errorClosure: { error in + handleErrorWithType(.createOCTManager, error: error) + _ = try? profileManager.deleteProfileWithName(profileName) + }) + } + + func isProfileEncrypted(_ profile: String) -> Bool { + let profilePath = ProfileManager().pathForProfileWithName(profile) + + let configuration = OCTManagerConfiguration.configurationWithBaseDirectory(profilePath)! + let dataPath = configuration.fileStorage.pathForToxSaveFile + + guard FileManager.default.fileExists(atPath: dataPath) else { + return false + } + + guard let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)) else { + return false + } + + return OCTToxEncryptSave.isDataEncrypted(data) + } +} diff --git a/Antidote/LoginCreateAccountController.swift b/Antidote/LoginCreateAccountController.swift new file mode 100644 index 0000000..d169435 --- /dev/null +++ b/Antidote/LoginCreateAccountController.swift @@ -0,0 +1,80 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +protocol LoginCreateAccountControllerDelegate: class { + func loginCreateAccountControllerCreate(_ controller: LoginCreateAccountController, name: String, profile: String) + func loginCreateAccountControllerImport(_ controller: LoginCreateAccountController, profile: String) +} + +class LoginCreateAccountController: LoginGenericCreateController { + enum ControllerType { + case createAccount + case importProfile + } + + weak var delegate: LoginCreateAccountControllerDelegate? + + fileprivate let type: ControllerType + + init(theme: Theme, type: ControllerType) { + self.type = type + + super.init(theme: theme) + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func configureViews() { + firstTextField.title = String(localized: "create_account_username_title") + firstTextField.placeholder = String(localized: "create_account_username_placeholder") + firstTextField.maxTextUTF8Length = Int(kOCTToxMaxNameLength) + + secondTextField.title = String(localized: "create_account_profile_title") + secondTextField.placeholder = String(localized: "create_account_profile_placeholder") + secondTextField.hint = String(localized: "create_account_profile_hint") + secondTextField.returnKeyType = .next + + bottomButton.setTitle(String(localized: "create_account_next_button"), for: UIControlState()) + + switch type { + case .createAccount: + titleLabel.text = String(localized: "create_profile") + case .importProfile: + titleLabel.text = String(localized: "import_profile") + firstTextField.isHidden = true + } + } + + override func bottomButtonPressed() { + let profile = secondTextField.text ?? "" + + guard !profile.isEmpty else { + UIAlertController.showWithTitle(String(localized: "login_enter_username_and_profile"), message: nil, retryBlock: nil) + return + } + + guard !ProfileManager().allProfileNames.contains(profile) else { + UIAlertController.showWithTitle(String(localized: "login_profile_already_exists"), message: nil, retryBlock: nil) + return + } + + + switch type { + case .createAccount: + let name = firstTextField.text ?? "" + guard !name.isEmpty else { + UIAlertController.showWithTitle(String(localized: "login_enter_username_and_profile"), message: nil, retryBlock: nil) + return + } + + delegate?.loginCreateAccountControllerCreate(self, name: name, profile: profile) + case .importProfile: + delegate?.loginCreateAccountControllerImport(self, profile: profile) + } + } +} diff --git a/Antidote/LoginCreateAccountCoordinator.swift b/Antidote/LoginCreateAccountCoordinator.swift new file mode 100644 index 0000000..ec4562e --- /dev/null +++ b/Antidote/LoginCreateAccountCoordinator.swift @@ -0,0 +1,98 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +protocol LoginCreateAccountCoordinatorDelegate: class { + func loginCreateAccountCoordinator(_ coordinator: LoginCreateAccountCoordinator, + didCreateAccountWithProfileName profileName: String, + username: String, + password: String) + func loginCreateAccountCoordinator(_ coordinator: LoginCreateAccountCoordinator, + didImportProfileWithProfileName profileName: String) + func loginCreateAccountCoordinator(_ coordinator: LoginCreateAccountCoordinator, didCreatePassword password: String) +} + +class LoginCreateAccountCoordinator { + enum CoordinatorType { + /// Create new account and new password. + case createAccountAndPassword + /// Import existing profile. + case importProfile + /// Create new password. + case createPassword + } + + weak var delegate: LoginCreateAccountCoordinatorDelegate? + + var userInfo = [String: Any]() + + fileprivate let theme: Theme + fileprivate let navigationController: UINavigationController + fileprivate let type: CoordinatorType + + fileprivate var enteredUsername: String? + fileprivate var enteredProfile: String? + + init(theme: Theme, navigationController: UINavigationController, type: CoordinatorType) { + self.theme = theme + self.navigationController = navigationController + self.type = type + } +} + +extension LoginCreateAccountCoordinator: CoordinatorProtocol { + func startWithOptions(_ options: CoordinatorOptions?) { + switch type { + case .createAccountAndPassword: + let controller = LoginCreateAccountController(theme: theme, type: .createAccount) + controller.delegate = self + navigationController.pushViewController(controller, animated: true) + case .importProfile: + let controller = LoginCreateAccountController(theme: theme, type: .importProfile) + controller.delegate = self + navigationController.pushViewController(controller, animated: true) + case .createPassword: + let controller = LoginCreatePasswordController(theme: theme) + controller.delegate = self + navigationController.pushViewController(controller, animated: true) + } + } +} + +extension LoginCreateAccountCoordinator: LoginCreateAccountControllerDelegate { + func loginCreateAccountControllerCreate(_ controller: LoginCreateAccountController, name: String, profile: String) { + enteredUsername = name + enteredProfile = profile + + let controller = LoginCreatePasswordController(theme: theme) + controller.delegate = self + navigationController.pushViewController(controller, animated: true) + } + + func loginCreateAccountControllerImport(_ controller: LoginCreateAccountController, profile: String) { + delegate?.loginCreateAccountCoordinator(self, didImportProfileWithProfileName: profile) + } +} + +extension LoginCreateAccountCoordinator: LoginCreatePasswordControllerDelegate { + func loginCreatePasswordController(_ controller: LoginCreatePasswordController, password: String) { + switch type { + case .createAccountAndPassword: + guard let profile = enteredProfile, + let username = enteredUsername else { + fatalError("LoginCreateAccountCoordinator: unexpected state") + } + + delegate?.loginCreateAccountCoordinator(self, + didCreateAccountWithProfileName: profile, + username: username, + password: password) + case .importProfile: + fatalError("LoginCreateAccountCoordinator: unexpected state") + case .createPassword: + delegate?.loginCreateAccountCoordinator(self, didCreatePassword: password) + } + } +} diff --git a/Antidote/LoginCreatePasswordController.swift b/Antidote/LoginCreatePasswordController.swift new file mode 100644 index 0000000..52185b9 --- /dev/null +++ b/Antidote/LoginCreatePasswordController.swift @@ -0,0 +1,46 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +protocol LoginCreatePasswordControllerDelegate: class { + func loginCreatePasswordController(_ controller: LoginCreatePasswordController, password: String) +} + +class LoginCreatePasswordController: LoginGenericCreateController { + weak var delegate: LoginCreatePasswordControllerDelegate? + + override func configureViews() { + titleLabel.text = String(localized: "set_password_title") + + firstTextField.placeholder = String(localized: "password") + firstTextField.secureTextEntry = true + firstTextField.hint = String(localized: "set_password_hint") + + secondTextField.placeholder = String(localized: "repeat_password") + secondTextField.secureTextEntry = true + + bottomButton.setTitle(String(localized: "create_account_go_button"), for: UIControlState()) + } + + override func bottomButtonPressed() { + guard let first = firstTextField.text, + let second = secondTextField.text else { + handleErrorWithType(.passwordIsEmpty) + return + } + + guard !first.isEmpty && !second.isEmpty else { + handleErrorWithType(.passwordIsEmpty) + return + } + + guard first == second else { + handleErrorWithType(.passwordsDoNotMatch) + return + } + + delegate?.loginCreatePasswordController(self, password: first) + } +} diff --git a/Antidote/LoginFormController.swift b/Antidote/LoginFormController.swift new file mode 100644 index 0000000..23f440a --- /dev/null +++ b/Antidote/LoginFormController.swift @@ -0,0 +1,339 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +protocol LoginFormControllerDelegate: class { + func loginFormControllerLogin(_ controller: LoginFormController, profileName: String, password: String?) + func loginFormControllerCreateAccount(_ controller: LoginFormController) + func loginFormControllerImportProfile(_ controller: LoginFormController) + + func loginFormController(_ controller: LoginFormController, isProfileEncrypted profile: String) -> Bool +} + +class LoginFormController: LoginLogoController { + fileprivate struct PrivateConstants { + static let IconContainerWidth: CGFloat = Constants.TextFieldHeight - 15.0 + static let IconContainerHeight: CGFloat = Constants.TextFieldHeight + + static let FormOffset = 20.0 + static let FormSmallOffset = 10.0 + + static let AnimationDuration = 0.3 + } + + weak var delegate: LoginFormControllerDelegate? + + fileprivate var formView: IncompressibleView! + fileprivate var profileFakeTextField: UITextField! + fileprivate var profileButton: UIButton! + fileprivate var passwordField: UITextField! + + fileprivate var loginButton: RoundedButton! + + fileprivate var bottomButtonsContainer: UIView! + fileprivate var createAccountButton: UIButton! + fileprivate var orLabel: UILabel! + fileprivate var importProfileButton: UIButton! + + fileprivate var profileButtonBottomToFormConstraint: Constraint! + fileprivate var passwordFieldBottomToFormConstraint: Constraint! + + fileprivate let profileNames: [String] + fileprivate var selectedIndex: Int + + init(theme: Theme, profileNames: [String], selectedIndex: Int) { + self.profileNames = profileNames + self.selectedIndex = selectedIndex + + super.init(theme: theme) + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + super.loadView() + + createGestureRecognizers() + createFormViews() + createLoginButton() + createBottomButtons() + + installConstraints() + + updateFormAnimated(false) + } + + override func keyboardWillShowAnimated(keyboardFrame frame: CGRect) { + guard navigationController?.topViewController == self else { + return + } + let underLoginHeight = + mainContainerView.frame.size.height - + contentContainerView.frame.origin.y - + loginButton.frame.maxY + + let offset = min(0.0, underLoginHeight - frame.height) + + mainContainerViewTopConstraint?.update(offset: offset) + view.layoutIfNeeded() + } + + override func keyboardWillHideAnimated(keyboardFrame frame: CGRect) { + mainContainerViewTopConstraint?.update(offset: 0.0) + view.layoutIfNeeded() + } +} + +// MARK: Actions +extension LoginFormController { + @objc func profileButtonPressed() { + view.endEditing(true) + + let picker = FullscreenPicker(theme: theme, strings: profileNames, selectedIndex: selectedIndex) + picker.delegate = self + + contentContainerView.accessibilityElementsHidden = true + picker.showAnimatedInView(view) +} + + @objc func loginButtonPressed() { + let isEmpty = (passwordField.text == nil) || passwordField.text!.isEmpty + let password = isEmpty ? nil : passwordField.text + + delegate?.loginFormControllerLogin(self, profileName: profileNames[selectedIndex], password: password) + } + + @objc func createAccountButtonPressed() { + delegate?.loginFormControllerCreateAccount(self) + } + + @objc func importProfileButtonPressed() { + delegate?.loginFormControllerImportProfile(self) + } + + @objc func tapOnView() { + view.endEditing(true) + } +} + +extension LoginFormController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + loginButtonPressed() + return true + } +} + +extension LoginFormController: FullscreenPickerDelegate { + func fullscreenPicker(_ picker: FullscreenPicker, willDismissWithSelectedIndex index: Int) { + contentContainerView.accessibilityElementsHidden = false + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, self.profileButton); + + if index == selectedIndex { + return + } + selectedIndex = index + + updateFormAnimated(true) + } +} + +private extension LoginFormController { + func createGestureRecognizers() { + let tapGR = UITapGestureRecognizer(target: self, action: #selector(LoginFormController.tapOnView)) + view.addGestureRecognizer(tapGR) + } + + func createFormViews() { + formView = IncompressibleView() + formView.backgroundColor = .clear + formView.layer.cornerRadius = 5.0 + formView.layer.masksToBounds = true + contentContainerView.addSubview(formView) + + profileFakeTextField = UITextField() + profileFakeTextField.leftViewMode = .always + profileFakeTextField.leftView = iconContainerWithImageName("login-profile-icon") + profileFakeTextField.borderStyle = .roundedRect + profileFakeTextField.layer.borderColor = theme.colorForType(.LoginButtonBackground).cgColor + profileFakeTextField.layer.borderWidth = 0.5 + profileFakeTextField.layer.masksToBounds = true + profileFakeTextField.layer.cornerRadius = 6.0 + profileFakeTextField.isAccessibilityElement = false + profileFakeTextField.accessibilityElementsHidden = true + formView.addSubview(profileFakeTextField) + + profileButton = UIButton() + profileButton.addTarget(self, action: #selector(LoginFormController.profileButtonPressed), for:.touchUpInside) + profileButton.accessibilityLabel = String(localized: "profile_title") + formView.addSubview(profileButton) + + passwordField = UITextField() + passwordField.delegate = self + passwordField.placeholder = String(localized:"password") + passwordField.isSecureTextEntry = true + passwordField.returnKeyType = .go + passwordField.borderStyle = .roundedRect + passwordField.leftViewMode = .always + passwordField.leftView = iconContainerWithImageName("login-password-icon") + passwordField.layer.borderColor = theme.colorForType(.LoginButtonBackground).cgColor + passwordField.layer.borderWidth = 0.5 + passwordField.layer.masksToBounds = true + passwordField.layer.cornerRadius = 6.0 + formView.addSubview(passwordField) + } + + func createLoginButton() { + loginButton = RoundedButton(theme: theme, type: .login) + loginButton.setTitle(String(localized:"log_in"), for: UIControlState()) + loginButton.addTarget(self, action: #selector(LoginFormController.loginButtonPressed), for: .touchUpInside) + contentContainerView.addSubview(loginButton) + } + + func createBottomButtons() { + bottomButtonsContainer = UIView() + bottomButtonsContainer.backgroundColor = .clear + contentContainerView.addSubview(bottomButtonsContainer) + + createAccountButton = createDescriptionButtonWithTitle( + String(localized: "create_profile"), + action: #selector(LoginFormController.createAccountButtonPressed)) + bottomButtonsContainer.addSubview(createAccountButton) + + orLabel = UILabel() + orLabel.text = String(localized: "login_or_label") + orLabel.textColor = theme.colorForType(.LoginButtonBackground) + orLabel.backgroundColor = .clear + bottomButtonsContainer.addSubview(orLabel) + + importProfileButton = createDescriptionButtonWithTitle( + String(localized:"import_to_antidote"), + action: #selector(LoginFormController.importProfileButtonPressed)) + bottomButtonsContainer.addSubview(importProfileButton) + } + + func installConstraints() { + formView.customIntrinsicContentSize.width = CGFloat(Constants.MaxFormWidth) + formView.snp.makeConstraints { + $0.top.equalTo(contentContainerView) + $0.centerX.equalTo(contentContainerView) + $0.width.lessThanOrEqualTo(Constants.MaxFormWidth) + $0.width.lessThanOrEqualTo(contentContainerView).offset(-2 * Constants.HorizontalOffset) + } + + profileFakeTextField.snp.makeConstraints { + $0.edges.equalTo(profileButton) + } + + profileButton.snp.makeConstraints { + $0.top.equalTo(formView).offset(PrivateConstants.FormOffset) + $0.leading.equalTo(formView) + $0.trailing.equalTo(formView) + profileButtonBottomToFormConstraint = $0.bottom.equalTo(formView).offset(-PrivateConstants.FormOffset).constraint + + $0.height.equalTo(Constants.TextFieldHeight) + } + + passwordField.snp.makeConstraints { + $0.top.equalTo(profileButton.snp.bottom).offset(PrivateConstants.FormSmallOffset) + $0.leading.trailing.equalTo(profileButton) + passwordFieldBottomToFormConstraint = $0.bottom.equalTo(formView).offset(-PrivateConstants.FormOffset).constraint + + $0.height.equalTo(Constants.TextFieldHeight) + } + + loginButton.snp.makeConstraints { + $0.top.equalTo(formView.snp.bottom).offset(PrivateConstants.FormSmallOffset) + $0.leading.trailing.equalTo(formView) + } + + bottomButtonsContainer.snp.makeConstraints { + $0.top.greaterThanOrEqualTo(loginButton.snp.bottom).offset(PrivateConstants.FormOffset) + $0.centerX.equalTo(view) + $0.bottom.equalTo(view).offset(-PrivateConstants.FormOffset) + } + + createAccountButton.snp.makeConstraints { + $0.top.leading.bottom.equalTo(bottomButtonsContainer) + } + + orLabel.snp.makeConstraints { + $0.centerY.equalTo(bottomButtonsContainer) + $0.leading.equalTo(createAccountButton.snp.trailing).offset(PrivateConstants.FormSmallOffset) + $0.trailing.equalTo(importProfileButton.snp.leading).offset(-PrivateConstants.FormSmallOffset) + } + + importProfileButton.snp.makeConstraints { + $0.top.trailing.bottom.equalTo(bottomButtonsContainer) + } + } + + func iconContainerWithImageName(_ imageName: String) -> UIView { + let image = UIImage.templateNamed(imageName) + + let imageView = UIImageView(image: image) + imageView.tintColor = UIColor(white: 200.0/255.0, alpha:1.0) + + let container = UIView() + container.backgroundColor = .clear + container.addSubview(imageView) + + container.frame.size.width = PrivateConstants.IconContainerWidth + container.frame.size.height = PrivateConstants.IconContainerHeight + + imageView.frame.origin.x = PrivateConstants.IconContainerWidth - imageView.frame.size.width + imageView.frame.origin.y = (PrivateConstants.IconContainerHeight - imageView.frame.size.height) / 2 - 1.0 + + return container + } + + func createDescriptionButtonWithTitle(_ title: String, action: Selector) -> UIButton { + let button = UIButton() + button.setTitle(title, for: UIControlState()) + button.setTitleColor(theme.colorForType(.LoginLinkColor), for: UIControlState()) + button.titleLabel?.font = UIFont.systemFont(ofSize: 16.0) + button.addTarget(self, action: action, for: .touchUpInside) + + return button + } + + func updateFormAnimated(_ animated: Bool) { + let profileName = profileNames[selectedIndex] + + profileFakeTextField.text = profileName + profileButton.accessibilityValue = profileName + passwordField.text = nil + + let isEncrypted = delegate?.loginFormController(self, isProfileEncrypted: profileName) ?? false + + showPasswordField(isEncrypted, animated: animated) + } + + func showPasswordField(_ show: Bool, animated: Bool) { + func updateForm() { + if (show) { + profileButtonBottomToFormConstraint.deactivate() + passwordFieldBottomToFormConstraint.activate() + passwordField.alpha = 1.0 + } + else { + profileButtonBottomToFormConstraint.activate() + passwordFieldBottomToFormConstraint.deactivate() + passwordField.alpha = 0.0 + } + + view.layoutIfNeeded() + } + + if animated { + UIView.animate(withDuration: PrivateConstants.AnimationDuration, animations: updateForm) + } + else { + updateForm() + } + } +} diff --git a/Antidote/LoginGenericCreateController.swift b/Antidote/LoginGenericCreateController.swift new file mode 100644 index 0000000..e44ebf4 --- /dev/null +++ b/Antidote/LoginGenericCreateController.swift @@ -0,0 +1,154 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import SnapKit + +private struct PrivateConstants { + static let FieldsOffset = 20.0 + static let VerticalOffset = 30.0 +} + +class LoginGenericCreateController: LoginBaseController { + fileprivate var containerView: IncompressibleView! + fileprivate var containerViewTopConstraint: Constraint! + + var titleLabel: UILabel! + var firstTextField: ExtendedTextField! + var secondTextField: ExtendedTextField! + var bottomButton: RoundedButton! + + override func loadView() { + super.loadView() + + createGestureRecognizers() + createContainerView() + createTitleLabel() + createExtendedTextFields() + createGoButton() + + configureViews() + installConstraints() + } + + override func keyboardWillShowAnimated(keyboardFrame frame: CGRect) { + if containerView.frame.isEmpty { + return + } + let underFormHeight = containerView.frame.size.height - secondTextField.frame.maxY + + let offset = min(0.0, underFormHeight - frame.height) + + containerViewTopConstraint.update(offset: offset) + view.layoutIfNeeded() + } + + override func keyboardWillHideAnimated(keyboardFrame frame: CGRect) { + containerViewTopConstraint.update(offset: 0.0) + view.layoutIfNeeded() + } + + func configureViews() { + fatalError("override in subclass") + } +} + +extension LoginGenericCreateController { + @objc func tapOnView() { + view.endEditing(true) + } + + @objc func bottomButtonPressed() { + fatalError("override in subclass") + } +} + +extension LoginGenericCreateController: ExtendedTextFieldDelegate { + func loginExtendedTextFieldReturnKeyPressed(_ field: ExtendedTextField) { + if field == firstTextField { + _ = secondTextField.becomeFirstResponder() + } + else if field == secondTextField { + view.endEditing(true) + bottomButtonPressed() + } + } +} + +private extension LoginGenericCreateController { + func createGestureRecognizers() { + let tapGR = UITapGestureRecognizer(target: self, action: #selector(LoginCreateAccountController.tapOnView)) + view.addGestureRecognizer(tapGR) + } + + func createContainerView() { + containerView = IncompressibleView() + containerView.backgroundColor = .clear + view.addSubview(containerView) + } + + func createTitleLabel() { + titleLabel = UILabel() + titleLabel.textColor = theme.colorForType(.LoginButtonBackground) + titleLabel.font = UIFont.antidoteFontWithSize(26.0, weight: .light) + titleLabel.backgroundColor = .clear + containerView.addSubview(titleLabel) + } + + func createExtendedTextFields() { + firstTextField = ExtendedTextField(theme: theme, type: .login) + firstTextField.delegate = self + firstTextField.returnKeyType = .next + containerView.addSubview(firstTextField) + + secondTextField = ExtendedTextField(theme: theme, type: .login) + secondTextField.delegate = self + secondTextField.returnKeyType = .go + containerView.addSubview(secondTextField) + } + + func createGoButton() { + bottomButton = RoundedButton(theme: theme, type: .login) + bottomButton.addTarget(self, action: #selector(LoginCreateAccountController.bottomButtonPressed), for: .touchUpInside) + containerView.addSubview(bottomButton) + } + + func installConstraints() { + containerView.customIntrinsicContentSize.width = CGFloat(Constants.MaxFormWidth) + containerView.snp.makeConstraints { + containerViewTopConstraint = $0.top.equalTo(view).constraint + $0.centerX.equalTo(view) + $0.width.lessThanOrEqualTo(Constants.MaxFormWidth) + $0.width.lessThanOrEqualTo(view).offset(-2 * Constants.HorizontalOffset) + $0.height.equalTo(view) + } + + titleLabel.snp.makeConstraints { + $0.top.equalTo(containerView).offset(PrivateConstants.VerticalOffset) + $0.centerX.equalTo(containerView) + } + + firstTextField.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(PrivateConstants.FieldsOffset) + $0.leading.equalTo(containerView) + $0.trailing.equalTo(containerView) + } + + secondTextField.snp.makeConstraints { + $0.leading.trailing.equalTo(firstTextField) + + if firstTextField.isHidden { + $0.top.equalTo(titleLabel.snp.bottom).offset(PrivateConstants.FieldsOffset) + } + else { + $0.top.equalTo(firstTextField.snp.bottom).offset(PrivateConstants.FieldsOffset) + } + } + + bottomButton.snp.makeConstraints { + $0.top.equalTo(secondTextField.snp.bottom).offset(PrivateConstants.VerticalOffset) + $0.leading.trailing.equalTo(firstTextField) + } + } +} diff --git a/Antidote/LoginLogoController.swift b/Antidote/LoginLogoController.swift new file mode 100644 index 0000000..f12c3bc --- /dev/null +++ b/Antidote/LoginLogoController.swift @@ -0,0 +1,89 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct PrivateConstants { + static let LogoTopOffset = -200.0 + static let LogoHeight = 100.0 +} + +class LoginLogoController: LoginBaseController { + /** + * Main view, which is used as container for all subviews. + */ + var mainContainerView: UIView! + var mainContainerViewTopConstraint: Constraint? + + var logoImageView: UIImageView! + + /** + * Use this container to add subviews in subclasses. + * Is placed under logo. + */ + var contentContainerView: UIView! + + override func loadView() { + super.loadView() + + createMainContainerView() + createLogoImageView() + createContainerView() + + installConstraints() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController?.setNavigationBarHidden(true, animated:animated) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + navigationController?.setNavigationBarHidden(false, animated:animated) + } +} + +private extension LoginLogoController { + func createMainContainerView() { + mainContainerView = UIView() + mainContainerView.backgroundColor = .clear + view.addSubview(mainContainerView) + } + + func createLogoImageView() { + let image = UIImage(named: "login-logo") + + logoImageView = UIImageView(image: image) + logoImageView.contentMode = .scaleAspectFit + mainContainerView.addSubview(logoImageView) + } + + func createContainerView() { + contentContainerView = UIView() + contentContainerView.backgroundColor = .clear + mainContainerView.addSubview(contentContainerView) + } + + func installConstraints() { + mainContainerView.snp.makeConstraints { + mainContainerViewTopConstraint = $0.top.equalTo(view).constraint + $0.leading.trailing.equalTo(view) + $0.height.equalTo(view) + } + + logoImageView.snp.makeConstraints { + $0.centerX.equalTo(mainContainerView) + $0.top.equalTo(mainContainerView.snp.centerY).offset(PrivateConstants.LogoTopOffset) + $0.height.equalTo(PrivateConstants.LogoHeight) + } + + contentContainerView.snp.makeConstraints { + $0.top.equalTo(logoImageView.snp.bottom).offset(Constants.VerticalOffset) + $0.bottom.equalTo(mainContainerView) + $0.leading.trailing.equalTo(mainContainerView) + } + } +} diff --git a/Antidote/NSDateFormatterExtension.swift b/Antidote/NSDateFormatterExtension.swift new file mode 100644 index 0000000..42e52a4 --- /dev/null +++ b/Antidote/NSDateFormatterExtension.swift @@ -0,0 +1,35 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +extension DateFormatter { + enum FormatterType { + case time + case dateAndTime + case relativeDate + case relativeDateAndTime + } + + convenience init(type: FormatterType) { + self.init() + + switch type { + case .time: + dateFormat = "H:mm" + case .dateAndTime: + dateStyle = .short + timeStyle = .short + doesRelativeDateFormatting = false + case .relativeDate: + dateStyle = .short + timeStyle = .none + doesRelativeDateFormatting = true + case .relativeDateAndTime: + dateStyle = .short + timeStyle = .short + doesRelativeDateFormatting = true + } + } +} diff --git a/Antidote/NSTimerExtension.swift b/Antidote/NSTimerExtension.swift new file mode 100644 index 0000000..f0d98b5 --- /dev/null +++ b/Antidote/NSTimerExtension.swift @@ -0,0 +1,30 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +private class ClosureWrapper { + let closure: T + + init(closure: T) { + self.closure = closure + } +} + +extension Timer { + static func scheduledTimer(timeInterval interval: TimeInterval,closure:@escaping(Timer) -> Void, repeats: Bool) -> Timer { + // TODO: check if escaping is actually correct here + let userInfo = ClosureWrapper(closure: closure) + + return scheduledTimer(timeInterval: interval, target: self, selector: #selector(Timer.executeBlock(_:)), userInfo: userInfo, repeats: repeats) + } + + @objc static func executeBlock(_ timer: Timer) { + guard let wrapper = timer.userInfo as? ClosureWrapper<(Timer) -> Void> else { + return + } + + wrapper.closure(timer) + } +} diff --git a/Antidote/NSURLExtension.swift b/Antidote/NSURLExtension.swift new file mode 100644 index 0000000..10ce0be --- /dev/null +++ b/Antidote/NSURLExtension.swift @@ -0,0 +1,29 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import MobileCoreServices + +extension URL { + func isToxURL() -> Bool { + guard isFileURL else { + return false + } + + let request = URLRequest(url: self, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 0.1) + var response: URLResponse? = nil + + _ = try? NSURLConnection.sendSynchronousRequest(request, returning: &response) + + guard let mimeType = response?.mimeType else { + return false + } + + guard let identifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType as CFString, nil)?.takeRetainedValue() else { + return false + } + + return UTTypeEqual(identifier, kUTTypeData) + } +} diff --git a/Antidote/NotificationCoordinator.swift b/Antidote/NotificationCoordinator.swift new file mode 100644 index 0000000..fbff789 --- /dev/null +++ b/Antidote/NotificationCoordinator.swift @@ -0,0 +1,398 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +private enum NotificationType { + case newMessage(OCTMessageAbstract) + case friendRequest(OCTFriendRequest) +} + +private struct Constants { + static let NotificationVisibleDuration = 3.0 +} + +protocol NotificationCoordinatorDelegate: class { + func notificationCoordinator(_ coordinator: NotificationCoordinator, showChat chat: OCTChat) + func notificationCoordinatorShowFriendRequest(_ coordinator: NotificationCoordinator, showRequest request: OCTFriendRequest) + func notificationCoordinatorAnswerIncomingCall(_ coordinator: NotificationCoordinator, userInfo: String) + + func notificationCoordinator(_ coordinator: NotificationCoordinator, updateFriendsBadge badge: Int) + func notificationCoordinator(_ coordinator: NotificationCoordinator, updateChatsBadge badge: Int) +} + +class NotificationCoordinator: NSObject { + weak var delegate: NotificationCoordinatorDelegate? + + fileprivate let theme: Theme + fileprivate let userDefaults = UserDefaultsManager() + + fileprivate let notificationWindow: NotificationWindow + + fileprivate weak var submanagerObjects: OCTSubmanagerObjects! + + fileprivate var messagesToken: RLMNotificationToken? + fileprivate var chats: Results + fileprivate var chatsToken: RLMNotificationToken? + fileprivate var requests: Results + fileprivate var requestsToken: RLMNotificationToken? + + fileprivate let avatarManager: AvatarManager + fileprivate let audioPlayer = AlertAudioPlayer() + + fileprivate var notificationQueue = [NotificationType]() + fileprivate var inAppNotificationAppIdsRegistered = [String: Bool]() + fileprivate var bannedChatIdentifiers = Set() + + init(theme: Theme, submanagerObjects: OCTSubmanagerObjects) { + self.theme = theme + self.notificationWindow = NotificationWindow(theme: theme) + + self.submanagerObjects = submanagerObjects + self.avatarManager = AvatarManager(theme: theme) + + let predicate = NSPredicate(format: "lastMessage.dateInterval > lastReadDateInterval") + self.chats = submanagerObjects.chats(predicate: predicate) + self.requests = submanagerObjects.friendRequests() + + super.init() + + addNotificationBlocks() + + NotificationCenter.default.addObserver(self, selector: #selector(NotificationCoordinator.applicationDidBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + + messagesToken?.invalidate() + chatsToken?.invalidate() + requestsToken?.invalidate() + } + + /** + Show or hide connnecting view. + */ + func toggleConnectingView(show: Bool, animated: Bool) { + notificationWindow.showConnectingView(show, animated: animated) + } + + /** + Stops showing notifications for given chat. + Also removes all related to that chat notifications from queue. + */ + func banNotificationsForChat(_ chat: OCTChat) { + bannedChatIdentifiers.insert(chat.uniqueIdentifier) + + notificationQueue = notificationQueue.filter { + switch $0 { + case .newMessage(let messageAbstract): + return messageAbstract.chatUniqueIdentifier != chat.uniqueIdentifier + case .friendRequest: + return true + } + } + + LNNotificationCenter.default().clearPendingNotifications(forApplicationIdentifier: chat.uniqueIdentifier); + } + + /** + Unban notifications for given chat (if they were banned before). + */ + func unbanNotificationsForChat(_ chat: OCTChat) { + bannedChatIdentifiers.remove(chat.uniqueIdentifier) + } + + func handleLocalNotification(_ notification: UILocalNotification) { + guard let userInfo = notification.userInfo as? [String: String] else { + return + } + + guard let action = NotificationAction(dictionary: userInfo) else { + return + } + + performAction(action) + } + + func showCallNotificationWithCaller(_ caller: String, userInfo: String) { + let object = NotificationObject( + title: caller, + body: String(localized: "notification_is_calling"), + action: .answerIncomingCall(userInfo: userInfo), + soundName: "isotoxin_Ringtone.aac") + + showLocalNotificationObject(object) + } + + func registerInAppNotificationAppId(_ appId: String) { + if inAppNotificationAppIdsRegistered[appId] == nil { + LNNotificationCenter.default().registerApplication(withIdentifier: appId, name: Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String, icon: UIImage(named: "notification-app-icon"), defaultSettings: LNNotificationAppSettings.default()) + inAppNotificationAppIdsRegistered[appId] = true + } + } +} + +extension NotificationCoordinator: CoordinatorProtocol { + func startWithOptions(_ options: CoordinatorOptions?) { + let settings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) + + let application = UIApplication.shared + application.registerUserNotificationSettings(settings) + application.cancelAllLocalNotifications() + + updateBadges() + } +} + +// MARK: Notifications +extension NotificationCoordinator { + @objc func applicationDidBecomeActive() { + UIApplication.shared.cancelAllLocalNotifications() + } +} + +private extension NotificationCoordinator { + func addNotificationBlocks() { + let messages = submanagerObjects.messages().sortedResultsUsingProperty("dateInterval", ascending: false) + messagesToken = messages.addNotificationBlock { [unowned self] change in + switch change { + case .initial: + break + case .update(let messages, _, let insertions, _): + guard let messages = messages else { + break + } + if insertions.contains(0) { + let message = messages[0] + + self.playSoundForMessageIfNeeded(message) + + if self.shouldEnqueueMessage(message) { + self.enqueueNotification(.newMessage(message)) + } + } + case .error(let error): + fatalError("\(error)") + } + } + + chatsToken = chats.addNotificationBlock { [unowned self] change in + switch change { + case .initial: + break + case .update: + self.updateBadges() + case .error(let error): + fatalError("\(error)") + } + } + + requestsToken = requests.addNotificationBlock { [unowned self] change in + switch change { + case .initial: + break + case .update(let requests, _, let insertions, _): + guard let requests = requests else { + break + } + for index in insertions { + let request = requests[index] + + self.audioPlayer.playSound(.NewMessage) + self.enqueueNotification(.friendRequest(request)) + } + self.updateBadges() + case .error(let error): + fatalError("\(error)") + } + } + } + + func playSoundForMessageIfNeeded(_ message: OCTMessageAbstract) { + if message.isOutgoing() { + return + } + + if message.messageText != nil || message.messageFile != nil { + audioPlayer.playSound(.NewMessage) + } + } + + func shouldEnqueueMessage(_ message: OCTMessageAbstract) -> Bool { + if message.isOutgoing() { + return false + } + + if UIApplication.isActive && bannedChatIdentifiers.contains(message.chatUniqueIdentifier) { + return false + } + + if message.messageText != nil || message.messageFile != nil { + return true + } + + return false + } + + func enqueueNotification(_ notification: NotificationType) { + notificationQueue.append(notification) + + showNextNotification() + } + + func showNextNotification() { + if notificationQueue.isEmpty { + return + } + + let notification = notificationQueue.removeFirst() + let object = notificationObjectFromNotification(notification) + + if UIApplication.isActive { + switch notification { + case .newMessage(let messageAbstract): + showInAppNotificationObject(object, chatUniqueIdentifier: messageAbstract.chatUniqueIdentifier) + default: + showInAppNotificationObject(object, chatUniqueIdentifier: nil) + } + } + else { + showLocalNotificationObject(object) + } + } + + func showInAppNotificationObject(_ object: NotificationObject, chatUniqueIdentifier: String?) { + var appId:String + + if chatUniqueIdentifier != nil { + appId = chatUniqueIdentifier! + } else { + appId = Bundle.main.bundleIdentifier! + } + + registerInAppNotificationAppId(appId); + + let notification = LNNotification.init(message: object.body, title: object.title) + notification?.defaultAction = LNNotificationAction.init(title: nil, handler: { [weak self] _ in + self?.performAction(object.action) + }) + + LNNotificationCenter.default().present(notification, forApplicationIdentifier: appId) + + showNextNotification() + } + + func showLocalNotificationObject(_ object: NotificationObject) { + let local = UILocalNotification() + local.alertBody = "\(object.title): \(object.body)" + local.userInfo = object.action.archive() + local.soundName = object.soundName + + UIApplication.shared.presentLocalNotificationNow(local) + + showNextNotification() + } + + func notificationObjectFromNotification(_ notification: NotificationType) -> NotificationObject { + switch notification { + case .friendRequest(let request): + return notificationObjectFromRequest(request) + case .newMessage(let message): + return notificationObjectFromMessage(message) + } + } + + func notificationObjectFromRequest(_ request: OCTFriendRequest) -> NotificationObject { + let title = String(localized: "notification_incoming_contact_request") + let body = request.message ?? "" + let action = NotificationAction.openRequest(requestUniqueIdentifier: request.uniqueIdentifier) + + return NotificationObject(title: title, body: body, action: action, soundName: "isotoxin_NewMessage.aac") + } + + func notificationObjectFromMessage(_ message: OCTMessageAbstract) -> NotificationObject { + let title: String + + if let friend = submanagerObjects.object(withUniqueIdentifier: message.senderUniqueIdentifier, for: .friend) as? OCTFriend { + title = friend.nickname + } + else { + title = "" + } + + var body: String = "" + let action = NotificationAction.openChat(chatUniqueIdentifier: message.chatUniqueIdentifier) + + if let messageText = message.messageText { + let defaultString = String(localized: "notification_new_message") + + if userDefaults.showNotificationPreview { + body = messageText.text ?? defaultString + } + else { + body = defaultString + } + } + else if let messageFile = message.messageFile { + let defaultString = String(localized: "notification_incoming_file") + + if userDefaults.showNotificationPreview { + body = messageFile.fileName ?? defaultString + } + else { + body = defaultString + } + } + + return NotificationObject(title: title, body: body, action: action, soundName: "isotoxin_NewMessage.aac") + } + + func performAction(_ action: NotificationAction) { + switch action { + case .openChat(let identifier): + guard let chat = submanagerObjects.object(withUniqueIdentifier: identifier, for: .chat) as? OCTChat else { + return + } + + delegate?.notificationCoordinator(self, showChat: chat) + banNotificationsForChat(chat) + case .openRequest(let identifier): + guard let request = submanagerObjects.object(withUniqueIdentifier: identifier, for: .friendRequest) as? OCTFriendRequest else { + return + } + + delegate?.notificationCoordinatorShowFriendRequest(self, showRequest: request) + case .answerIncomingCall(let userInfo): + delegate?.notificationCoordinatorAnswerIncomingCall(self, userInfo: userInfo) + } + } + + func updateBadges() { + let chatsCount = chats.count + let requestsCount = requests.count + + delegate?.notificationCoordinator(self, updateChatsBadge: chatsCount) + delegate?.notificationCoordinator(self, updateFriendsBadge: requestsCount) + + UIApplication.shared.applicationIconBadgeNumber = chatsCount + requestsCount + } + + // func chatsBadge() -> Int { + // // TODO update to new Realm and filter unread chats with predicate "lastMessage.dateInterval > lastReadDateInterval" + // var badge = 0 + + // for index in 0.. [String: String] { + switch self { + case .openChat(let identifier): + return [ + Constants.ValueKey: Constants.OpenChatValue, + Constants.ArgumentKey: identifier, + ] + case .openRequest(let identifier): + return [ + Constants.ValueKey: Constants.OpenRequestValue, + Constants.ArgumentKey: identifier, + ] + case .answerIncomingCall(let userInfo): + return [ + Constants.ValueKey: Constants.AnswerIncomingCallValue, + Constants.ArgumentKey: userInfo, + ] + } + } +} + +struct NotificationObject { + /// Title of notification + let title: String + + /// Body text of notification + let body: String + + /// Action to be fired when user interacts with notification + let action: NotificationAction + + /// Sound to play when notification is fired. Valid only for local notifications. + let soundName: String +} diff --git a/Antidote/NotificationWindow.swift b/Antidote/NotificationWindow.swift new file mode 100644 index 0000000..6f1de7a --- /dev/null +++ b/Antidote/NotificationWindow.swift @@ -0,0 +1,133 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let AnimationDuration = 0.3 + static let ConnectingBlinkPeriod = 1.0 +} + +class NotificationWindow: UIWindow { + fileprivate let theme: Theme + + fileprivate var connectingView: UIView! + fileprivate var connectingViewLabel: UILabel! + fileprivate var connectingViewTopConstraint: Constraint! + + init(theme: Theme) { + self.theme = theme + + super.init(frame: UIScreen.main.bounds) + + windowLevel = UIWindowLevelStatusBar + 500 + backgroundColor = .clear + isHidden = false + + createRootViewController() + createConnectingView() + startConnectingViewAnimation() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + for subview in subviews { + let converted = convert(point, to: subview) + + if subview.hitTest(converted, with: event) != nil { + return true + } + } + + return false + } + + func showConnectingView(_ show: Bool, animated: Bool) { + let showPreparation = { [unowned self] in + self.connectingView.isHidden = false + } + + let showBlock = { [unowned self] in + self.connectingViewTopConstraint.update(offset: 0.0) + self.layoutIfNeeded() + + self.startConnectingViewAnimation() + } + + let showCompletion = {} + + let hidePreparation = {} + + let hideBlock = { [unowned self] in + self.connectingViewTopConstraint.update(offset: -self.connectingView.frame.size.height) + self.layoutIfNeeded() + self.connectingViewLabel.layer.removeAllAnimations() + } + + let hideCompletion = { [unowned self] in + self.connectingView.isHidden = true + } + + show ? showPreparation() : hidePreparation() + + if animated { + UIView.animate(withDuration: Constants.AnimationDuration, animations: { + show ? showBlock() : hideBlock() + }, completion: { finished in + show ? showCompletion() : hideCompletion() + }) + } + else { + show ? showBlock() : hideBlock() + show ? showCompletion() : hideCompletion() + } + } +} + +private extension NotificationWindow { + func createRootViewController() { + rootViewController = UIViewController() + rootViewController!.view = ViewPassingGestures() + rootViewController!.view.backgroundColor = .clear + } + + func createConnectingView() { + connectingView = UIView() + connectingView.backgroundColor = theme.colorForType(.ConnectingBackground) + addSubview(connectingView) + + connectingViewLabel = UILabel() + connectingViewLabel.textColor = theme.colorForType(.ConnectingText) + connectingViewLabel.backgroundColor = .clear + connectingViewLabel.text = String(localized: "connecting_label") + connectingViewLabel.textAlignment = .center + connectingViewLabel.font = UIFont.antidoteFontWithSize(12.0, weight: .light) + connectingView.addSubview(connectingViewLabel) + + connectingView.snp.makeConstraints { + connectingViewTopConstraint = $0.top.equalTo(self).constraint + $0.leading.trailing.equalTo(self) + $0.height.equalTo(UIApplication.shared.statusBarFrame.size.height) + } + + connectingViewLabel.snp.makeConstraints { + $0.edges.equalTo(connectingView) + } + } + + func startConnectingViewAnimation() { + connectingViewLabel.alpha = 0.0 + UIView.animate(withDuration: Constants.ConnectingBlinkPeriod, delay: 0.0, options: [.repeat, .autoreverse], animations: { + self.connectingViewLabel.alpha = 1.0 + }, completion: nil) + } + + func stopConnectingViewAnimation() { + stopConnectingViewAnimation() + } +} diff --git a/Antidote/OCTManagerConfigurationExtension.swift b/Antidote/OCTManagerConfigurationExtension.swift new file mode 100644 index 0000000..d229131 --- /dev/null +++ b/Antidote/OCTManagerConfigurationExtension.swift @@ -0,0 +1,25 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +extension OCTManagerConfiguration { + static func configurationWithBaseDirectory(_ baseDirectory: String) -> OCTManagerConfiguration? { + var isDirectory: ObjCBool = false + let exists = FileManager.default.fileExists(atPath: baseDirectory, isDirectory:&isDirectory) + + guard exists && isDirectory.boolValue else { + return nil + } + + let configuration = OCTManagerConfiguration.default() + + let userDefaultsManager = UserDefaultsManager() + + configuration.options.ipv6Enabled = true + configuration.options.udpEnabled = userDefaultsManager.UDPEnabled + + configuration.fileStorage = OCTDefaultFileStorage(baseDirectory: baseDirectory, temporaryDirectory: NSTemporaryDirectory()) + + return configuration + } +} diff --git a/Antidote/OCTManagerMock.swift b/Antidote/OCTManagerMock.swift new file mode 100644 index 0000000..167d8f8 --- /dev/null +++ b/Antidote/OCTManagerMock.swift @@ -0,0 +1,198 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import MobileCoreServices + +private enum Gender { + case male + case female +} + +class OCTManagerMock: NSObject, OCTManager { + var bootstrap: OCTSubmanagerBootstrap + var calls: OCTSubmanagerCalls + var chats: OCTSubmanagerChats + var files: OCTSubmanagerFiles + var friends: OCTSubmanagerFriends + var objects: OCTSubmanagerObjects + var user: OCTSubmanagerUser + + var realm: RLMRealm + + override init() { + let configuration = RLMRealmConfiguration.default() + configuration.inMemoryIdentifier = "test realm" + realm = try! RLMRealm(configuration: configuration) + + bootstrap = OCTSubmanagerBootstrapMock() + calls = OCTSubmanagerCallsMock() + chats = OCTSubmanagerChatsMock() + files = OCTSubmanagerFilesMock() + friends = OCTSubmanagerFriendsMock() + objects = OCTSubmanagerObjectsMock(realm: realm) + user = OCTSubmanagerUserMock() + + super.init() + + populateRealm() + } + + func configuration() -> OCTManagerConfiguration { + return OCTManagerConfiguration() + } + + func exportToxSaveFile() throws -> String { + return "123" + } + + func changeEncryptPassword(_ newPassword: String, oldPassword: String) -> Bool { + return true + } + + func isManagerEncrypted(withPassword password: String) -> Bool { + return true + } +} + +private extension OCTManagerMock { + func populateRealm() { + realm.beginWriteTransaction() + + let f1 = addFriend(gender: .female, number: 1, connectionStatus: .TCP, status: .none) + let f2 = addFriend(gender: .male, number: 1, connectionStatus: .TCP, status: .busy) + let f3 = addFriend(gender: .female, number: 2, connectionStatus: .none, status: .none) + let f4 = addFriend(gender: .male, number: 2, connectionStatus: .TCP, status: .away) + let f5 = addFriend(gender: .male, number: 3, connectionStatus: .TCP, status: .none) + let f6 = addFriend(gender: .female, number: 3, connectionStatus: .TCP, status: .away) + let f7 = addFriend(gender: .male, number: 4, connectionStatus: .TCP, status: .away) + let f8 = addFriend(gender: .female, number: 4, connectionStatus: .none, status: .none) + let f9 = addFriend(gender: .female, number: 5, connectionStatus: .TCP, status: .none) + let f10 = addFriend(gender: .male, number: 5, connectionStatus: .none, status: .none) + + let c1 = addChat(friend: f1) + let c2 = addChat(friend: f2) + let c3 = addChat(friend: f3) + let c4 = addChat(friend: f4) + let c5 = addChat(friend: f5) + let c6 = addChat(friend: f6) + let c7 = addChat(friend: f7) + let c8 = addChat(friend: f8) + let c9 = addChat(friend: f9) + let c10 = addChat(friend: f10) + + addDemoConversationToChat(c1) + addCallMessage(chat: c2, outgoing: false, answered: false, duration: 0.0) + addTextMessage(chat: c3, outgoing: false, text: String(localized: "app_store_screenshot_chat_message_1")) + addCallMessage(chat: c4, outgoing: true, answered: true, duration: 1473.0) + addTextMessage(chat: c5, outgoing: false, text: String(localized: "app_store_screenshot_chat_message_2")) + addFileMessage(chat: c6, outgoing: false, fileName: "party.png") + addTextMessage(chat: c7, outgoing: true, text: String(localized: "app_store_screenshot_chat_message_3")) + addTextMessage(chat: c8, outgoing: true, text: String(localized: "app_store_screenshot_chat_message_4")) + addFileMessage(chat: c9, outgoing: true, fileName: "presentation_2016.pdf") + addTextMessage(chat: c10, outgoing: false, text: String(localized: "app_store_screenshot_chat_message_5")) + + c1.lastReadDateInterval = Date().timeIntervalSince1970 + // unread message + // c2.lastReadDateInterval = NSDate().timeIntervalSince1970 + c3.lastReadDateInterval = Date().timeIntervalSince1970 + c4.lastReadDateInterval = Date().timeIntervalSince1970 + c5.lastReadDateInterval = Date().timeIntervalSince1970 + c6.lastReadDateInterval = Date().timeIntervalSince1970 + c7.lastReadDateInterval = Date().timeIntervalSince1970 + c8.lastReadDateInterval = Date().timeIntervalSince1970 + c9.lastReadDateInterval = Date().timeIntervalSince1970 + c10.lastReadDateInterval = Date().timeIntervalSince1970 + + try! realm.commitWriteTransaction() + } + + func addFriend(gender: Gender, + number: Int, + connectionStatus: OCTToxConnectionStatus, + status: OCTToxUserStatus) -> OCTFriend { + let friend = OCTFriend() + friend.publicKey = "123" + friend.connectionStatus = connectionStatus + friend.isConnected = connectionStatus != .none + friend.status = status + + switch gender { + case .male: + friend.nickname = String(localized: "app_store_screenshot_friend_male_\(number)") + friend.avatarData = UIImagePNGRepresentation(UIImage(named: "male-\(number)")!) + case .female: + friend.nickname = String(localized: "app_store_screenshot_friend_female_\(number)") + friend.avatarData = UIImagePNGRepresentation(UIImage(named: "female-\(number)")!) + } + + realm.add(friend) + + return friend + } + + func addChat(friend: OCTFriend) -> OCTChat { + let chat = OCTChat() + + realm.add(chat) + chat.friends.add(friend) + + return chat + } + + func addDemoConversationToChat(_ chat: OCTChat) { + addFileMessage(chat: chat, outgoing: false, fileName: "party.png") + addTextMessage(chat: chat, outgoing: true, text: String(localized: "app_store_screenshot_conversation_1")) + addTextMessage(chat: chat, outgoing: false, text: String(localized: "app_store_screenshot_conversation_2")) + addTextMessage(chat: chat, outgoing: true, text: String(localized: "app_store_screenshot_conversation_3")) + addTextMessage(chat: chat, outgoing: false, text: String(localized: "app_store_screenshot_conversation_4")) + addTextMessage(chat: chat, outgoing: false, text: String(localized: "app_store_screenshot_conversation_5")) + addTextMessage(chat: chat, outgoing: true, text: String(localized: "app_store_screenshot_conversation_6")) + addTextMessage(chat: chat, outgoing: true, text: String(localized: "app_store_screenshot_conversation_7")) + } + + func addTextMessage(chat: OCTChat, outgoing: Bool, text: String) { + let messageText = OCTMessageText() + messageText.text = text + messageText.isDelivered = outgoing + + let message = addMessageAbstract(chat: chat, outgoing: outgoing) + message.messageText = messageText + } + + func addFileMessage(chat: OCTChat, outgoing: Bool, fileName: String) { + let messageFile = OCTMessageFile() + messageFile.fileName = fileName + messageFile.internalFilePath = Bundle.main.path(forResource: "dummy-photo", ofType: "jpg") + messageFile.fileType = .ready + messageFile.fileUTI = kUTTypeImage as String + + let message = addMessageAbstract(chat: chat, outgoing: outgoing) + message.messageFile = messageFile + } + + func addCallMessage(chat: OCTChat, outgoing: Bool, answered: Bool, duration: TimeInterval) { + let messageCall = OCTMessageCall() + messageCall.callDuration = duration + messageCall.callEvent = answered ? .answered : .unanswered + + let message = addMessageAbstract(chat: chat, outgoing: outgoing) + message.messageCall = messageCall + } + + func addMessageAbstract(chat: OCTChat, outgoing: Bool) -> OCTMessageAbstract { + let message = OCTMessageAbstract() + if !outgoing { + let friend = chat.friends.firstObject() as! OCTFriend + message.senderUniqueIdentifier = friend.uniqueIdentifier + } + message.chatUniqueIdentifier = chat.uniqueIdentifier + message.dateInterval = Date().timeIntervalSince1970 + + realm.add(message) + chat.lastMessage = message + + return message + } +} diff --git a/Antidote/OCTSubmanagerBootstrapMock.swift b/Antidote/OCTSubmanagerBootstrapMock.swift new file mode 100644 index 0000000..3f6f943 --- /dev/null +++ b/Antidote/OCTSubmanagerBootstrapMock.swift @@ -0,0 +1,19 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class OCTSubmanagerBootstrapMock: NSObject, OCTSubmanagerBootstrap { + func addNode(withIpv4Host ipv4Host: String?, ipv6Host: String?, udpPort: OCTToxPort, tcpPorts: [NSNumber], publicKey: String) { + // nop + } + + func addPredefinedNodes() { + // nop + } + + func bootstrap() { + // nop + } +} diff --git a/Antidote/OCTSubmanagerCallsMock.swift b/Antidote/OCTSubmanagerCallsMock.swift new file mode 100644 index 0000000..75fad15 --- /dev/null +++ b/Antidote/OCTSubmanagerCallsMock.swift @@ -0,0 +1,50 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class OCTSubmanagerCallsMock: NSObject, OCTSubmanagerCalls { + weak var delegate: OCTSubmanagerCallDelegate? = nil + var enableMicrophone: Bool = false + + func setup() throws { + // nop + } + + func call(to chat: OCTChat, enableAudio: Bool, enableVideo: Bool) throws -> OCTCall { + return OCTCall() + } + + func enableVideoSending(_ enable: Bool, for call: OCTCall) throws { + // nop + } + + func answer(_ call: OCTCall, enableAudio: Bool, enableVideo: Bool) throws { + // nop + } + + func send(_ control: OCTToxAVCallControl, to call: OCTCall) throws { + // nop + } + + func videoFeed() -> UIView? { + return nil + } + + func getVideoCallPreview(_ completionBlock: @escaping (CALayer?) -> Void) { + // nop + } + + func setAudioBitrate(_ bitrate: Int32, for call: OCTCall) throws { + // nop + } + + func routeAudio(toSpeaker speaker: Bool) throws { + // nop + } + + func `switch`(toCameraFront front: Bool) throws { + // nop + } +} diff --git a/Antidote/OCTSubmanagerCallsSnapshotMock.swift b/Antidote/OCTSubmanagerCallsSnapshotMock.swift new file mode 100644 index 0000000..f487783 --- /dev/null +++ b/Antidote/OCTSubmanagerCallsSnapshotMock.swift @@ -0,0 +1,50 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class OCTSubmanagerCallsScreenshotsMock: NSObject, OCTSubmanagerCalls { + weak var delegate: OCTSubmanagerCallDelegate? = nil + var enableMicrophone: Bool = false + + func setup() throws { + // nop + } + + func callToChat(chat: OCTChat, enableAudio: Bool, enableVideo: Bool) throws -> OCTCall { + return OCTCall() + } + + func enableVideoSending(enable: Bool, forCall call: OCTCall) throws { + // nop + } + + func answerCall(call: OCTCall, enableAudio: Bool, enableVideo: Bool) throws { + // nop + } + + func sendCallControl(control: OCTToxAVCallControl, toCall call: OCTCall) throws { + // nop + } + + func videoFeed() -> UIView? { + return nil + } + + func getVideoCallPreview(completionBlock: (CALayer?) -> Void) { + // nop + } + + func setAudioBitrate(bitrate: Int32, forCall call: OCTCall) throws { + // nop + } + + func routeAudioToSpeaker(speaker: Bool) throws { + // nop + } + + func switchToCameraFront(front: Bool) throws { + // nop + } +} diff --git a/Antidote/OCTSubmanagerChatsMock.swift b/Antidote/OCTSubmanagerChatsMock.swift new file mode 100644 index 0000000..b9c9cdd --- /dev/null +++ b/Antidote/OCTSubmanagerChatsMock.swift @@ -0,0 +1,40 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class OCTSubmanagerChatsMock: NSObject, OCTSubmanagerChats { + func getOrCreateChat(with friend: OCTFriend!) -> OCTChat! { + return OCTChat() + } + + func sendOwnPush() { + // nop + } + + func removeMessages(_ messages: [OCTMessageAbstract]!) { + // nop + } + + func removeAllMessages(in chat: OCTChat!, removeChat: Bool) { + // nop + } + + public func sendMessagePush(to chat: OCTChat!) + { + // nop + } + + public func sendMessage(to chat: OCTChat!, + text: String!, + type: OCTToxMessageType, + successBlock userSuccessBlock: ((OCTMessageAbstract?) -> Void)!, + failureBlock userFailureBlock: ((Error?) -> Void)!) { + // nop + } + + func setIsTyping(_ isTyping: Bool, in chat: OCTChat!) throws { + // nop + } +} diff --git a/Antidote/OCTSubmanagerFilesMock.swift b/Antidote/OCTSubmanagerFilesMock.swift new file mode 100644 index 0000000..373edd1 --- /dev/null +++ b/Antidote/OCTSubmanagerFilesMock.swift @@ -0,0 +1,39 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class OCTSubmanagerFilesMock: NSObject, OCTSubmanagerFiles { + func send(_ data: Data, withFileName fileName: String, to chat: OCTChat, failureBlock: ((Error) -> Void)? = nil) { + // nop + } + + func sendFile(atPath filePath: String, moveToUploads: Bool, to chat: OCTChat, failureBlock: ((Error) -> Void)? = nil) { + // nop + } + + func acceptFileTransfer(_ message: OCTMessageAbstract, failureBlock: ((Error) -> Void)? = nil) { + // nop + } + + func cancelFileTransfer(_ message: OCTMessageAbstract) throws { + // nop + } + + func retrySendingFile(_ message: OCTMessageAbstract, failureBlock: ((Error) -> Void)? = nil) { + // nop + } + + func pauseFileTransfer(_ pause: Bool, message: OCTMessageAbstract) throws { + // nop + } + + func add(_ subscriber: OCTSubmanagerFilesProgressSubscriber, forFileTransfer message: OCTMessageAbstract) throws { + // nop + } + + func remove(_ subscriber: OCTSubmanagerFilesProgressSubscriber, forFileTransfer message: OCTMessageAbstract) throws { + // nop + } +} diff --git a/Antidote/OCTSubmanagerFriendsMock.swift b/Antidote/OCTSubmanagerFriendsMock.swift new file mode 100644 index 0000000..c598d7e --- /dev/null +++ b/Antidote/OCTSubmanagerFriendsMock.swift @@ -0,0 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class OCTSubmanagerFriendsMock: NSObject, OCTSubmanagerFriends { + func sendFriendRequest(toAddress address: String!, message: String!) throws { + // nop + } + + func approve(_ friendRequest: OCTFriendRequest!) throws { + // nop + } + + func remove(_ friendRequest: OCTFriendRequest!) { + // nop + } + + func remove(_ friend: OCTFriend!) throws { + // nop + } +} diff --git a/Antidote/OCTSubmanagerObjectsExtension.swift b/Antidote/OCTSubmanagerObjectsExtension.swift new file mode 100644 index 0000000..987d574 --- /dev/null +++ b/Antidote/OCTSubmanagerObjectsExtension.swift @@ -0,0 +1,77 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +extension OCTSubmanagerObjects { + func friends(predicate: NSPredicate? = nil) -> Results { + let rlmResults = objects(for: .friend, predicate: predicate)! + return Results(results: rlmResults) + } + + func friendRequests(predicate: NSPredicate? = nil) -> Results { + let rlmResults = objects(for: .friendRequest, predicate: predicate)! + return Results(results: rlmResults) + } + + func chats(predicate: NSPredicate? = nil) -> Results { + let rlmResults = objects(for: .chat, predicate: predicate)! + return Results(results: rlmResults) + } + + func calls(predicate: NSPredicate? = nil) -> Results { + let rlmResults = objects(for: .call, predicate: predicate)! + return Results(results: rlmResults) + } + + func messages(predicate: NSPredicate? = nil) -> Results { + let rlmResults = objects(for: .messageAbstract, predicate: predicate)! + return Results(results: rlmResults) + } + + func getProfileSettings() -> ProfileSettings { + guard let data = self.genericSettingsData else { + return ProfileSettings() + } + + let unarchiver = NSKeyedUnarchiver(forReadingWith: data) + let settings = ProfileSettings(coder: unarchiver) + unarchiver.finishDecoding() + + return settings + } + + func saveProfileSettings(_ settings: ProfileSettings) { + let data = NSMutableData() + let archiver = NSKeyedArchiver(forWritingWith: data) + + settings.encode(with: archiver) + archiver.finishEncoding() + + self.genericSettingsData = data.copy() as! Data + } + + func notificationBlock(for object: T, + _ block: @escaping (ResultsChange) -> Void) -> RLMNotificationToken { + let predicate = NSPredicate(format: "uniqueIdentifier == %@", object.uniqueIdentifier) + let results: Results + + switch object { + case is OCTFriend: + results = Results(results: objects(for: .friend, predicate: predicate)!) + case is OCTFriendRequest: + results = Results(results: objects(for: .friendRequest, predicate: predicate)!) + case is OCTChat: + results = Results(results: objects(for: .chat, predicate: predicate)!) + case is OCTCall: + results = Results(results: objects(for: .call, predicate: predicate)!) + case is OCTMessageAbstract: + results = Results(results: objects(for: .messageAbstract, predicate: predicate)!) + default: + fatalError("OCT type not handled properly") + } + + return results.addNotificationBlock(block) + } +} diff --git a/Antidote/OCTSubmanagerObjectsMock.swift b/Antidote/OCTSubmanagerObjectsMock.swift new file mode 100644 index 0000000..72039a5 --- /dev/null +++ b/Antidote/OCTSubmanagerObjectsMock.swift @@ -0,0 +1,58 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class OCTSubmanagerObjectsMock: NSObject, OCTSubmanagerObjects { + var genericSettingsData: Data? = nil + let realm: RLMRealm + + init(realm: RLMRealm) { + self.realm = realm + + super.init() + } + + func objects(for type: OCTFetchRequestType, predicate: NSPredicate!) -> RLMResults! { + switch type { + case .friend: + return OCTFriend.objects(in: realm, with: predicate) + case .friendRequest: + return OCTFriendRequest.objects(in: realm, with: predicate) + case .chat: + return OCTChat.objects(in: realm, with: predicate) + case .call: + return OCTCall.objects(in: realm, with: predicate) + case .messageAbstract: + return OCTMessageAbstract.objects(in: realm, with: predicate) + } + } + + func object(withUniqueIdentifier uniqueIdentifier: String!, for type: OCTFetchRequestType) -> OCTObject! { + switch type { + case .friend: + return OCTFriend.object(in: realm, forPrimaryKey: uniqueIdentifier) + case .friendRequest: + return OCTFriendRequest.object(in: realm, forPrimaryKey: uniqueIdentifier) + case .chat: + return OCTChat.object(in: realm, forPrimaryKey: uniqueIdentifier) + case .call: + return OCTCall.object(in: realm, forPrimaryKey: uniqueIdentifier) + case .messageAbstract: + return OCTMessageAbstract.object(in: realm, forPrimaryKey: uniqueIdentifier) + } + } + + func change(_ friend: OCTFriend!, nickname: String!) { + // nop + } + + func change(_ chat: OCTChat!, enteredText: String!) { + // nop + } + + func change(_ chat: OCTChat!, lastReadDateInterval: TimeInterval) { + // nop + } +} diff --git a/Antidote/OCTSubmanagerUserMock.swift b/Antidote/OCTSubmanagerUserMock.swift new file mode 100644 index 0000000..5fd26de --- /dev/null +++ b/Antidote/OCTSubmanagerUserMock.swift @@ -0,0 +1,48 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class OCTSubmanagerUserMock: NSObject, OCTSubmanagerUser { + weak var delegate: OCTSubmanagerUserDelegate? = nil + var connectionStatus: OCTToxConnectionStatus = .TCP + var userAddress: String = "123" + var publicKey: String = "123" + var nospam: OCTToxNoSpam = 123 + var userStatus: OCTToxUserStatus = .none + var capabilities: OCTToxCapabilities = 0 + + override init() { + super.init() + + let delayTime = DispatchTime.now() + Double(Int64(2.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) + DispatchQueue.main.asyncAfter(deadline: delayTime) { [weak self] in + self?.delegate?.submanagerUser(self!, connectionStatusUpdate: self!.connectionStatus) + } + } + + func setUserName(_ name: String?) throws { + // nop + } + + func userName() -> String? { + return nil + } + + func setUserStatusMessage(_ statusMessage: String?) throws { + // nop + } + + func userStatusMessage() -> String? { + return nil + } + + func setUserAvatar(_ avatar: Data?) throws { + // nop + } + + func userAvatar() -> Data? { + return nil + } +} diff --git a/Antidote/PinAuthorizationCoordinator.swift b/Antidote/PinAuthorizationCoordinator.swift new file mode 100644 index 0000000..4a2239d --- /dev/null +++ b/Antidote/PinAuthorizationCoordinator.swift @@ -0,0 +1,241 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import AudioToolbox +import LocalAuthentication + +fileprivate struct Constants { + static let pinAttemptsNumber = 10 +} + +protocol PinAuthorizationCoordinatorDelegate: class { + func pinAuthorizationCoordinatorDidLogout(_ coordinator: PinAuthorizationCoordinator) +} + +class PinAuthorizationCoordinator: NSObject { + weak var delegate: PinAuthorizationCoordinatorDelegate? + + fileprivate enum State { + case unlocked + case locked(lockTime: CFTimeInterval) + case validatingPin + } + + fileprivate let theme: Theme + fileprivate let window: UIWindow + + fileprivate weak var submanagerObjects: OCTSubmanagerObjects! + + fileprivate var state: State + + var preventFromLocking: Bool = false { + didSet { + if !preventFromLocking && UIApplication.shared.applicationState != .active { + // In case if locking option change in background we want to lock app when user comes back. + lockIfNeeded(CACurrentMediaTime()) + } + } + } + + init(theme: Theme, submanagerObjects: OCTSubmanagerObjects, lockOnStartup: Bool) { + self.theme = theme + self.window = UIWindow(frame: UIScreen.main.bounds) + self.submanagerObjects = submanagerObjects + self.state = .unlocked + + super.init() + + // Showing window on top of all other windows. + window.windowLevel = UIWindowLevelStatusBar + 1000 + + if lockOnStartup { + lockIfNeeded(0) + } + + NotificationCenter.default.addObserver(self, + selector: #selector(PinAuthorizationCoordinator.appWillResignActiveNotification), + name: NSNotification.Name.UIApplicationWillResignActive, + object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(PinAuthorizationCoordinator.appDidBecomeActiveNotification), + name: NSNotification.Name.UIApplicationDidBecomeActive, + object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + @objc func appWillResignActiveNotification() { + lockIfNeeded(CACurrentMediaTime()) + } + + @objc func appDidBecomeActiveNotification() { + switch state { + case .unlocked: + // unlocked, nothing to do here + break + case .locked(let lockTime): + isPinDateExpired(lockTime) ? challengeUserToAuthorize(lockTime) : unlock() + case .validatingPin: + // checking pin, no action required + break + } + } +} + +extension PinAuthorizationCoordinator: CoordinatorProtocol { + func startWithOptions(_ options: CoordinatorOptions?) { + switch state { + case .locked(let lockTime): + challengeUserToAuthorize(lockTime) + case .unlocked: + // ignore + break + case .validatingPin: + // ignore + break + } + } +} + +extension PinAuthorizationCoordinator: EnterPinControllerDelegate { + func enterPinController(_ controller: EnterPinController, successWithPin pin: String) { + unlock() + } + + func enterPinControllerFailure(_ controller: EnterPinController) { + let keychainManager = KeychainManager() + + var failedAttempts = keychainManager.failedPinAttemptsNumber ?? 0 + failedAttempts += 1 + + keychainManager.failedPinAttemptsNumber = failedAttempts + + guard failedAttempts < Constants.pinAttemptsNumber else { + keychainManager.failedPinAttemptsNumber = nil + handleErrorWithType(.pinLogOut) + + delegate?.pinAuthorizationCoordinatorDidLogout(self) + return + } + + controller.resetEnteredPin() + controller.topText = String(localized: "pin_incorrect") + controller.descriptionText = String(localized: "pin_failed_attempts", "\(failedAttempts)") + AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate)) + } +} + +private extension PinAuthorizationCoordinator { + func lockIfNeeded(_ lockTime: CFTimeInterval) { + guard submanagerObjects.getProfileSettings().unlockPinCode != nil else { + return + } + + if preventFromLocking { + return + } + + for window in UIApplication.shared.windows { + window.endEditing(true) + } + + let storyboard = UIStoryboard(name: "LaunchPlaceholderBoard", bundle: Bundle.main) + window.rootViewController = storyboard.instantiateViewController(withIdentifier: "LaunchPlaceholderController") + window.isHidden = false + + switch state { + case .unlocked: + state = .locked(lockTime: lockTime) + case .locked: + // In case of Locked state don't want to update lockTime. + break + case .validatingPin: + // In case of ValidatingPin state we also don't want to do anything. + break + } + } + + func unlock() { + KeychainManager().failedPinAttemptsNumber = nil + + state = .unlocked + window.isHidden = true + } + + func challengeUserToAuthorize(_ lockTime: CFTimeInterval) { + if window.rootViewController is EnterPinController { + // already showing pin controller + return + } + + if shouldUseTouchID() { + state = .validatingPin + + LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, + localizedReason: String(localized: "pin_touch_id_description"), + reply: { [weak self] success, error in + DispatchQueue.main.async { + self?.state = .locked(lockTime: lockTime) + + success ? self?.unlock() : self?.showValidatePinController() + } + }) + } + else { + showValidatePinController() + } + } + + func showValidatePinController() { + let settings = submanagerObjects.getProfileSettings() + guard let pin = settings.unlockPinCode else { + fatalError("pin shouldn't be nil") + } + + let failedAttempts = KeychainManager().failedPinAttemptsNumber ?? 0 + + let controller = EnterPinController(theme: theme, state: .validatePin(validPin: pin)) + controller.topText = String(localized: "pin_enter_to_unlock") + controller.descriptionText = + failedAttempts > 0 ? + String(localized: "pin_failed_attempts", "\(failedAttempts)") : + nil + controller.delegate = self + window.rootViewController = controller + } + + func isPinDateExpired(_ lockTime: CFTimeInterval) -> Bool { + let settings = submanagerObjects.getProfileSettings() + let delta = CACurrentMediaTime() - lockTime + + switch settings.lockTimeout { + case .Immediately: + return true + case .Seconds30: + return delta > 30 + case .Minute1: + return delta > 60 + case .Minute2: + return delta > (60 * 2) + case .Minute5: + return delta > (60 * 5) + } + } + + func shouldUseTouchID() -> Bool { + guard submanagerObjects.getProfileSettings().useTouchID else { + return false + } + + guard LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) else { + return false + } + + return true + } +} diff --git a/Antidote/PinInputView.swift b/Antidote/PinInputView.swift new file mode 100644 index 0000000..0a2c772 --- /dev/null +++ b/Antidote/PinInputView.swift @@ -0,0 +1,353 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +private struct Constants { + static let DotsSize: CGFloat = 16 + static let ButtonSize: CGFloat = 75 + static let VerticalOffsetSmall: CGFloat = 12 + static let VerticalOffsetBig: CGFloat = 17 + static let HorizontalOffset: CGFloat = 17 +} + +protocol PinInputViewDelegate: class { + func pinInputView(_ view: PinInputView, numericButtonPressed i: Int) + func pinInputViewDeleteButtonPressed(_ view: PinInputView) +} + +class PinInputView: UIView { + weak var delegate: PinInputViewDelegate? + + /// Entered numbers. Must be in 0...pinLength range. + var enteredNumbersCount: Int = 0 { + didSet { + enteredNumbersCount = max(enteredNumbersCount, 0) + enteredNumbersCount = min(enteredNumbersCount, pinLength) + + updateDotsImages() + } + } + + var topText: String { + get { + return topLabel.text! + } + set { + topLabel.text = newValue + } + } + + var descriptionText: String? { + get { + return descriptionLabel.text + } + set { + descriptionLabel.text = newValue + } + } + + fileprivate let pinLength: Int + + fileprivate let topColorComponents: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) + fileprivate let bottomColorComponents: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) + + fileprivate var topLabel: UILabel! + fileprivate var descriptionLabel: UILabel! + fileprivate var dotsContainer: UIView! + fileprivate var dotsImageViews = [UIImageView]() + fileprivate var numericButtons = [UIButton]() + fileprivate var deleteButton: UIButton! + + init(pinLength: Int, topColor: UIColor, bottomColor: UIColor) { + self.pinLength = pinLength + self.topColorComponents = topColor.components() + self.bottomColorComponents = bottomColor.components() + + super.init(frame: CGRect.zero) + + createLabels() + createDotsImageViews() + createNumericButtons() + createDeleteButton() + + installConstraints() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /** + Applies gradient colors to all subviews. + Call this method after adding PinInputView to superview. + */ + func applyColors() { + guard superview != nil else { + fatalError("superview shouldn't be nil") + } + + layoutIfNeeded() + updateButtonColors() + updateOtherColors() + + updateDotsImages() + } +} + +extension PinInputView { + @objc func numericButtonPressed(_ button: UIButton) { + guard let i = numericButtons.index(of: button) else { + return + } + + delegate?.pinInputView(self, numericButtonPressed: i) + } + + @objc func deleteButtonPressed(_ button: UIButton) { + delegate?.pinInputViewDeleteButtonPressed(self) + } +} + +private extension PinInputView { + func createLabels() { + topLabel = UILabel() + topLabel.font = UIFont.antidoteFontWithSize(18.0, weight: .medium) + addSubview(topLabel) + + descriptionLabel = UILabel() + descriptionLabel.font = UIFont.antidoteFontWithSize(16.0, weight: .light) + addSubview(descriptionLabel) + } + + func createDotsImageViews() { + for _ in 0.. UIColor { + guard self.frame.size.height > 0 else { + log("PinInputView should not be nil") + return .clear + } + + guard y >= 0 && y <= self.frame.size.height else { + log("Point y \(y) is outside of view") + return .clear + } + + let percent = y / self.frame.size.height + + let red = topColorComponents.red + percent * (bottomColorComponents.red - topColorComponents.red) + let green = topColorComponents.green + percent * (bottomColorComponents.green - topColorComponents.green) + let blue = topColorComponents.blue + percent * (bottomColorComponents.blue - topColorComponents.blue) + let alpha = topColorComponents.alpha + percent * (bottomColorComponents.alpha - topColorComponents.alpha) + + return UIColor(red: red, green: green, blue: blue, alpha: alpha) + } + + func gradientCircleImage(topColor: UIColor, bottomColor: UIColor, size: CGFloat, filled: Bool) -> UIImage { + let radius = size * UIScreen.main.scale / 2 + + let gradientLayer = CAGradientLayer() + gradientLayer.frame.size.width = 2 * radius + gradientLayer.frame.size.height = 2 * radius + gradientLayer.colors = [topColor.cgColor, bottomColor.cgColor] + gradientLayer.masksToBounds = true + gradientLayer.cornerRadius = radius + + if !filled { + // apply mask + let lineWidth: CGFloat = 2.0 + + let path = UIBezierPath() + path.addArc(withCenter: CGPoint(x: radius, y: radius), + radius: radius - lineWidth, + startAngle: 0.0, + endAngle: CGFloat(2 * Double.pi), + clockwise: true) + + let mask = CAShapeLayer() + mask.frame = gradientLayer.frame + mask.path = path.cgPath + mask.lineWidth = lineWidth + mask.fillColor = UIColor.clear.cgColor + mask.strokeColor = UIColor.black.cgColor + + gradientLayer.mask = mask + } + + UIGraphicsBeginImageContext(gradientLayer.bounds.size) + gradientLayer.render(in: UIGraphicsGetCurrentContext()!) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return image! + } +} diff --git a/Antidote/PortraitNavigationController.swift b/Antidote/PortraitNavigationController.swift new file mode 100644 index 0000000..0552bd2 --- /dev/null +++ b/Antidote/PortraitNavigationController.swift @@ -0,0 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +class PortraitNavigationController: UINavigationController { + override var shouldAutorotate : Bool { + return false + } + + override var supportedInterfaceOrientations : UIInterfaceOrientationMask { + return UIInterfaceOrientationMask.portrait + } +} diff --git a/Antidote/PrimaryIpadController.swift b/Antidote/PrimaryIpadController.swift new file mode 100644 index 0000000..0aaa1a7 --- /dev/null +++ b/Antidote/PrimaryIpadController.swift @@ -0,0 +1,160 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +protocol PrimaryIpadControllerDelegate: class { + func primaryIpadController(_ controller: PrimaryIpadController, didSelectChat chat: OCTChat) + func primaryIpadControllerShowFriends(_ controller: PrimaryIpadController) + func primaryIpadControllerShowSettings(_ controller: PrimaryIpadController) + func primaryIpadControllerShowProfile(_ controller: PrimaryIpadController) +} + +/** + Controller for the iPad that is displayed as primary split controller + */ +class PrimaryIpadController: UIViewController { + weak var delegate: PrimaryIpadControllerDelegate? + + var userStatus: UserStatus = .offline { + didSet { + navigationView.avatarView.userStatusView.userStatus = userStatus + } + } + + var userAvatar: UIImage? { + didSet { + if let image = userAvatar { + navigationView.avatarView.imageView.image = image + } + else { + navigationView.avatarView.imageView.image = UIImage.templateNamed("tab-bar-profile") + } + } + } + + var userName: String? { + didSet { + navigationView.label.text = userName + } + } + + var friendsBadgeText: String? { + didSet { + friendsButton.badgeText = friendsBadgeText + } + } + + fileprivate let theme: Theme + fileprivate weak var submanagerChats: OCTSubmanagerChats! + fileprivate weak var submanagerObjects: OCTSubmanagerObjects! + + fileprivate var navigationView: iPadNavigationView! + fileprivate var friendsButton: iPadFriendsButton! + + fileprivate var tableManager: ChatListTableManager! + + init(theme: Theme, submanagerChats: OCTSubmanagerChats, submanagerObjects: OCTSubmanagerObjects) { + self.theme = theme + self.submanagerChats = submanagerChats + self.submanagerObjects = submanagerObjects + + super.init(nibName: nil, bundle: nil) + + addNavigationButtons() + + edgesForExtendedLayout = UIRectEdge() + friendsButton = iPadFriendsButton(theme: theme) + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + setupButtons() + createTableView() + installConstraints() + } +} + +// MARK: Actions +extension PrimaryIpadController { + func friendsButtonPressed() { + delegate?.primaryIpadControllerShowFriends(self) + } + + @objc func settingsButtonPressed() { + delegate?.primaryIpadControllerShowSettings(self) + } + + func profileButtonPressed() { + delegate?.primaryIpadControllerShowProfile(self) + } +} + +extension PrimaryIpadController: ChatListTableManagerDelegate { + func chatListTableManager(_ manager: ChatListTableManager, didSelectChat chat: OCTChat) { + delegate?.primaryIpadController(self, didSelectChat: chat) + } + + func chatListTableManager(_ manager: ChatListTableManager, presentAlertController controller: UIAlertController) { + present(controller, animated: true, completion: nil) + } + + func chatListTableManagerWasUpdated(_ manager: ChatListTableManager) { + // nope + } +} + +private extension PrimaryIpadController { + func addNavigationButtons() { + // none for now + navigationView = iPadNavigationView(theme: theme) + navigationView.didTapHandler = profileButtonPressed + navigationItem.leftBarButtonItem = UIBarButtonItem(customView: navigationView) + + navigationItem.rightBarButtonItem = UIBarButtonItem( + image: UIImage(named: "tab-bar-settings"), + style: .plain, + target: self, + action: #selector(PrimaryIpadController.settingsButtonPressed)) + } + + func setupButtons() { + friendsButton.didTapHandler = friendsButtonPressed + view.addSubview(friendsButton) + } + + func createTableView() { + let tableView = UITableView() + tableView.estimatedRowHeight = 44.0 + tableView.backgroundColor = theme.colorForType(.NormalBackground) + tableView.sectionIndexColor = theme.colorForType(.LinkText) + // removing separators on empty lines + tableView.tableFooterView = UIView() + + view.addSubview(tableView) + + tableView.register(ChatListCell.self, forCellReuseIdentifier: ChatListCell.staticReuseIdentifier) + + tableManager = ChatListTableManager(theme: theme, tableView: tableView, submanagerChats: submanagerChats, submanagerObjects: submanagerObjects) + tableManager.delegate = self + } + + func installConstraints() { + friendsButton.snp.makeConstraints { + $0.top.equalTo(view) + $0.leading.trailing.equalTo(view) + $0.height.equalTo(60.0) + } + + tableManager.tableView.snp.makeConstraints { + $0.top.equalTo(friendsButton.snp.bottom) + $0.leading.trailing.bottom.equalTo(view) + } + } +} diff --git a/Antidote/ProfileDetailsController.swift b/Antidote/ProfileDetailsController.swift new file mode 100644 index 0000000..6b03507 --- /dev/null +++ b/Antidote/ProfileDetailsController.swift @@ -0,0 +1,200 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import LocalAuthentication + +protocol ProfileDetailsControllerDelegate: class { + func profileDetailsControllerSetPin(_ controller: ProfileDetailsController) + func profileDetailsControllerChangeLockTimeout(_ controller: ProfileDetailsController) + func profileDetailsControllerChangePassword(_ controller: ProfileDetailsController) + func profileDetailsControllerDeleteProfile(_ controller: ProfileDetailsController) +} + +class ProfileDetailsController: StaticTableController { + weak var delegate: ProfileDetailsControllerDelegate? + + fileprivate weak var toxManager: OCTManager! + + fileprivate let pinEnabledModel = StaticTableSwitchCellModel() + fileprivate let lockTimeoutModel = StaticTableInfoCellModel() + fileprivate let touchIdEnabledModel = StaticTableSwitchCellModel() + + fileprivate let changePasswordModel = StaticTableButtonCellModel() + fileprivate let exportProfileModel = StaticTableButtonCellModel() + fileprivate let deleteProfileModel = StaticTableButtonCellModel() + + fileprivate var documentInteractionController: UIDocumentInteractionController? + + init(theme: Theme, toxManager: OCTManager) { + self.toxManager = toxManager + + var model = [[StaticTableBaseCellModel]]() + var footers = [String?]() + + model.append([pinEnabledModel, lockTimeoutModel]) + footers.append(String(localized: "pin_description")) + + if LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) { + model.append([touchIdEnabledModel]) + footers.append(String(localized: "pin_touch_id_description")) + } + + model.append([changePasswordModel]) + footers.append(nil) + + model.append([exportProfileModel, deleteProfileModel]) + footers.append(nil) + + super.init(theme: theme, style: .grouped, model: model, footers: footers) + + updateModel() + + title = String(localized: "profile_details") + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + updateModel() + reloadTableView() + } +} + +extension ProfileDetailsController: UIDocumentInteractionControllerDelegate { + func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController { + return self + } + + func documentInteractionControllerViewForPreview(_ controller: UIDocumentInteractionController) -> UIView? { + return view + } + + func documentInteractionControllerRectForPreview(_ controller: UIDocumentInteractionController) -> CGRect { + return view.frame + } +} + +private extension ProfileDetailsController { + func updateModel() { + let settings = toxManager.objects.getProfileSettings() + let isPinEnabled = settings.unlockPinCode != nil + + pinEnabledModel.title = String(localized: "pin_enabled") + pinEnabledModel.on = isPinEnabled + pinEnabledModel.valueChangedHandler = pinEnabledValueChanged + + lockTimeoutModel.title = String(localized: "pin_lock_timeout") + lockTimeoutModel.showArrow = true + lockTimeoutModel.didSelectHandler = changeLockTimeout + + switch settings.lockTimeout { + case .Immediately: + lockTimeoutModel.value = String(localized: "pin_lock_immediately") + case .Seconds30: + lockTimeoutModel.value = String(localized: "pin_lock_30_seconds") + case .Minute1: + lockTimeoutModel.value = String(localized: "pin_lock_1_minute") + case .Minute2: + lockTimeoutModel.value = String(localized: "pin_lock_2_minutes") + case .Minute5: + lockTimeoutModel.value = String(localized: "pin_lock_5_minutes") + } + + touchIdEnabledModel.enabled = isPinEnabled + touchIdEnabledModel.title = String(localized: "pin_touch_id_enabled") + touchIdEnabledModel.on = settings.useTouchID + touchIdEnabledModel.valueChangedHandler = touchIdEnabledValueChanged + + changePasswordModel.title = String(localized: "change_password") + changePasswordModel.didSelectHandler = changePassword + + exportProfileModel.title = String(localized: "export_profile") + exportProfileModel.didSelectHandler = exportProfile + + deleteProfileModel.title = String(localized: "delete_profile") + deleteProfileModel.didSelectHandler = deleteProfile + } + + func pinEnabledValueChanged(_ on: Bool) { + if on { + delegate?.profileDetailsControllerSetPin(self) + } + else { + let settings = toxManager.objects.getProfileSettings() + settings.unlockPinCode = nil + toxManager.objects.saveProfileSettings(settings) + } + + updateModel() + reloadTableView() + } + + func changeLockTimeout(_: StaticTableBaseCell) { + delegate?.profileDetailsControllerChangeLockTimeout(self) + } + + func touchIdEnabledValueChanged(_ on: Bool) { + let settings = toxManager.objects.getProfileSettings() + settings.useTouchID = on + toxManager.objects.saveProfileSettings(settings) + } + + func changePassword(_: StaticTableBaseCell) { + delegate?.profileDetailsControllerChangePassword(self) + } + + func exportProfile(_: StaticTableBaseCell) { + do { + let path = try toxManager.exportToxSaveFile() + + let name = UserDefaultsManager().lastActiveProfile ?? "profile" + + documentInteractionController = UIDocumentInteractionController(url: URL(fileURLWithPath: path)) + documentInteractionController!.delegate = self + documentInteractionController!.name = "\(name).tox" + documentInteractionController!.presentOptionsMenu(from: view.frame, in:view, animated: true) + } + catch let error as NSError { + handleErrorWithType(.exportProfile, error: error) + } + } + + func deleteProfile(_ cell: StaticTableBaseCell) { + let title1 = String(localized: "delete_profile_confirmation_title_1") + let title2 = String(localized: "delete_profile_confirmation_title_2") + let message = String(localized: "delete_profile_confirmation_message") + let yes = String(localized: "alert_delete") + let cancel = String(localized: "alert_cancel") + + let alert1 = UIAlertController(title: title1, message: message, preferredStyle: .actionSheet) + alert1.popoverPresentationController?.sourceView = cell + alert1.popoverPresentationController?.sourceRect = CGRect(x: cell.frame.size.width / 2, y: cell.frame.size.height / 2, width: 1.0, height: 1.0) + + alert1.addAction(UIAlertAction(title: yes, style: .destructive) { [unowned self] _ -> Void in + let alert2 = UIAlertController(title: title2, message: nil, preferredStyle: .actionSheet) + alert2.popoverPresentationController?.sourceView = cell + alert2.popoverPresentationController?.sourceRect = CGRect(x: cell.frame.size.width / 2, y: cell.frame.size.height / 2, width: 1.0, height: 1.0) + + alert2.addAction(UIAlertAction(title: yes, style: .destructive) { [unowned self] _ -> Void in + self.reallyDeleteProfile() + }) + alert2.addAction(UIAlertAction(title: cancel, style: .cancel, handler: nil)) + + self.present(alert2, animated: true, completion: nil) + }) + + alert1.addAction(UIAlertAction(title: cancel, style: .cancel, handler: nil)) + + present(alert1, animated: true, completion: nil) + } + + func reallyDeleteProfile() { + delegate?.profileDetailsControllerDeleteProfile(self) + } +} diff --git a/Antidote/ProfileMainController.swift b/Antidote/ProfileMainController.swift new file mode 100644 index 0000000..44e2377 --- /dev/null +++ b/Antidote/ProfileMainController.swift @@ -0,0 +1,322 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import Firebase + +protocol ProfileMainControllerDelegate: class { + func profileMainControllerLogout(_ controller: ProfileMainController) + func profileMainControllerChangeUserName(_ controller: ProfileMainController) + func profileMainControllerChangeUserStatus(_ controller: ProfileMainController) + func profileMainControllerChangeStatusMessage(_ controller: ProfileMainController) + func profileMainController(_ controller: ProfileMainController, showQRCodeWithText text: String) + func profileMainControllerShowProfileDetails(_ controller: ProfileMainController) + func profileMainControllerDidChangeAvatar(_ controller: ProfileMainController) +} + +class ProfileMainController: StaticTableController { + weak var delegate: ProfileMainControllerDelegate? + + fileprivate weak var submanagerUser: OCTSubmanagerUser! + fileprivate let avatarManager: AvatarManager + + fileprivate let avatarModel = StaticTableAvatarCellModel() + fileprivate let userNameModel = StaticTableDefaultCellModel() + fileprivate let statusMessageModel = StaticTableDefaultCellModel() + // fileprivate let userStatusModel = StaticTableDefaultCellModel() + fileprivate let toxIdModel = StaticTableDefaultCellModel() + fileprivate let pushurlModel = StaticTableDefaultCellModel() + fileprivate let capabilitiesModel = StaticTableDefaultCellModel() + fileprivate let profileDetailsModel = StaticTableDefaultCellModel() + fileprivate let logoutModel = StaticTableButtonCellModel() + + init(theme: Theme, submanagerUser: OCTSubmanagerUser) { + self.submanagerUser = submanagerUser + + avatarManager = AvatarManager(theme: theme) + + super.init(theme: theme, style: .plain, model: [ + [ + avatarModel, + ], + [ + userNameModel, + statusMessageModel, + ], + //[ + // userStatusModel, + //], + [ + toxIdModel, + ], + [ + pushurlModel, + ], + [ + capabilitiesModel, + ], + [ + profileDetailsModel, + ], + [ + logoutModel, + ], + ]) + + updateModels() + + title = String(localized: "profile_title") + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + updateModels() + reloadTableView() + } +} + +extension ProfileMainController: UIImagePickerControllerDelegate { + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { + dismiss(animated: true, completion: nil) + + guard var image = info[UIImagePickerControllerOriginalImage] as? UIImage else { + return + } + + if image.size.width != image.size.height { + let side = min(image.size.width, image.size.height) + let x = (image.size.width - side) / 2 + let y = (image.size.height - side) / 2 + let rect = CGRect(x: x, y: y, width: side, height: side) + + image = image.cropWithRect(rect) + } + + let data: Data + + do { + data = try pngDataFromImage(image) + } + catch { + handleErrorWithType(.convertImageToPNG, error: nil) + return + } + + do { + try submanagerUser.setUserAvatar(data) + updateModels() + reloadTableView() + + delegate?.profileMainControllerDidChangeAvatar(self) + } + catch let error as NSError { + handleErrorWithType(.changeAvatar, error: error) + } + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + dismiss(animated: true, completion: nil) + } +} + +extension ProfileMainController: UINavigationControllerDelegate {} + +private extension ProfileMainController { + struct PNGFromDataError: Error {} + + func updateModels() { + if let avatarData = submanagerUser.userAvatar() { + avatarModel.avatar = UIImage(data: avatarData) + } + else { + avatarModel.avatar = avatarManager.avatarFromString( + submanagerUser.userName() ?? "?", + diameter: StaticTableAvatarCellModel.Constants.AvatarImageSize) + } + avatarModel.didTapOnAvatar = performAvatarAction + + userNameModel.title = String(localized: "name") + userNameModel.value = submanagerUser.userName() + userNameModel.rightImageType = .arrow + userNameModel.didSelectHandler = changeUserName + + // Hardcoding any connected status to show only online/away/busy statuses here. + let userStatus = UserStatus(connectionStatus: OCTToxConnectionStatus.TCP, userStatus: submanagerUser.userStatus) + + // userStatusModel.userStatus = userStatus + // userStatusModel.value = userStatus.toString() + // userStatusModel.rightImageType = .arrow + // userStatusModel.didSelectHandler = changeUserStatus + + statusMessageModel.title = String(localized: "status_message") + statusMessageModel.value = submanagerUser.userStatusMessage() + statusMessageModel.rightImageType = .arrow + statusMessageModel.didSelectHandler = changeStatusMessage + + toxIdModel.title = String(localized: "my_tox_id") + toxIdModel.value = submanagerUser.userAddress + toxIdModel.rightButton = String(localized: "show_qr") + toxIdModel.rightButtonHandler = showToxIdQR + toxIdModel.userInteractionEnabled = false + toxIdModel.canCopyValue = true + // for debugging print own ToxID ---------------- + // print("TOXID: \(submanagerUser.userAddress)") + // for debugging print own ToxID ---------------- + + pushurlModel.title = "Push URL" + let pushtoken = Messaging.messaging().fcmToken ?? "" + if (pushtoken.count > 0) { + pushurlModel.value = "https://tox.zoff.xyz/toxfcm/fcm.php?id=" + pushtoken + "&type=1" + } else { + pushurlModel.value = "" + } + pushurlModel.userInteractionEnabled = false + + capabilitiesModel.title = "Tox Capabilities" + capabilitiesModel.value = capabilitiesToString(submanagerUser.capabilities as NSNumber) + capabilitiesModel.userInteractionEnabled = false + + profileDetailsModel.value = String(localized: "profile_details") + profileDetailsModel.didSelectHandler = showProfileDetails + profileDetailsModel.rightImageType = .arrow + + logoutModel.title = String(localized: "logout_button") + logoutModel.didSelectHandler = logout + } + + func capabilitiesToString(_ cap: NSNumber) -> String { + var ret: String = "BASIC" + if ((UInt(cap) & 1) > 0) { + ret = ret + " CAPABILITIES" + } + if ((UInt(cap) & 2) > 0) { + ret = ret + " MSGV2" + } + if ((UInt(cap) & 4) > 0) { + ret = ret + " H264" + } + if ((UInt(cap) & 8) > 0) { + ret = ret + " MSGV3" + } + if ((UInt(cap) & 16) > 0) { + ret = ret + " FTV2" + } + return ret; + } + + func logout(_: StaticTableBaseCell) { + delegate?.profileMainControllerLogout(self) + } + + func performAvatarAction(_ cell: StaticTableAvatarCell) { + let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + alert.popoverPresentationController?.sourceView = cell + alert.popoverPresentationController?.sourceRect = CGRect(x: cell.frame.size.width / 2, y: cell.frame.size.height / 2, width: 1.0, height: 1.0) + + if UIImagePickerController.isSourceTypeAvailable(.camera) { + alert.addAction(UIAlertAction(title: String(localized: "photo_from_camera"), style: .default) { [unowned self] _ -> Void in + let controller = UIImagePickerController() + controller.sourceType = .camera + controller.delegate = self + + if (UIImagePickerController.isCameraDeviceAvailable(.front)) { + controller.cameraDevice = .front + } + + self.present(controller, animated: true, completion: nil) + }) + } + + if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) { + alert.addAction(UIAlertAction(title: String(localized: "photo_from_photo_library"), style: .default) { [unowned self] _ -> Void in + let controller = UIImagePickerController() + controller.sourceType = .photoLibrary + controller.delegate = self + self.present(controller, animated: true, completion: nil) + }) + } + + if submanagerUser.userAvatar() != nil { + alert.addAction(UIAlertAction(title: String(localized: "alert_delete"), style: .destructive) { [unowned self] _ -> Void in + self.removeAvatar() + }) + } + + alert.addAction(UIAlertAction(title: String(localized: "alert_cancel"), style: .cancel, handler: nil)) + + present(alert, animated: true, completion: nil) + } + + func removeAvatar() { + do { + try submanagerUser.setUserAvatar(nil) + updateModels() + reloadTableView() + + delegate?.profileMainControllerDidChangeAvatar(self) + } + catch let error as NSError { + handleErrorWithType(.changeAvatar, error: error) + } + } + + func pngDataFromImage(_ image: UIImage) throws -> Data { + var imageSize = image.size + + // Maximum png size will be (4 * width * height) + // * 1.5 to get as big avatar size as possible + while OCTToxFileSize(4 * imageSize.width * imageSize.height) > OCTToxFileSize(1.5 * Double(kOCTManagerMaxAvatarSize)) { + imageSize.width *= 0.9 + imageSize.height *= 0.9 + } + + imageSize.width = ceil(imageSize.width) + imageSize.height = ceil(imageSize.height) + + var data: Data + var tempImage = image + + repeat { + UIGraphicsBeginImageContext(imageSize) + tempImage.draw(in: CGRect(origin: CGPoint.zero, size: imageSize)) + tempImage = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + + guard let theData = UIImagePNGRepresentation(tempImage) else { + throw PNGFromDataError() + } + data = theData + + imageSize.width *= 0.9 + imageSize.height *= 0.9 + } while (OCTToxFileSize(data.count) > kOCTManagerMaxAvatarSize) + + return data + } + + func changeUserName(_: StaticTableBaseCell) { + delegate?.profileMainControllerChangeUserName(self) + } + + func changeUserStatus(_: StaticTableBaseCell) { + delegate?.profileMainControllerChangeUserStatus(self) + } + + func changeStatusMessage(_: StaticTableBaseCell) { + delegate?.profileMainControllerChangeStatusMessage(self) + } + + func showToxIdQR() { + delegate?.profileMainController(self, showQRCodeWithText: submanagerUser.userAddress) + } + + func showProfileDetails(_: StaticTableBaseCell) { + delegate?.profileMainControllerShowProfileDetails(self) + } +} diff --git a/Antidote/ProfileManager.swift b/Antidote/ProfileManager.swift new file mode 100644 index 0000000..450b7ed --- /dev/null +++ b/Antidote/ProfileManager.swift @@ -0,0 +1,85 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +private struct Constants { + static let SaveDirectoryPath = "saves" + + // TODO get this constant from objcTox OCTDefaultFileStorage + static let ToxFileName = "save.tox" +} + +class ProfileManager { + fileprivate(set) var allProfileNames: [String] + + init() { + allProfileNames = [] + + reloadProfileNames() + } + + func createProfileWithName(_ name: String, copyFromURL: URL? = nil) throws { + let path = pathFromName(name) + let fileManager = FileManager.default + + try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) + + if let url = copyFromURL { + let saveURL = URL(fileURLWithPath: path).appendingPathComponent(Constants.ToxFileName) + try fileManager.moveItem(at: url, to: saveURL) + } + + reloadProfileNames() + } + + func deleteProfileWithName(_ name: String) throws { + let path = pathFromName(name) + + try FileManager.default.removeItem(atPath: path) + + reloadProfileNames() + } + + func renameProfileWithName(_ fromName: String, toName: String) throws { + let fromPath = pathFromName(fromName) + let toPath = pathFromName(toName) + + try FileManager.default.moveItem(atPath: fromPath, toPath: toPath) + + reloadProfileNames() + } + + func pathForProfileWithName(_ name: String) -> String { + return pathFromName(name) + } +} + +private extension ProfileManager { + func saveDirectoryPath() -> String { + let path: NSString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! as NSString + return path.appendingPathComponent(Constants.SaveDirectoryPath) + } + + func pathFromName(_ name: String) -> String { + let directoryPath: NSString = saveDirectoryPath() as NSString + return directoryPath.appendingPathComponent(name) + } + + func reloadProfileNames() { + let fileManager = FileManager.default + let savePath = saveDirectoryPath() + + let contents = try? fileManager.contentsOfDirectory(atPath: savePath) + + allProfileNames = contents?.filter { + let path = (savePath as NSString).appendingPathComponent($0) + + var isDirectory: ObjCBool = false + fileManager.fileExists(atPath: path, isDirectory:&isDirectory) + + return isDirectory.boolValue + } ?? [String]() + } +} diff --git a/Antidote/ProfileSettings.swift b/Antidote/ProfileSettings.swift new file mode 100644 index 0000000..5390198 --- /dev/null +++ b/Antidote/ProfileSettings.swift @@ -0,0 +1,58 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +private struct Constants { + static let UnlockPinCodeKey = "UnlockPinCodeKey" + static let UseTouchIDKey = "UseTouchIDKey" + static let LockTimeoutKey = "LockTimeoutKey" +} + +class ProfileSettings: NSObject, NSCoding { + enum LockTimeout: String { + case Immediately + case Seconds30 + case Minute1 + case Minute2 + case Minute5 + } + + /// Pin code used to unlock device. + var unlockPinCode: String? + + /// Whether use Touch ID for unlocking Antidote. + var useTouchID: Bool + + /// Time after which Antidote will be blocked in background. + var lockTimeout: LockTimeout + + required override init() { + unlockPinCode = nil + useTouchID = false + lockTimeout = .Immediately + + super.init() + } + + required init(coder aDecoder: NSCoder) { + unlockPinCode = aDecoder.decodeObject(forKey: Constants.UnlockPinCodeKey) as? String + useTouchID = aDecoder.decodeBool(forKey: Constants.UseTouchIDKey) + + if let rawTimeout = aDecoder.decodeObject(forKey: Constants.LockTimeoutKey) as? String { + lockTimeout = LockTimeout(rawValue: rawTimeout) ?? .Immediately + } + else { + lockTimeout = .Immediately + } + + super.init() + } + + func encode(with aCoder: NSCoder) { + aCoder.encode(unlockPinCode, forKey: Constants.UnlockPinCodeKey) + aCoder.encode(useTouchID, forKey: Constants.UseTouchIDKey) + aCoder.encode(lockTimeout.rawValue, forKey: Constants.LockTimeoutKey) + } +} diff --git a/Antidote/ProfileTabCoordinator.swift b/Antidote/ProfileTabCoordinator.swift new file mode 100644 index 0000000..78bb584 --- /dev/null +++ b/Antidote/ProfileTabCoordinator.swift @@ -0,0 +1,202 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import AudioToolbox + +protocol ProfileTabCoordinatorDelegate: class { + func profileTabCoordinatorDelegateLogout(_ coordinator: ProfileTabCoordinator) + func profileTabCoordinatorDelegateDeleteProfile(_ coordinator: ProfileTabCoordinator) + func profileTabCoordinatorDelegateDidChangeUserStatus(_ coordinator: ProfileTabCoordinator) + func profileTabCoordinatorDelegateDidChangeAvatar(_ coordinator: ProfileTabCoordinator) + func profileTabCoordinatorDelegateDidChangeUserName(_ coordinator: ProfileTabCoordinator) +} + +class ProfileTabCoordinator: ActiveSessionNavigationCoordinator { + weak var delegate: ProfileTabCoordinatorDelegate? + + fileprivate weak var toxManager: OCTManager! + + init(theme: Theme, toxManager: OCTManager) { + self.toxManager = toxManager + + super.init(theme: theme) + } + + override func startWithOptions(_ options: CoordinatorOptions?) { + let controller = ProfileMainController(theme: theme, submanagerUser: toxManager.user) + controller.delegate = self + navigationController.pushViewController(controller, animated: false) + } +} + +extension OCTToxErrorSetInfoCode: Error { + +} + +extension ProfileTabCoordinator: ProfileMainControllerDelegate { + func profileMainControllerLogout(_ controller: ProfileMainController) { + delegate?.profileTabCoordinatorDelegateLogout(self) + } + + func profileMainControllerChangeUserName(_ controller: ProfileMainController) { + showTextEditController(title: String(localized: "name"), defaultValue: toxManager.user.userName() ?? "") { + [unowned self] newName -> Void in + + do { + try self.toxManager.user.setUserName(newName) + self.delegate?.profileTabCoordinatorDelegateDidChangeUserName(self) + } + catch let error as NSError { + handleErrorWithType(.toxSetInfoCodeName, error: error) + } + } + } + + func profileMainControllerChangeUserStatus(_ controller: ProfileMainController) { + let controller = ChangeUserStatusController(theme: theme, selectedStatus: toxManager.user.userStatus) + controller.delegate = self + navigationController.pushViewController(controller, animated: true) + } + + func profileMainControllerChangeStatusMessage(_ controller: ProfileMainController) { + showTextEditController(title: String(localized: "status_message"), defaultValue: toxManager.user.userStatusMessage() ?? "") { + newStatusMessage -> Void in + + do { + try self.toxManager.user.setUserStatusMessage(newStatusMessage) + } + catch let error as NSError { + handleErrorWithType(.toxSetInfoCodeStatusMessage, error: error) + } + } + } + + func profileMainController(_ controller: ProfileMainController, showQRCodeWithText text: String) { + let controller = QRViewerController(theme: theme, text: text) + controller.delegate = self + + let toPresent = UINavigationController(rootViewController: controller) + + navigationController.present(toPresent, animated: true, completion: nil) + } + + func profileMainControllerShowProfileDetails(_ controller: ProfileMainController) { + let controller = ProfileDetailsController(theme: theme, toxManager: toxManager) + controller.delegate = self + navigationController.pushViewController(controller, animated: true) + } + + func profileMainControllerDidChangeAvatar(_ controller: ProfileMainController) { + delegate?.profileTabCoordinatorDelegateDidChangeAvatar(self) + } +} + +extension ProfileTabCoordinator: ChangeUserStatusControllerDelegate { + func changeUserStatusController(_ controller: ChangeUserStatusController, selectedStatus: OCTToxUserStatus) { + toxManager.user.userStatus = selectedStatus + navigationController.popViewController(animated: true) + + delegate?.profileTabCoordinatorDelegateDidChangeUserStatus(self) + } +} + +extension ProfileTabCoordinator: QRViewerControllerDelegate { + func qrViewerControllerDidFinishPresenting() { + navigationController.dismiss(animated: true, completion: nil) + } +} + +extension ProfileTabCoordinator: ChangePasswordControllerDelegate { + func changePasswordControllerDidFinishPresenting(_ controller: ChangePasswordController) { + navigationController.dismiss(animated: true, completion: nil) + } +} + +extension ProfileTabCoordinator: ProfileDetailsControllerDelegate { + func profileDetailsControllerSetPin(_ controller: ProfileDetailsController) { + let controller = EnterPinController(theme: theme, state: .setPin) + controller.topText = String(localized: "pin_set") + controller.delegate = self + + let toPresent = PortraitNavigationController(rootViewController: controller) + toPresent.isNavigationBarHidden = true + navigationController.present(toPresent, animated: true, completion: nil) + } + + func profileDetailsControllerChangeLockTimeout(_ controller: ProfileDetailsController) { + let controller = ChangePinTimeoutController(theme: theme, submanagerObjects: toxManager.objects) + controller.delegate = self + navigationController.pushViewController(controller, animated: true) + } + + func profileDetailsControllerChangePassword(_ controller: ProfileDetailsController) { + let controller = ChangePasswordController(theme: theme, toxManager: toxManager) + controller.delegate = self + + let toPresent = UINavigationController(rootViewController: controller) + navigationController.present(toPresent, animated: true, completion: nil) + } + + func profileDetailsControllerDeleteProfile(_ controller: ProfileDetailsController) { + delegate?.profileTabCoordinatorDelegateDeleteProfile(self) + } +} + +extension ProfileTabCoordinator: EnterPinControllerDelegate { + func enterPinController(_ controller: EnterPinController, successWithPin pin: String) { + switch controller.state { + case .validatePin: + let settings = toxManager.objects.getProfileSettings() + settings.unlockPinCode = pin + toxManager.objects.saveProfileSettings(settings) + + navigationController.dismiss(animated: true, completion: nil) + case .setPin: + guard let presentedNavigation = controller.navigationController else { + fatalError("wrong state") + } + + let validate = EnterPinController(theme: theme, state: .validatePin(validPin: pin)) + validate.topText = String(localized: "pin_confirm") + validate.delegate = self + + presentedNavigation.viewControllers = [validate] + } + } + + func enterPinControllerFailure(_ controller: EnterPinController) { + guard let presentedNavigation = controller.navigationController else { + fatalError("wrong state") + } + + AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate)) + + let setPin = EnterPinController(theme: theme, state: .setPin) + setPin.topText = String(localized: "pin_do_not_match") + setPin.delegate = self + + presentedNavigation.viewControllers = [setPin] + } +} + +extension ProfileTabCoordinator: ChangePinTimeoutControllerDelegate { + func changePinTimeoutControllerDone(_ controller: ChangePinTimeoutController) { + navigationController.popViewController(animated: true) + } +} + +private extension ProfileTabCoordinator { + func showTextEditController(title: String, defaultValue: String, setValueClosure: @escaping (String) -> Void) { + let controller = TextEditController(theme: theme, title: title, defaultValue: defaultValue, changeTextHandler: { + newName -> Void in + + setValueClosure(newName) + }, userFinishedEditing: { [unowned self] in + self.navigationController.popViewController(animated: true) + }) + + navigationController.pushViewController(controller, animated: true) + } +} diff --git a/Antidote/ProgressCircleView.swift b/Antidote/ProgressCircleView.swift new file mode 100644 index 0000000..dd189c3 --- /dev/null +++ b/Antidote/ProgressCircleView.swift @@ -0,0 +1,71 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +private struct Constants { + static let LineWidth: CGFloat = 6.0 + static let AnimationDuration = 1.0 +} + +class ProgressCircleView: UIView { + fileprivate let backgroundLayer: CAShapeLayer + fileprivate let progressLayer: CAShapeLayer + + var backgroundLineColor: UIColor? { + didSet { + backgroundLayer.strokeColor = backgroundLineColor?.cgColor + } + } + + var lineColor: UIColor? { + didSet { + progressLayer.strokeColor = lineColor?.cgColor + } + } + + /// From 0.0 to 1.0 + var progress: CGFloat = 0.0 { + didSet { + progressLayer.strokeEnd = progress + } + } + + override init(frame: CGRect) { + backgroundLayer = CAShapeLayer() + progressLayer = CAShapeLayer() + + super.init(frame: frame) + + backgroundLayer.fillColor = UIColor.clear.cgColor + backgroundLayer.lineWidth = Constants.LineWidth + layer.addSublayer(backgroundLayer) + + progressLayer.strokeEnd = progress + progressLayer.fillColor = UIColor.clear.cgColor + progressLayer.lineWidth = Constants.LineWidth + layer.addSublayer(progressLayer) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + let bezierPath = UIBezierPath() + bezierPath.addArc( + withCenter: CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2), + radius: bounds.size.width / 2, + startAngle: CGFloat(-Double.pi / 2), + endAngle: CGFloat(1.5 * Double.pi), + clockwise: true) + + backgroundLayer.path = bezierPath.cgPath + backgroundLayer.frame = bounds + progressLayer.path = bezierPath.cgPath + progressLayer.frame = bounds + } +} diff --git a/Antidote/QRScannerAimView.swift b/Antidote/QRScannerAimView.swift new file mode 100644 index 0000000..81b45c4 --- /dev/null +++ b/Antidote/QRScannerAimView.swift @@ -0,0 +1,38 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +class QRScannerAimView: UIView { + fileprivate let dashLayer: CAShapeLayer + + init(theme: Theme) { + dashLayer = CAShapeLayer() + + super.init(frame: CGRect.zero) + + configureDashLayer(theme) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var frame: CGRect { + didSet { + dashLayer.path = UIBezierPath(rect: bounds).cgPath + dashLayer.frame = bounds + } + } +} + +private extension QRScannerAimView { + func configureDashLayer(_ theme: Theme) { + dashLayer.strokeColor = theme.colorForType(.LinkText).cgColor + dashLayer.fillColor = UIColor.clear.cgColor + dashLayer.lineDashPattern = [20, 5] + dashLayer.lineWidth = 2.0 + layer.addSublayer(dashLayer) + } +} diff --git a/Antidote/QRScannerController.swift b/Antidote/QRScannerController.swift new file mode 100644 index 0000000..8f06351 --- /dev/null +++ b/Antidote/QRScannerController.swift @@ -0,0 +1,179 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import AVFoundation + +class QRScannerController: UIViewController { + var didScanStringsBlock: (([String]) -> Void)? + var cancelBlock: (() -> Void)? + + fileprivate let theme: Theme + + fileprivate var previewLayer: AVCaptureVideoPreviewLayer! + fileprivate var captureSession: AVCaptureSession! + + fileprivate var aimView: QRScannerAimView! + + var pauseScanning: Bool = false { + didSet { + pauseScanning ? captureSession.stopRunning() : captureSession.startRunning() + + if !pauseScanning { + aimView.frame = CGRect.zero + } + } + } + + init(theme: Theme) { + self.theme = theme + + super.init(nibName: nil, bundle: nil) + + createCaptureSession() + createBarButtonItems() + + NotificationCenter.default.addObserver( + self, + selector: #selector(QRScannerController.applicationDidEnterBackground), + name: NSNotification.Name.UIApplicationDidEnterBackground, + object: nil) + + NotificationCenter.default.addObserver( + self, + selector: #selector(QRScannerController.applicationWillEnterForeground), + name: NSNotification.Name.UIApplicationWillEnterForeground, + object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + createViewsAndLayers() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + captureSession.startRunning() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + captureSession.stopRunning() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + previewLayer.frame = view.bounds + } +} + +// MARK: Actions +extension QRScannerController { + @objc func cancelButtonPressed() { + cancelBlock?() + } +} + +// MARK: Notifications +extension QRScannerController { + @objc func applicationDidEnterBackground() { + captureSession.stopRunning() + } + + @objc func applicationWillEnterForeground() { + if !pauseScanning { + captureSession.startRunning() + } + } +} + +extension QRScannerController: AVCaptureMetadataOutputObjectsDelegate { + func metadataOutput(_ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { + let readableObjects = metadataObjects.filter { + $0 is AVMetadataMachineReadableCodeObject + }.map { + previewLayer.transformedMetadataObject(for: $0 ) as! AVMetadataMachineReadableCodeObject + } + + guard !readableObjects.isEmpty else { + return + } + + aimView.frame = readableObjects[0].bounds + + let strings = readableObjects.map { + $0.stringValue! + } + + didScanStringsBlock?(strings) + } +} + +private extension QRScannerController { + func createCaptureSession() { + captureSession = AVCaptureSession() + + let input = captureSessionInput() + let output = AVCaptureMetadataOutput() + + if (input != nil) && captureSession.canAddInput(input!) { + captureSession.addInput(input!) + } + + if captureSession.canAddOutput(output) { + captureSession.addOutput(output) + + output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) + + if output.availableMetadataObjectTypes.contains({ AVMetadataObject.ObjectType.qr }()) { + output.metadataObjectTypes = [AVMetadataObject.ObjectType.qr] + } + } + } + + func captureSessionInput() -> AVCaptureDeviceInput? { + guard let device = AVCaptureDevice.default(for: AVMediaType.video) else { + return nil + } + + if device.isAutoFocusRangeRestrictionSupported { + do { + try device.lockForConfiguration() + device.autoFocusRangeRestriction = .near + device.unlockForConfiguration() + } + catch { + // nop + } + } + + return try? AVCaptureDeviceInput(device: device) + } + + func createBarButtonItems() { + navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(QRScannerController.cancelButtonPressed)) + } + + func createViewsAndLayers() { + previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill + view.layer.addSublayer(previewLayer) + + aimView = QRScannerAimView(theme: theme) + view.addSubview(aimView) + view.bringSubview(toFront: aimView) + } +} diff --git a/Antidote/QRViewerController.swift b/Antidote/QRViewerController.swift new file mode 100644 index 0000000..be2e469 --- /dev/null +++ b/Antidote/QRViewerController.swift @@ -0,0 +1,99 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +protocol QRViewerControllerDelegate: class { + func qrViewerControllerDidFinishPresenting() +} + +class QRViewerController: UIViewController { + weak var delegate: QRViewerControllerDelegate? + + fileprivate let theme: Theme + fileprivate let text: String + + fileprivate var previousBrightness: CGFloat = 1.0 + + fileprivate var closeButton: UIButton! + fileprivate var imageView: UIImageView! + + init(theme: Theme, text: String) { + self.theme = theme + self.text = text + + super.init(nibName: nil, bundle: nil) + + edgesForExtendedLayout = UIRectEdge() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + installNavigationItems() + createViews() + installConstraints() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + previousBrightness = UIScreen.main.brightness + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + UIScreen.main.brightness = previousBrightness + } +} + +extension QRViewerController { + @objc func closeButtonPressed() { + delegate?.qrViewerControllerDidFinishPresenting() + } +} + +private extension QRViewerController { + func installNavigationItems() { + navigationItem.rightBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .done, + target: self, + action: #selector(QRViewerController.closeButtonPressed)) + } + + func createViews() { + imageView = UIImageView(image: qrImageFromText()) + imageView.contentMode = .scaleAspectFit + view.addSubview(imageView) + } + + func installConstraints() { + imageView.snp.makeConstraints { + $0.center.equalTo(view) + $0.width.lessThanOrEqualTo(view.snp.width) + $0.width.lessThanOrEqualTo(view.snp.height) + $0.width.equalTo(imageView.snp.height) + } + } + + func qrImageFromText() -> UIImage { + let filter = CIFilter(name:"CIQRCodeGenerator")! + filter.setDefaults() + filter.setValue(text.data(using: String.Encoding.utf8), forKey: "inputMessage") + + let ciImage = filter.outputImage! + let screenBounds = UIScreen.main.bounds + + let scale = min(screenBounds.size.width / ciImage.extent.size.width, screenBounds.size.height / ciImage.extent.size.height) + let transformedImage = ciImage.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) + + return UIImage(ciImage: transformedImage) + } +} diff --git a/Antidote/QuickLookPreviewController.swift b/Antidote/QuickLookPreviewController.swift new file mode 100644 index 0000000..b712f5a --- /dev/null +++ b/Antidote/QuickLookPreviewController.swift @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import QuickLook + +protocol QuickLookPreviewControllerDataSource: QLPreviewControllerDataSource { + var previewController: QuickLookPreviewController? { get set } +} + +class QuickLookPreviewController: QLPreviewController { + var dataSourceStorage: QuickLookPreviewControllerDataSource? +} diff --git a/Antidote/Reach.swift b/Antidote/Reach.swift new file mode 100644 index 0000000..a6eeca7 --- /dev/null +++ b/Antidote/Reach.swift @@ -0,0 +1,119 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// The MIT License (MIT) +// +// Copyright (c) 2015 Isuru Nanayakkara +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + + +import Foundation +import SystemConfiguration + + +let ReachabilityStatusChangedNotification = "ReachabilityStatusChangedNotification" + +enum ReachabilityType: CustomStringConvertible { + case wwan + case wiFi + + var description: String { + switch self { + case .wwan: return "WWAN" + case .wiFi: return "WiFi" + } + } +} + +enum ReachabilityStatus: CustomStringConvertible { + case offline + case online(ReachabilityType) + case unknown + + var description: String { + switch self { + case .offline: return "Offline" + case .online(let type): return "Online (\(type))" + case .unknown: return "Unknown" + } + } +} + +open class Reach { + + func connectionStatus() -> ReachabilityStatus { + var zeroAddress = sockaddr_in() + zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) + zeroAddress.sin_family = sa_family_t(AF_INET) + + guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { + SCNetworkReachabilityCreateWithAddress(nil, $0) + } + }) else { + return .unknown + } + + var flags : SCNetworkReachabilityFlags = [] + if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) { + return .unknown + } + + return ReachabilityStatus(reachabilityFlags: flags) + } + + + func monitorReachabilityChanges() { + let host = "google.com" + var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) + let reachability = SCNetworkReachabilityCreateWithName(nil, host)! + + SCNetworkReachabilitySetCallback(reachability, { (_, flags, _) in + let status = ReachabilityStatus(reachabilityFlags: flags) + + NotificationCenter.default.post(name: Notification.Name(rawValue: ReachabilityStatusChangedNotification), + object: nil, + userInfo: ["Status": status.description]) + + }, &context) + + SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), CFRunLoopMode.commonModes.rawValue) + } + +} + +extension ReachabilityStatus { + fileprivate init(reachabilityFlags flags: SCNetworkReachabilityFlags) { + let connectionRequired = flags.contains(.connectionRequired) + let isReachable = flags.contains(.reachable) + let isWWAN = flags.contains(.isWWAN) + + if !connectionRequired && isReachable { + if isWWAN { + self = .online(.wwan) + } else { + self = .online(.wiFi) + } + } else { + self = .offline + } + } +} diff --git a/Antidote/Results.swift b/Antidote/Results.swift new file mode 100644 index 0000000..e41e075 --- /dev/null +++ b/Antidote/Results.swift @@ -0,0 +1,82 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +/// Swift wrapper for RLMResults +class Results { + fileprivate let results: RLMResults + + var count: Int { + get { + return Int(results.count) + } + } + + var firstObject: T { + get { + return results.firstObject() as! T + } + } + + var lastObject: T { + get { + return results.lastObject() as! T + } + } + + init(results: RLMResults) { + let name = NSStringFromClass(T.self) + assert(name == results.objectClassName, "Specified wrong generic class") + + self.results = results + } + + func indexOfObject(_ object: T) -> Int { + return Int(results.index(of: object)) + } + + func sortedResultsUsingProperty(_ property: String, ascending: Bool) -> Results { + let sortedResults = results.sortedResults(usingKeyPath: property, ascending: ascending) + return Results(results: sortedResults) + } + + func sortedResultsUsingDescriptors(_ properties: Array) -> Results { + let sortedResults = results.sortedResults(using: properties) + return Results(results: sortedResults) + } + + func addNotificationBlock(_ block: @escaping (ResultsChange) -> Void) -> RLMNotificationToken { + return results.addNotificationBlock { rlmResults, changes, error in + DispatchQueue.main.async { + if let error = error { + block(ResultsChange.error(error as NSError)) + return + } + + let results: Results? = (rlmResults != nil) ? Results(results: rlmResults!) : nil + + if let changes = changes { + block(ResultsChange.update(results, + deletions: changes.deletions as! [Int], + insertions: changes.insertions as! [Int], + modifications: changes.modifications as! [Int])) + return + } + + block(ResultsChange.initial(results)) + } + } + } + + subscript(index: Int) -> T { + return results[UInt(index)] as! T + } + + func objects(with predicate: NSPredicate) -> Results { + let matching = results.objects(with: predicate) + return Results(results: matching) + } +} + diff --git a/Antidote/ResultsChange.swift b/Antidote/ResultsChange.swift new file mode 100644 index 0000000..6103659 --- /dev/null +++ b/Antidote/ResultsChange.swift @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +/// Swift wrapper for RLMResults addNotificationBlock. +enum ResultsChange { + case initial(Results?) + case update(Results?, deletions: [Int], insertions: [Int], modifications: [Int]) + case error(NSError) +} diff --git a/Antidote/ResultsExtension.swift b/Antidote/ResultsExtension.swift new file mode 100644 index 0000000..92b05c4 --- /dev/null +++ b/Antidote/ResultsExtension.swift @@ -0,0 +1,21 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// +// ResultsExtension.swift +// Antidote +// +// Created by Dmytro Vorobiov on 26/04/2017. +// Copyright © 2017 dvor. All rights reserved. +// + +import Foundation + +extension Results where T : OCTMessageAbstract { + func undeliveredMessages() -> Results { + let undeliveredPredicate = NSPredicate(format: "messageText != nil AND messageText.isDelivered == NO AND senderUniqueIdentifier == nil") + return self.objects(with: undeliveredPredicate) + } +} + diff --git a/Antidote/RoundedButton.swift b/Antidote/RoundedButton.swift new file mode 100644 index 0000000..6cb03cd --- /dev/null +++ b/Antidote/RoundedButton.swift @@ -0,0 +1,52 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +private struct Constants { + static let Height = 40.0 +} + +class RoundedButton: UIButton { + enum ButtonType { + case login + case runningPositive + case runningNegative + } + + init(theme: Theme, type: ButtonType) { + super.init(frame: CGRect.zero) + + let titleColor: UIColor + let bgColor: UIColor + + switch type { + case .login: + titleColor = theme.colorForType(.LoginButtonText) + bgColor = theme.colorForType(.LoginButtonBackground) + case .runningPositive: + titleColor = theme.colorForType(.RoundedButtonText) + bgColor = theme.colorForType(.RoundedPositiveButtonBackground) + case .runningNegative: + titleColor = theme.colorForType(.RoundedButtonText) + bgColor = theme.colorForType(.RoundedNegativeButtonBackground) + } + + setTitleColor(titleColor, for:UIControlState()) + titleLabel?.font = UIFont.systemFont(ofSize: 18.0) + layer.cornerRadius = 5.0 + layer.masksToBounds = true + + let bgImage = UIImage.imageWithColor(bgColor, size: CGSize(width: 1.0, height: 1.0)) + setBackgroundImage(bgImage, for:UIControlState()) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var intrinsicContentSize : CGSize { + return CGSize(width: 0.0, height: Constants.Height) + } +} diff --git a/Antidote/RunningCoordinator.swift b/Antidote/RunningCoordinator.swift new file mode 100644 index 0000000..2e3f5e1 --- /dev/null +++ b/Antidote/RunningCoordinator.swift @@ -0,0 +1,92 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +protocol RunningCoordinatorDelegate: class { + func runningCoordinatorDidLogout(_ coordinator: RunningCoordinator, importToxProfileFromURL: URL?) + func runningCoordinatorDeleteProfile(_ coordinator: RunningCoordinator) + func runningCoordinatorRecreateCoordinatorsStack(_ coordinator: RunningCoordinator, options: CoordinatorOptions) +} + +class RunningCoordinator { + weak var delegate: RunningCoordinatorDelegate? + + fileprivate let theme: Theme + fileprivate let window: UIWindow + + fileprivate var toxManager: OCTManager + fileprivate var options: CoordinatorOptions? + + var activeSessionCoordinator: ActiveSessionCoordinator? + fileprivate var pinAuthorizationCoordinator: PinAuthorizationCoordinator + + init(theme: Theme, window: UIWindow, toxManager: OCTManager, skipAuthorizationChallenge: Bool) { + self.theme = theme + self.window = window + self.toxManager = toxManager + self.pinAuthorizationCoordinator = PinAuthorizationCoordinator(theme: theme, + submanagerObjects: toxManager.objects, + lockOnStartup: !skipAuthorizationChallenge) + + pinAuthorizationCoordinator.delegate = self + } +} + +extension RunningCoordinator: TopCoordinatorProtocol { + func startWithOptions(_ options: CoordinatorOptions?) { + self.options = options + + activeSessionCoordinator = ActiveSessionCoordinator(theme: theme, window: window, toxManager: toxManager) + activeSessionCoordinator?.delegate = self + activeSessionCoordinator?.startWithOptions(options) + + pinAuthorizationCoordinator.startWithOptions(nil) + } + + func handleLocalNotification(_ notification: UILocalNotification) { + activeSessionCoordinator?.handleLocalNotification(notification) + } + + func handleInboxURL(_ url: URL) { + activeSessionCoordinator?.handleInboxURL(url) + } +} + +extension RunningCoordinator: ActiveSessionCoordinatorDelegate { + func activeSessionCoordinatorDidLogout(_ coordinator: ActiveSessionCoordinator, importToxProfileFromURL url: URL?) { + logoutUser(importToxProfileFromURL: url) + } + + func activeSessionCoordinatorDeleteProfile(_ coordinator: ActiveSessionCoordinator) { + delegate?.runningCoordinatorDeleteProfile(self) + } + + func activeSessionCoordinatorRecreateCoordinatorsStack(_ coordinator: ActiveSessionCoordinator, options: CoordinatorOptions) { + delegate?.runningCoordinatorRecreateCoordinatorsStack(self, options: options) + } + + func activeSessionCoordinatorDidStartCall(_ coordinator: ActiveSessionCoordinator) { + pinAuthorizationCoordinator.preventFromLocking = true + } + + func activeSessionCoordinatorDidFinishCall(_ coordinator: ActiveSessionCoordinator) { + pinAuthorizationCoordinator.preventFromLocking = false + } +} + +extension RunningCoordinator: PinAuthorizationCoordinatorDelegate { + func pinAuthorizationCoordinatorDidLogout(_ coordinator: PinAuthorizationCoordinator) { + logoutUser() + } +} + +private extension RunningCoordinator { + func logoutUser(importToxProfileFromURL url: URL? = nil) { + let keychainManager = KeychainManager() + keychainManager.deleteActiveAccountData() + + delegate?.runningCoordinatorDidLogout(self, importToxProfileFromURL: url) + } +} diff --git a/Antidote/SettingsAboutController.swift b/Antidote/SettingsAboutController.swift new file mode 100644 index 0000000..7a4c5ff --- /dev/null +++ b/Antidote/SettingsAboutController.swift @@ -0,0 +1,61 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +protocol SettingsAboutControllerDelegate: class { + func settingsAboutControllerShowAcknowledgements(_ controller: SettingsAboutController) +} + +class SettingsAboutController: StaticTableController { + weak var delegate: SettingsAboutControllerDelegate? + + fileprivate let antidoteVersionModel = StaticTableInfoCellModel() + fileprivate let antidoteBuildModel = StaticTableInfoCellModel() + fileprivate let toxcoreVersionModel = StaticTableInfoCellModel() + fileprivate let acknowledgementsModel = StaticTableDefaultCellModel() + + init(theme: Theme) { + super.init(theme: theme, style: .grouped, model: [ + [ + antidoteVersionModel, + antidoteBuildModel, + ], + [ + toxcoreVersionModel, + ], + [ + acknowledgementsModel, + ], + ]) + + title = String(localized: "settings_about") + updateModels() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private extension SettingsAboutController { + func updateModels() { + antidoteVersionModel.title = String(localized: "settings_antidote_version") + antidoteVersionModel.value = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + + antidoteBuildModel.title = String(localized: "settings_antidote_build") + antidoteBuildModel.value = Bundle.main.infoDictionary?["CFBundleVersion"] as? String + + toxcoreVersionModel.title = String(localized: "settings_toxcore_version") + toxcoreVersionModel.value = OCTTox.version() + + acknowledgementsModel.value = String(localized: "settings_acknowledgements") + acknowledgementsModel.didSelectHandler = showAcknowledgements + acknowledgementsModel.rightImageType = .arrow + } + + func showAcknowledgements(_: StaticTableBaseCell) { + delegate?.settingsAboutControllerShowAcknowledgements(self) + } +} diff --git a/Antidote/SettingsAdvancedController.swift b/Antidote/SettingsAdvancedController.swift new file mode 100644 index 0000000..6ea0d94 --- /dev/null +++ b/Antidote/SettingsAdvancedController.swift @@ -0,0 +1,60 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +protocol SettingsAdvancedControllerDelegate: class { + func settingsAdvancedControllerToxOptionsChanged(_ controller: SettingsAdvancedController) +} + +class SettingsAdvancedController: StaticTableController { + weak var delegate: SettingsAdvancedControllerDelegate? + + fileprivate let theme: Theme + fileprivate let userDefaults = UserDefaultsManager() + + fileprivate let UDPModel = StaticTableSwitchCellModel() + fileprivate let restoreDefaultsModel = StaticTableButtonCellModel() + + init(theme: Theme) { + self.theme = theme + + super.init(theme: theme, style: .grouped, model: [ + [ + UDPModel, + ], + [ + restoreDefaultsModel, + ], + ]) + + title = String(localized: "settings_advanced_settings") + updateModels() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private extension SettingsAdvancedController { + func updateModels() { + UDPModel.title = String(localized: "settings_udp_enabled") + UDPModel.on = userDefaults.UDPEnabled + UDPModel.valueChangedHandler = UDPChanged + + restoreDefaultsModel.title = String(localized: "settings_restore_default") + restoreDefaultsModel.didSelectHandler = restoreDefaultsSettings + } + + func UDPChanged(_ on: Bool) { + userDefaults.UDPEnabled = on + delegate?.settingsAdvancedControllerToxOptionsChanged(self) + } + + func restoreDefaultsSettings(_: StaticTableBaseCell) { + userDefaults.resetUDPEnabled() + delegate?.settingsAdvancedControllerToxOptionsChanged(self) + } +} diff --git a/Antidote/SettingsMainController.swift b/Antidote/SettingsMainController.swift new file mode 100644 index 0000000..1a1b384 --- /dev/null +++ b/Antidote/SettingsMainController.swift @@ -0,0 +1,149 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +protocol SettingsMainControllerDelegate: class { + func settingsMainControllerShowAboutScreen(_ controller: SettingsMainController) + func settingsMainControllerShowFaqScreen(_ controller: SettingsMainController) + func settingsMainControllerShowAdvancedSettings(_ controller: SettingsMainController) + func settingsMainControllerChangeAutodownloadImages(_ controller: SettingsMainController) +} + +class SettingsMainController: StaticTableController { + weak var delegate: SettingsMainControllerDelegate? + + fileprivate let theme: Theme + fileprivate let userDefaults = UserDefaultsManager() + + fileprivate let aboutModel = StaticTableDefaultCellModel() + fileprivate let faqModel = StaticTableDefaultCellModel() + fileprivate let autodownloadImagesModel = StaticTableInfoCellModel() + fileprivate let notificationsModel = StaticTableSwitchCellModel() + fileprivate let longerbgModel = StaticTableSwitchCellModel() + fileprivate let debugmodeModel = StaticTableSwitchCellModel() + fileprivate let dateonmessagemodeModel = StaticTableSwitchCellModel() + fileprivate let advancedSettingsModel = StaticTableDefaultCellModel() + + init(theme: Theme) { + self.theme = theme + + super.init(theme: theme, style: .grouped, model: [ + [ + autodownloadImagesModel, + ], + [ + longerbgModel, + ], + [ + notificationsModel, + dateonmessagemodeModel, + debugmodeModel, + ], + [ + advancedSettingsModel, + ], + [ + faqModel, + aboutModel, + ], + ], footers: [ + String(localized: "settings_autodownload_images_description"), + "This will keep the Application running for longer in the background to finish sending messages, but this will also reveal more meta data about you. It will link your IP address and your PUSH token. It's a tradeoff between convenience and metadata privacy.\n\nYou can use ProtonVPN to prevent that.\n\nSee https://protonvpn.com/free-vpn/\n\nand\n\nhttps://apps.apple.com/app/apple-store/id1437005085", + nil, + nil, + nil, + ]) + + title = String(localized: "settings_title") + updateModels() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + updateModels() + reloadTableView() + } +} + +private extension SettingsMainController{ + func updateModels() { + aboutModel.value = String(localized: "settings_about") + aboutModel.didSelectHandler = showAboutScreen + aboutModel.rightImageType = .arrow + + faqModel.value = String(localized: "settings_faq") + faqModel.didSelectHandler = showFaqScreen + faqModel.rightImageType = .arrow + + autodownloadImagesModel.title = String(localized: "settings_autodownload_images") + autodownloadImagesModel.showArrow = true + autodownloadImagesModel.didSelectHandler = changeAutodownloadImages + switch userDefaults.autodownloadImages { + case .Never: + autodownloadImagesModel.value = String(localized: "settings_never") + case .UsingWiFi: + autodownloadImagesModel.value = String(localized: "settings_wifi") + case .Always: + autodownloadImagesModel.value = String(localized: "settings_always") + } + + notificationsModel.title = String(localized: "settings_notifications_message_preview") + notificationsModel.on = userDefaults.showNotificationPreview + notificationsModel.valueChangedHandler = notificationsValueChanged + + longerbgModel.title = "Longer Background Mode" + longerbgModel.on = userDefaults.LongerbgMode + longerbgModel.valueChangedHandler = longerbgValueChanged + + debugmodeModel.title = "Debug Mode" + debugmodeModel.on = userDefaults.DebugMode + debugmodeModel.valueChangedHandler = debugmodeValueChanged + + dateonmessagemodeModel.title = "Always show date on Messages" + dateonmessagemodeModel.on = userDefaults.DateonmessageMode + dateonmessagemodeModel.valueChangedHandler = dateonmessagemodeValueChanged + + advancedSettingsModel.value = String(localized: "settings_advanced_settings") + advancedSettingsModel.didSelectHandler = showAdvancedSettings + advancedSettingsModel.rightImageType = .arrow + } + + func showAboutScreen(_: StaticTableBaseCell) { + delegate?.settingsMainControllerShowAboutScreen(self) + } + + func showFaqScreen(_: StaticTableBaseCell) { + delegate?.settingsMainControllerShowFaqScreen(self) + } + + func notificationsValueChanged(_ on: Bool) { + userDefaults.showNotificationPreview = on + } + + func longerbgValueChanged(_ on: Bool) { + userDefaults.LongerbgMode = on + } + + func debugmodeValueChanged(_ on: Bool) { + userDefaults.DebugMode = on + } + + func dateonmessagemodeValueChanged(_ on: Bool) { + userDefaults.DateonmessageMode = on + } + + func changeAutodownloadImages(_: StaticTableBaseCell) { + delegate?.settingsMainControllerChangeAutodownloadImages(self) + } + + func showAdvancedSettings(_: StaticTableBaseCell) { + delegate?.settingsMainControllerShowAdvancedSettings(self) + } +} diff --git a/Antidote/SettingsTabCoordinator.swift b/Antidote/SettingsTabCoordinator.swift new file mode 100644 index 0000000..763c6a5 --- /dev/null +++ b/Antidote/SettingsTabCoordinator.swift @@ -0,0 +1,95 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SafariServices + +private struct Options { + static let ToShowKey = "ToShowKey" + + enum Controller { + case advancedSettings + } +} + +protocol SettingsTabCoordinatorDelegate: class { + func settingsTabCoordinatorRecreateCoordinatorsStack(_ coordinator: SettingsTabCoordinator, options: CoordinatorOptions) +} + +class SettingsTabCoordinator: ActiveSessionNavigationCoordinator { + weak var delegate: SettingsTabCoordinatorDelegate? + + override func startWithOptions(_ options: CoordinatorOptions?) { + let controller = SettingsMainController(theme: theme) + controller.delegate = self + + navigationController.pushViewController(controller, animated: false) + + if let toShow = options?[Options.ToShowKey] as? Options.Controller { + switch toShow { + case .advancedSettings: + let advanced = SettingsAdvancedController(theme: theme) + advanced.delegate = self + + navigationController.pushViewController(advanced, animated: false) + } + } + } +} + +extension SettingsTabCoordinator: SettingsMainControllerDelegate { + func settingsMainControllerShowAboutScreen(_ controller: SettingsMainController) { + let controller = SettingsAboutController(theme: theme) + controller.delegate = self + + navigationController.pushViewController(controller, animated: true) + } + + func settingsMainControllerShowFaqScreen(_ controller: SettingsMainController) { + let controller = FAQController(theme: theme) + + navigationController.pushViewController(controller, animated: true) + } + + func settingsMainControllerChangeAutodownloadImages(_ controller: SettingsMainController) { + let controller = ChangeAutodownloadImagesController(theme: theme) + controller.delegate = self + + navigationController.pushViewController(controller, animated: true) + } + + func settingsMainControllerShowAdvancedSettings(_ controller: SettingsMainController) { + let controller = SettingsAdvancedController(theme: theme) + controller.delegate = self + + navigationController.pushViewController(controller, animated: true) + } +} + +extension SettingsTabCoordinator: SettingsAboutControllerDelegate { + func settingsAboutControllerShowAcknowledgements(_ controller: SettingsAboutController) { + let controller = TextViewController( + resourceName: "antidote-acknowledgements", + backgroundColor: theme.colorForType(.NormalBackground), + titleColor: theme.colorForType(.NormalText), + textColor: theme.colorForType(.NormalText)) + controller.title = String(localized: "settings_acknowledgements") + + navigationController.pushViewController(controller, animated: true) + } +} + +extension SettingsTabCoordinator: ChangeAutodownloadImagesControllerDelegate { + func changeAutodownloadImagesControllerDidChange(_ controller: ChangeAutodownloadImagesController) { + navigationController.popViewController(animated: true) + } +} + +extension SettingsTabCoordinator: SettingsAdvancedControllerDelegate { + func settingsAdvancedControllerToxOptionsChanged(_ controller: SettingsAdvancedController) { + delegate?.settingsTabCoordinatorRecreateCoordinatorsStack(self, options: [ + Options.ToShowKey: Options.Controller.advancedSettings + ]) + } +} diff --git a/Antidote/StaticBackgroundView.swift b/Antidote/StaticBackgroundView.swift new file mode 100644 index 0000000..76f5713 --- /dev/null +++ b/Antidote/StaticBackgroundView.swift @@ -0,0 +1,21 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +/** + View with static background color. Is used to prevent views inside UITableViewCell from blinking on tap. + */ +class StaticBackgroundView: UIView { + override var backgroundColor: UIColor? { + get { + return super.backgroundColor + } + set {} + } + + func setStaticBackgroundColor(_ color: UIColor?) { + super.backgroundColor = color + } +} diff --git a/Antidote/StaticTableAvatarCell.swift b/Antidote/StaticTableAvatarCell.swift new file mode 100644 index 0000000..96246cd --- /dev/null +++ b/Antidote/StaticTableAvatarCell.swift @@ -0,0 +1,95 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let AvatarVerticalOffset = 10.0 +} + +class StaticTableAvatarCell: StaticTableBaseCell { + fileprivate var didTapOnAvatar: ((StaticTableAvatarCell) -> Void)? + + fileprivate var button: UIButton! + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let avatarModel = model as? StaticTableAvatarCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + selectionStyle = .none + + button.isUserInteractionEnabled = avatarModel.userInteractionEnabled + button.setImage(avatarModel.avatar, for: UIControlState()) + didTapOnAvatar = avatarModel.didTapOnAvatar + } + + override func createViews() { + super.createViews() + + button = UIButton() + button.layer.cornerRadius = StaticTableAvatarCellModel.Constants.AvatarImageSize / 2 + button.layer.masksToBounds = true + button.addTarget(self, action: #selector(StaticTableAvatarCell.buttonPressed), for: .touchUpInside) + customContentView.addSubview(button) + } + + override func installConstraints() { + super.installConstraints() + + button.snp.makeConstraints { + $0.centerX.equalTo(customContentView) + $0.top.equalTo(customContentView).offset(Constants.AvatarVerticalOffset) + $0.bottom.equalTo(customContentView).offset(-Constants.AvatarVerticalOffset) + $0.size.equalTo(StaticTableAvatarCellModel.Constants.AvatarImageSize) + } + } +} + +// Accessibility +extension StaticTableAvatarCell { + override var isAccessibilityElement: Bool { + get { + return true + } + set {} + } + + override var accessibilityLabel: String? { + get { + return String(localized: "accessibility_avatar_button_label") + } + set {} + } + + override var accessibilityHint: String? { + get { + return button.isUserInteractionEnabled ? String(localized: "accessibility_avatar_button_hint") : nil + } + set {} + } + + override var accessibilityTraits: UIAccessibilityTraits { + get { + var traits = UIAccessibilityTraitImage + + if button.isUserInteractionEnabled { + traits |= UIAccessibilityTraitButton + } + + return traits + } + set {} + } +} + +extension StaticTableAvatarCell { + @objc func buttonPressed() { + didTapOnAvatar?(self) + } +} diff --git a/Antidote/StaticTableAvatarCellModel.swift b/Antidote/StaticTableAvatarCellModel.swift new file mode 100644 index 0000000..2fb8d35 --- /dev/null +++ b/Antidote/StaticTableAvatarCellModel.swift @@ -0,0 +1,16 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class StaticTableAvatarCellModel: StaticTableBaseCellModel { + struct Constants { + static let AvatarImageSize: CGFloat = 120.0 + } + + var avatar: UIImage? + var didTapOnAvatar: ((StaticTableAvatarCell) -> Void)? + + var userInteractionEnabled: Bool = true +} diff --git a/Antidote/StaticTableBaseCell.swift b/Antidote/StaticTableBaseCell.swift new file mode 100644 index 0000000..0af0535 --- /dev/null +++ b/Antidote/StaticTableBaseCell.swift @@ -0,0 +1,68 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let HorizontalOffset = 20.0 + static let MinHeight = 50.0 +} + +class StaticTableBaseCell: BaseCell { + /** + View to add all content to. + */ + var customContentView: UIView! + + fileprivate var bottomSeparatorView: UIView! + + func setBottomSeparatorHidden(_ hidden: Bool) { + bottomSeparatorView.isHidden = hidden + } + + /** + Override this method in subclass. + */ + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + bottomSeparatorView.backgroundColor = theme.colorForType(.SeparatorsAndBorders) + } + + /** + Override this method in subclass. + */ + override func createViews() { + super.createViews() + + customContentView = UIView() + customContentView.backgroundColor = UIColor.clear + contentView.addSubview(customContentView) + + bottomSeparatorView = UIView() + contentView.addSubview(bottomSeparatorView) + } + + /** + Override this method in subclass. + */ + override func installConstraints() { + super.installConstraints() + + customContentView.snp.makeConstraints { + $0.leading.equalTo(contentView).offset(Constants.HorizontalOffset) + $0.trailing.equalTo(contentView).offset(-Constants.HorizontalOffset) + $0.top.equalTo(contentView) + $0.height.greaterThanOrEqualTo(Constants.MinHeight) + } + + bottomSeparatorView.snp.makeConstraints { + $0.leading.equalTo(customContentView) + $0.top.equalTo(customContentView.snp.bottom) + $0.trailing.bottom.equalTo(contentView) + $0.height.equalTo(0.5) + } + } +} diff --git a/Antidote/StaticTableBaseCellModel.swift b/Antidote/StaticTableBaseCellModel.swift new file mode 100644 index 0000000..dc626ab --- /dev/null +++ b/Antidote/StaticTableBaseCellModel.swift @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class StaticTableBaseCellModel: BaseCellModel { + +} diff --git a/Antidote/StaticTableButtonCell.swift b/Antidote/StaticTableButtonCell.swift new file mode 100644 index 0000000..171d63c --- /dev/null +++ b/Antidote/StaticTableButtonCell.swift @@ -0,0 +1,66 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +private struct Constants { + static let VerticalOffset = 12.0 +} + +class StaticTableButtonCell: StaticTableBaseCell { + fileprivate var label: UILabel! + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let buttonModel = model as? StaticTableButtonCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + label.text = buttonModel.title + label.textColor = theme.colorForType(.LinkText) + } + + override func createViews() { + super.createViews() + + label = UILabel() + customContentView.addSubview(label) + } + + override func installConstraints() { + super.installConstraints() + + label.snp.makeConstraints { + $0.leading.trailing.equalTo(customContentView) + $0.top.equalTo(customContentView).offset(Constants.VerticalOffset) + $0.bottom.equalTo(customContentView).offset(-Constants.VerticalOffset) + } + } +} + +// Accessibility +extension StaticTableButtonCell { + override var isAccessibilityElement: Bool { + get { + return true + } + set {} + } + + override var accessibilityLabel: String? { + get { + return label.text + } + set {} + } + + override var accessibilityTraits: UIAccessibilityTraits { + get { + return UIAccessibilityTraitButton + } + set {} + } +} diff --git a/Antidote/StaticTableButtonCellModel.swift b/Antidote/StaticTableButtonCellModel.swift new file mode 100644 index 0000000..9684b04 --- /dev/null +++ b/Antidote/StaticTableButtonCellModel.swift @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class StaticTableButtonCellModel: StaticTableSelectableCellModel { + var title: String? +} diff --git a/Antidote/StaticTableChatButtonsCell.swift b/Antidote/StaticTableChatButtonsCell.swift new file mode 100644 index 0000000..d5589aa --- /dev/null +++ b/Antidote/StaticTableChatButtonsCell.swift @@ -0,0 +1,138 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let ButtonSize = 40.0 + static let VerticalOffset = 8.0 +} + +class StaticTableChatButtonsCell: StaticTableBaseCell { + fileprivate var chatButton: UIButton! + fileprivate var callButton: UIButton! + fileprivate var videoButton: UIButton! + + fileprivate var separators: [UIView]! + + fileprivate var chatButtonHandler: (() -> Void)? + fileprivate var callButtonHandler: (() -> Void)? + fileprivate var videoButtonHandler: (() -> Void)? + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let buttonsModel = model as? StaticTableChatButtonsCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + selectionStyle = .none + + chatButtonHandler = buttonsModel.chatButtonHandler + callButtonHandler = buttonsModel.callButtonHandler + videoButtonHandler = buttonsModel.videoButtonHandler + + chatButton.isEnabled = buttonsModel.chatButtonEnabled + callButton.isEnabled = buttonsModel.callButtonEnabled + videoButton.isEnabled = buttonsModel.videoButtonEnabled + } + + override func createViews() { + super.createViews() + + chatButton = createButtonWithImageName("friend-card-chat", + accessibilityLabel: String(localized: "accessibility_chat_button_label"), + accessibilityHint: String(localized: "accessibility_chat_button_hint"), + action: #selector(StaticTableChatButtonsCell.chatButtonPressed)) + callButton = createButtonWithImageName("start-call", + accessibilityLabel: String(localized: "accessibility_call_button_label"), + accessibilityHint: String(localized: "accessibility_call_button_hint"), + action: #selector(StaticTableChatButtonsCell.callButtonPressed)) + videoButton = createButtonWithImageName("video-call", + accessibilityLabel: String(localized: "accessibility_video_button_label"), + accessibilityHint: String(localized: "accessibility_video_button_hint"), + action: #selector(StaticTableChatButtonsCell.videoButtonPressed)) + + separators = [UIView]() + for _ in 0...3 { + let sep = UIView() + sep.backgroundColor = UIColor.clear + customContentView.addSubview(sep) + separators.append(sep) + } + } + + override func installConstraints() { + super.installConstraints() + + var previous: UIView? = nil + for (index, sep) in separators.enumerated() { + sep.snp.makeConstraints { + if previous != nil { + $0.width.equalTo(previous!.snp.width) + } + + if index == 0 { + $0.leading.equalTo(customContentView) + } + + if index == (separators.count-1) { + $0.trailing.equalTo(customContentView) + } + } + + previous = sep + } + + func installForButton(_ button: UIButton, index: Int) { + button.snp.makeConstraints { + $0.top.equalTo(customContentView).offset(Constants.VerticalOffset) + $0.bottom.equalTo(customContentView).offset(-Constants.VerticalOffset) + + $0.leading.equalTo(separators[index].snp.trailing) + $0.trailing.equalTo(separators[index+1].snp.leading) + + $0.size.equalTo(Constants.ButtonSize) + } + } + + installForButton(chatButton, index: 0) + installForButton(callButton, index: 1) + installForButton(videoButton, index: 2) + } +} + +extension StaticTableChatButtonsCell { + @objc func chatButtonPressed() { + chatButtonHandler?() + } + + @objc func callButtonPressed() { + callButtonHandler?() + } + + @objc func videoButtonPressed() { + videoButtonHandler?() + } +} + +private extension StaticTableChatButtonsCell { + func createButtonWithImageName(_ imageName: String, + accessibilityLabel: String, + accessibilityHint: String, + action: Selector) -> UIButton { + let image = UIImage.templateNamed(imageName) + + let button = UIButton() + button.setImage(image, for: UIControlState()) + button.accessibilityLabel = accessibilityLabel + button.accessibilityHint = accessibilityHint + button.addTarget(self, action: action, for: .touchUpInside) + customContentView.addSubview(button) + + return button + } +} diff --git a/Antidote/StaticTableChatButtonsCellModel.swift b/Antidote/StaticTableChatButtonsCellModel.swift new file mode 100644 index 0000000..9d856cb --- /dev/null +++ b/Antidote/StaticTableChatButtonsCellModel.swift @@ -0,0 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class StaticTableChatButtonsCellModel: StaticTableBaseCellModel { + var chatButtonHandler: (() -> Void)? + var callButtonHandler: (() -> Void)? + var videoButtonHandler: (() -> Void)? + + var chatButtonEnabled: Bool = true + var callButtonEnabled: Bool = true + var videoButtonEnabled: Bool = true +} diff --git a/Antidote/StaticTableController.swift b/Antidote/StaticTableController.swift new file mode 100644 index 0000000..f6d0236 --- /dev/null +++ b/Antidote/StaticTableController.swift @@ -0,0 +1,169 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +class StaticTableController: UIViewController { + fileprivate let theme: Theme + fileprivate let tableViewStyle: UITableViewStyle + fileprivate var modelArray: [[StaticTableBaseCellModel]] + fileprivate let footerArray: [String?]? + + fileprivate var tableView: UITableView? + + init(theme: Theme, style: UITableViewStyle, model: [[StaticTableBaseCellModel]], footers: [String?]? = nil) { + self.theme = theme + self.tableViewStyle = style + self.modelArray = model + self.footerArray = footers + + super.init(nibName: nil, bundle: nil) + + edgesForExtendedLayout = UIRectEdge() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + createTableView() + + installConstraints() + } + + func reloadTableView() { + tableView?.reloadData() + } + + func updateModelArray(_ model: [[StaticTableBaseCellModel]]) { + modelArray = model + reloadTableView() + } +} + +extension StaticTableController: UITableViewDataSource { + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let model = modelArray[indexPath.section][indexPath.row] + let cell: StaticTableBaseCell + + switch model { + case _ as StaticTableButtonCellModel: + cell = dequeueCellForClass(StaticTableButtonCell.staticReuseIdentifier) + case _ as StaticTableAvatarCellModel: + cell = dequeueCellForClass(StaticTableAvatarCell.staticReuseIdentifier) + case _ as StaticTableDefaultCellModel: + cell = dequeueCellForClass(StaticTableDefaultCell.staticReuseIdentifier) + case _ as StaticTableChatButtonsCellModel: + cell = dequeueCellForClass(StaticTableChatButtonsCell.staticReuseIdentifier) + case _ as StaticTableSwitchCellModel: + cell = dequeueCellForClass(StaticTableSwitchCell.staticReuseIdentifier) + case _ as StaticTableInfoCellModel: + cell = dequeueCellForClass(StaticTableInfoCell.staticReuseIdentifier) + case _ as StaticTableMultiChoiceButtonCellModel: + cell = dequeueCellForClass(StaticTableMultiChoiceButtonCell.staticReuseIdentifier) + default: + fatalError("Static model class \(model) has not been implemented") + } + + cell.setupWithTheme(theme, model: model) + + let isLastRow = (indexPath.row == (modelArray[indexPath.section].count - 1)) + let isLastSection = (indexPath.section == (modelArray.count - 1)) + + switch tableViewStyle { + case .plain: + cell.setBottomSeparatorHidden(!isLastRow || isLastSection) + case .grouped: + cell.setBottomSeparatorHidden(isLastRow) + + case .insetGrouped: + print("error") + } + + return cell + } + + func numberOfSections(in tableView: UITableView) -> Int { + return modelArray.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return modelArray[section].count + } + + func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + guard let array = footerArray else { + return nil + } + + guard section < array.count else { + return nil + } + + return array[section] + } +} + +extension StaticTableController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + guard let cell = tableView.cellForRow(at: indexPath) as? StaticTableBaseCell else { + return + } + + let model = modelArray[indexPath.section][indexPath.row] + + switch model { + case let model as StaticTableSelectableCellModel: + model.didSelectHandler?(cell) + default: + // nop + break; + } + } +} + +private extension StaticTableController { + func createTableView() { + tableView = UITableView(frame: CGRect.zero, style: tableViewStyle) + tableView!.dataSource = self + tableView!.delegate = self + tableView!.estimatedRowHeight = 44.0; + tableView!.separatorStyle = .none; + + switch tableViewStyle { + case .plain: + tableView!.backgroundColor = theme.colorForType(.NormalBackground) + case .grouped: + tableView!.backgroundColor = theme.colorForType(.SettingsBackground) + case .insetGrouped: + print("error") + } + + view.addSubview(tableView!) + + tableView!.register(StaticTableButtonCell.self, forCellReuseIdentifier: StaticTableButtonCell.staticReuseIdentifier) + tableView!.register(StaticTableAvatarCell.self, forCellReuseIdentifier: StaticTableAvatarCell.staticReuseIdentifier) + tableView!.register(StaticTableDefaultCell.self, forCellReuseIdentifier: StaticTableDefaultCell.staticReuseIdentifier) + tableView!.register(StaticTableChatButtonsCell.self, forCellReuseIdentifier: StaticTableChatButtonsCell.staticReuseIdentifier) + tableView!.register(StaticTableSwitchCell.self, forCellReuseIdentifier: StaticTableSwitchCell.staticReuseIdentifier) + tableView!.register(StaticTableInfoCell.self, forCellReuseIdentifier: StaticTableInfoCell.staticReuseIdentifier) + tableView!.register(StaticTableMultiChoiceButtonCell.self, forCellReuseIdentifier: StaticTableMultiChoiceButtonCell.staticReuseIdentifier) + } + + func installConstraints() { + tableView!.snp.makeConstraints { + $0.edges.equalTo(view) + } + } + + func dequeueCellForClass(_ identifier: String) -> StaticTableBaseCell { + return tableView!.dequeueReusableCell(withIdentifier: identifier) as! StaticTableBaseCell + } +} diff --git a/Antidote/StaticTableDefaultCell.swift b/Antidote/StaticTableDefaultCell.swift new file mode 100644 index 0000000..2b475a3 --- /dev/null +++ b/Antidote/StaticTableDefaultCell.swift @@ -0,0 +1,258 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let EdgesVerticalOffset = 10.0 + static let TitleHeight = 20.0 + static let TitleToUserStatusOffset = 7.0 + static let TitleToValueOffset = 2.0 + static let MinValueLabelHeight = 20.0 +} + +class StaticTableDefaultCell: StaticTableBaseCell { + fileprivate var userStatusView: UserStatusView! + fileprivate var titleLabel: UILabel! + fileprivate var valueLabel: CopyLabel! + fileprivate var rightButton: UIButton! + fileprivate var rightImageView: UIImageView! + + fileprivate var userStatusViewVisibleConstraint: Constraint! + fileprivate var userStatusViewHiddenConstraint: Constraint! + + fileprivate var valueLabelToTitleConstraint: Constraint! + fileprivate var valueLabelToContentTopConstraint: Constraint! + + fileprivate var valueLabelToArrowConstraint: Constraint! + fileprivate var valueLabelToContentRightConstraint: Constraint! + + fileprivate var rightButtonHandler: (() -> Void)? + + fileprivate var checkmarkSelected: Bool = false + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let defaultModel = model as? StaticTableDefaultCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + if let userStatus = defaultModel.userStatus { + userStatusView.theme = theme + userStatusView.userStatus = userStatus + userStatusView.isHidden = false + + userStatusViewHiddenConstraint.deactivate() + userStatusViewVisibleConstraint.activate() + } + else { + userStatusView.isHidden = true + + userStatusViewVisibleConstraint.deactivate() + userStatusViewHiddenConstraint.activate() + } + + titleLabel.textColor = theme.colorForType(.LinkText) + valueLabel.textColor = theme.colorForType(.NormalText) + rightButton.setTitleColor(theme.colorForType(.LinkText), for: UIControlState()) + + titleLabel.text = defaultModel.title + valueLabel.text = defaultModel.value + valueLabel.copyable = defaultModel.canCopyValue + + rightButton.isHidden = (defaultModel.rightButton == nil) + rightButton.setTitle(defaultModel.rightButton, for: UIControlState()) + rightButtonHandler = defaultModel.rightButtonHandler + + let showRightImageView: Bool + switch defaultModel.rightImageType { + case .none: + showRightImageView = false + checkmarkSelected = false + case .arrow: + showRightImageView = true + rightImageView.image = UIImage(named: "right-arrow")!.flippedToCorrectLayout() + checkmarkSelected = false + case .checkmark: + showRightImageView = true + rightImageView.image = UIImage(named: "checkmark")! + checkmarkSelected = true + } + + if defaultModel.userInteractionEnabled { + selectionStyle = .default + } + else { + selectionStyle = .none + } + + if defaultModel.title != nil { + valueLabelToContentTopConstraint.deactivate() + valueLabelToTitleConstraint.activate() + } + else { + valueLabelToTitleConstraint.deactivate() + valueLabelToContentTopConstraint.activate() + } + + if showRightImageView { + rightImageView.isHidden = false + + valueLabelToContentRightConstraint.deactivate() + valueLabelToArrowConstraint.activate() + } + else { + rightImageView.isHidden = true + + valueLabelToArrowConstraint.deactivate() + valueLabelToContentRightConstraint.activate() + } + } + + override func createViews() { + super.createViews() + + userStatusView = UserStatusView() + userStatusView.showExternalCircle = false + customContentView.addSubview(userStatusView) + + titleLabel = UILabel() + titleLabel.font = UIFont.antidoteFontWithSize(17.0, weight: .light) + titleLabel.backgroundColor = UIColor.clear + customContentView.addSubview(titleLabel) + + valueLabel = CopyLabel() + valueLabel.numberOfLines = 0 + valueLabel.font = UIFont.systemFont(ofSize: 17.0) + valueLabel.backgroundColor = UIColor.clear + customContentView.addSubview(valueLabel) + + rightButton = UIButton() + rightButton.addTarget(self, action: #selector(StaticTableDefaultCell.rightButtonPressed), for: .touchUpInside) + customContentView.addSubview(rightButton) + + rightImageView = UIImageView() + customContentView.addSubview(rightImageView) + } + + override func installConstraints() { + super.installConstraints() + + userStatusView.snp.makeConstraints { + $0.centerY.equalTo(customContentView) + $0.leading.equalTo(customContentView) + $0.size.equalTo(UserStatusView.Constants.DefaultSize) + } + + titleLabel.snp.makeConstraints { + $0.top.equalTo(customContentView).offset(Constants.EdgesVerticalOffset) + $0.height.equalTo(Constants.TitleHeight) + + userStatusViewVisibleConstraint = $0.leading.equalTo(userStatusView.snp.trailing).offset(Constants.TitleToUserStatusOffset).constraint + } + + userStatusViewVisibleConstraint.deactivate() + + titleLabel.snp.makeConstraints { + userStatusViewHiddenConstraint = $0.leading.equalTo(customContentView).constraint + } + + valueLabel.snp.makeConstraints { + valueLabelToTitleConstraint = $0.top.equalTo(titleLabel.snp.bottom).offset(Constants.TitleToValueOffset).constraint + valueLabelToContentTopConstraint = $0.top.equalTo(customContentView).offset(Constants.EdgesVerticalOffset).constraint + + valueLabelToContentRightConstraint = $0.trailing.equalTo(customContentView).constraint + + $0.leading.equalTo(titleLabel) + $0.bottom.equalTo(customContentView).offset(-Constants.EdgesVerticalOffset) + $0.height.greaterThanOrEqualTo(Constants.MinValueLabelHeight) + } + + // TODO fix warning in log + valueLabelToTitleConstraint.deactivate() + + rightButton.snp.makeConstraints { + $0.leading.greaterThanOrEqualTo(titleLabel.snp.trailing) + $0.trailing.equalTo(customContentView) + $0.centerY.equalTo(titleLabel) + $0.bottom.lessThanOrEqualTo(customContentView) + } + + rightImageView.snp.makeConstraints { + $0.centerY.equalTo(customContentView) + $0.trailing.equalTo(customContentView) + + valueLabelToArrowConstraint = $0.leading.greaterThanOrEqualTo(valueLabel.snp.trailing).constraint + } + } +} + +// Accessibility +extension StaticTableDefaultCell { + override var isAccessibilityElement: Bool { + get { + return true + } + set {} + } + + override var accessibilityLabel: String? { + get { + return titleLabel.text ?? valueLabel.text + } + set {} + } + + override var accessibilityValue: String? { + get { + if titleLabel.text != nil { + return valueLabel.text + } + + return nil + } + set {} + } + + override var accessibilityHint: String? { + get { + if valueLabel.copyable { + return String(localized: "accessibility_show_copy_hint") + } + + if selectionStyle == .none { + return nil + } + + return String(localized: "accessibility_edit_value_hint") + } + set {} + } + + override var accessibilityTraits: UIAccessibilityTraits { + get { + if selectionStyle == .none { + return UIAccessibilityTraitStaticText + } + + var traits = UIAccessibilityTraitButton + + if checkmarkSelected { + traits |= UIAccessibilityTraitSelected + } + + return traits + } + set {} + } +} + +extension StaticTableDefaultCell { + @objc func rightButtonPressed() { + rightButtonHandler?() + } +} diff --git a/Antidote/StaticTableDefaultCellModel.swift b/Antidote/StaticTableDefaultCellModel.swift new file mode 100644 index 0000000..70d306a --- /dev/null +++ b/Antidote/StaticTableDefaultCellModel.swift @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class StaticTableDefaultCellModel: StaticTableSelectableCellModel { + enum RightImageType { + case none + case arrow + case checkmark + } + + var userStatus: UserStatus? + var connectionStatus: ConnectionStatus? + + var title: String? + var value: String? + + var rightButton: String? + var rightButtonHandler: (() -> Void)? + + var rightImageType: RightImageType = .none + + var userInteractionEnabled: Bool = true + + var canCopyValue: Bool = false +} diff --git a/Antidote/StaticTableInfoCell.swift b/Antidote/StaticTableInfoCell.swift new file mode 100644 index 0000000..b877a1f --- /dev/null +++ b/Antidote/StaticTableInfoCell.swift @@ -0,0 +1,123 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let ValueToArrowOffset = 6.0 +} + +class StaticTableInfoCell: StaticTableBaseCell { + fileprivate var valueChangedHandler: ((Bool) -> Void)? + + fileprivate var titleLabel: UILabel! + fileprivate var valueLabel: UILabel! + fileprivate var arrowImageView: UIImageView! + + fileprivate var valueLabelToContentRightConstraint: Constraint! + fileprivate var valueLabelToArrowConstraint: Constraint! + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let infoModel = model as? StaticTableInfoCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + titleLabel.textColor = theme.colorForType(.NormalText) + titleLabel.text = infoModel.title + + valueLabel.textColor = theme.colorForType(.LinkText) + valueLabel.text = infoModel.value + + + if infoModel.showArrow { + arrowImageView.isHidden = false + valueLabelToContentRightConstraint.deactivate() + valueLabelToArrowConstraint.activate() + selectionStyle = .default + } + else { + arrowImageView.isHidden = true + valueLabelToArrowConstraint.deactivate() + valueLabelToContentRightConstraint.activate() + selectionStyle = .none + } + } + + override func createViews() { + super.createViews() + + titleLabel = UILabel() + titleLabel.backgroundColor = UIColor.clear + customContentView.addSubview(titleLabel) + + valueLabel = UILabel() + valueLabel.backgroundColor = UIColor.clear + customContentView.addSubview(valueLabel) + + arrowImageView = UIImageView() + arrowImageView.image = UIImage(named: "right-arrow")!.flippedToCorrectLayout() + customContentView.addSubview(arrowImageView) + } + + override func installConstraints() { + super.installConstraints() + + titleLabel.snp.makeConstraints { + $0.centerY.equalTo(customContentView) + $0.leading.equalTo(customContentView) + } + + valueLabel.snp.makeConstraints { + $0.centerY.equalTo(customContentView) + $0.leading.greaterThanOrEqualTo(titleLabel.snp.trailing) + valueLabelToContentRightConstraint = $0.trailing.equalTo(customContentView).constraint + } + + valueLabelToContentRightConstraint.deactivate() + + arrowImageView.snp.makeConstraints { + $0.centerY.equalTo(customContentView) + $0.trailing.equalTo(customContentView) + + valueLabelToArrowConstraint = $0.leading.greaterThanOrEqualTo(valueLabel.snp.trailing).offset(Constants.ValueToArrowOffset).constraint + } + + valueLabelToArrowConstraint.deactivate() + } +} + +// Accessibility +extension StaticTableInfoCell { + override var isAccessibilityElement: Bool { + get { + return true + } + set {} + } + + override var accessibilityLabel: String? { + get { + return titleLabel.text + } + set {} + } + + override var accessibilityValue: String? { + get { + return valueLabel.text + } + set {} + } + + override var accessibilityTraits: UIAccessibilityTraits { + get { + return arrowImageView.isHidden ? UIAccessibilityTraitStaticText : UIAccessibilityTraitButton + } + set {} + } +} diff --git a/Antidote/StaticTableInfoCellModel.swift b/Antidote/StaticTableInfoCellModel.swift new file mode 100644 index 0000000..8433296 --- /dev/null +++ b/Antidote/StaticTableInfoCellModel.swift @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class StaticTableInfoCellModel: StaticTableSelectableCellModel { + var title: String? + var value: String? + + var showArrow: Bool = false +} diff --git a/Antidote/StaticTableMultiChoiceButtonCell.swift b/Antidote/StaticTableMultiChoiceButtonCell.swift new file mode 100644 index 0000000..1a05ef7 --- /dev/null +++ b/Antidote/StaticTableMultiChoiceButtonCell.swift @@ -0,0 +1,91 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let HorizontalOffset = 8.0 + static let Height = 40.0 +} + +class StaticTableMultiChoiceButtonCell: StaticTableBaseCell { + fileprivate var buttonsContainer: UIView! + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let multiModel = model as? StaticTableMultiChoiceButtonCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + selectionStyle = .none + + _ = buttonsContainer.subviews.map { + $0.removeFromSuperview() + } + + var previousButton: RoundedButton? + + for buttonModel in multiModel.buttons { + let button = addButtonWithTheme(theme, model: buttonModel) + + button.snp.makeConstraints { + $0.top.bottom.equalTo(buttonsContainer) + + if let previousButton = previousButton { + $0.leading.equalTo(previousButton.snp.trailing).offset(Constants.HorizontalOffset) + $0.width.equalTo(previousButton) + } + else { + $0.leading.equalTo(buttonsContainer) + } + } + previousButton = button + } + + if let previousButton = previousButton { + previousButton.snp.makeConstraints { + $0.trailing.equalTo(buttonsContainer) + } + } + } + + override func createViews() { + super.createViews() + + buttonsContainer = UIView() + buttonsContainer.backgroundColor = .clear + customContentView.addSubview(buttonsContainer) + } + + override func installConstraints() { + super.installConstraints() + + buttonsContainer.snp.makeConstraints { + $0.leading.trailing.equalTo(customContentView) + $0.centerY.equalTo(customContentView) + $0.height.equalTo(Constants.Height) + } + } + + func addButtonWithTheme(_ theme: Theme, model: StaticTableMultiChoiceButtonCellModel.ButtonModel) -> RoundedButton { + let type: RoundedButton.ButtonType + + switch model.style { + case .negative: + type = .runningNegative + case .positive: + type = .runningPositive + } + + let button = RoundedButton(theme: theme, type: type) + button.setTitle(model.title, for: UIControlState()) + button.addTarget(model.target, action: model.action, for: .touchUpInside) + buttonsContainer.addSubview(button) + + return button + } +} diff --git a/Antidote/StaticTableMultiChoiceButtonCellModel.swift b/Antidote/StaticTableMultiChoiceButtonCellModel.swift new file mode 100644 index 0000000..d899ef0 --- /dev/null +++ b/Antidote/StaticTableMultiChoiceButtonCellModel.swift @@ -0,0 +1,21 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class StaticTableMultiChoiceButtonCellModel: StaticTableBaseCellModel { + enum ButtonStyle { + case negative + case positive + } + + struct ButtonModel { + let title: String + let style: ButtonStyle + let target: AnyObject? + let action: Selector + } + + var buttons = [ButtonModel]() +} diff --git a/Antidote/StaticTableSelectableCellModel.swift b/Antidote/StaticTableSelectableCellModel.swift new file mode 100644 index 0000000..389e58a --- /dev/null +++ b/Antidote/StaticTableSelectableCellModel.swift @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class StaticTableSelectableCellModel: StaticTableBaseCellModel { + var didSelectHandler: ((StaticTableBaseCell) -> Void)? +} diff --git a/Antidote/StaticTableSwitchCell.swift b/Antidote/StaticTableSwitchCell.swift new file mode 100644 index 0000000..ceb8ce1 --- /dev/null +++ b/Antidote/StaticTableSwitchCell.swift @@ -0,0 +1,122 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +class StaticTableSwitchCell: StaticTableBaseCell { + fileprivate var valueChangedHandler: ((Bool) -> Void)? + + fileprivate var titleLabel: UILabel! + fileprivate var accessibilityButton: UIButton! + fileprivate var switchView: UISwitch! + + override func setupWithTheme(_ theme: Theme, model: BaseCellModel) { + super.setupWithTheme(theme, model: model) + + guard let switchModel = model as? StaticTableSwitchCellModel else { + assert(false, "Wrong model \(model) passed to cell \(self)") + return + } + + selectionStyle = .none + + titleLabel.textColor = theme.colorForType(.NormalText) + titleLabel.text = switchModel.title + + switchView.isEnabled = switchModel.enabled + switchView.tintColor = theme.colorForType(.LinkText) + switchView.isOn = switchModel.on + + valueChangedHandler = switchModel.valueChangedHandler + } + + override func createViews() { + super.createViews() + + titleLabel = UILabel() + titleLabel.backgroundColor = UIColor.clear + customContentView.addSubview(titleLabel) + + accessibilityButton = UIButton() + accessibilityButton.addTarget(self, + action: #selector(StaticTableSwitchCell.accessibilityButtonPressed), + for: .touchUpInside) + customContentView.addSubview(accessibilityButton) + + switchView = UISwitch() + switchView.addTarget(self, action: #selector(StaticTableSwitchCell.switchValueChanged), for: .valueChanged) + customContentView.addSubview(switchView) + } + + override func installConstraints() { + super.installConstraints() + + titleLabel.snp.makeConstraints { + $0.centerY.equalTo(customContentView) + $0.leading.equalTo(customContentView) + } + + accessibilityButton.snp.makeConstraints { + $0.edges.equalTo(customContentView) + } + + switchView.snp.makeConstraints { + $0.centerY.equalTo(customContentView) + $0.leading.greaterThanOrEqualTo(titleLabel.snp.trailing) + $0.trailing.equalTo(customContentView) + } + } +} + +// Accessibility +extension StaticTableSwitchCell { + override var isAccessibilityElement: Bool { + get { + return true + } + set {} + } + + override var accessibilityLabel: String? { + get { + return titleLabel.text + } + set {} + } + + override var accessibilityValue: String? { + get { + return switchView.accessibilityValue + } + set {} + } + + override var accessibilityHint: String? { + get { + return switchView.accessibilityHint + } + set {} + } + + override var accessibilityTraits: UIAccessibilityTraits { + get { + return switchView.accessibilityTraits + } + set {} + } +} + +extension StaticTableSwitchCell { + @objc func accessibilityButtonPressed() { + if UIAccessibilityIsVoiceOverRunning() { + switchView.isOn = !switchView.isOn + switchValueChanged() + } + } + + @objc func switchValueChanged() { + valueChangedHandler?(switchView.isOn) + } +} diff --git a/Antidote/StaticTableSwitchCellModel.swift b/Antidote/StaticTableSwitchCellModel.swift new file mode 100644 index 0000000..5e60937 --- /dev/null +++ b/Antidote/StaticTableSwitchCellModel.swift @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class StaticTableSwitchCellModel: StaticTableBaseCellModel { + var title: String? + var on: Bool = false + + var valueChangedHandler: ((Bool) -> Void)? + + var enabled: Bool = true +} diff --git a/Antidote/StringExtension.swift b/Antidote/StringExtension.swift new file mode 100644 index 0000000..08e5a3a --- /dev/null +++ b/Antidote/StringExtension.swift @@ -0,0 +1,70 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +extension String { + init(timeInterval: TimeInterval) { + var timeInterval = timeInterval + + let hours = Int(timeInterval / 3600) + timeInterval -= TimeInterval(hours * 3600) + + let minutes = Int(timeInterval / 60) + timeInterval -= TimeInterval(minutes * 60) + + let seconds = Int(timeInterval) + + if hours > 0 { + self.init(format: "%02d:%02d:%02d", hours, minutes, seconds) + } + else { + self.init(format: "%02d:%02d", minutes, seconds) + } + } + + init(localized: String, _ arguments: CVarArg...) { + let format = NSLocalizedString(localized, tableName: nil, bundle: Bundle.main, value: "", comment: "") + self.init(format: format, arguments: arguments) + } + + init(localized: String, comment: String, _ arguments: CVarArg...) { + let format = NSLocalizedString(localized, tableName: nil, bundle: Bundle.main, value: "", comment: comment) + self.init(format: format, arguments: arguments) + } + + func substringToByteLength(_ length: Int, encoding: String.Encoding) -> String { + guard length > 0 else { + return "" + } + + var substring = self as NSString + + while substring.lengthOfBytes(using: encoding.rawValue) > length { + let newLength = substring.length - 1 + + guard newLength > 0 else { + return "" + } + + substring = substring.substring(to: newLength) as NSString + } + + return substring as String + } + + func stringSizeWithFont(_ font: UIFont) -> CGSize { + return stringSizeWithFont(font, constrainedToSize:CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)) + } + + func stringSizeWithFont(_ font: UIFont, constrainedToSize size: CGSize) -> CGSize { + let boundingRect = (self as NSString).boundingRect( + with: size, + options: .usesLineFragmentOrigin, + attributes: [NSAttributedStringKey.font : font], + context: nil) + + return CGSize(width: ceil(boundingRect.size.width), height: ceil(boundingRect.size.height)) + } +} diff --git a/Antidote/TabBarAbstractItem.swift b/Antidote/TabBarAbstractItem.swift new file mode 100644 index 0000000..192ed8d --- /dev/null +++ b/Antidote/TabBarAbstractItem.swift @@ -0,0 +1,33 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +class TabBarAbstractItem: UIView { + var selected: Bool = false + var didTapHandler: (() -> Void)? +} + +// Accessibility +extension TabBarAbstractItem { + override var isAccessibilityElement: Bool { + get { + return true + } + set {} + } + + override var accessibilityTraits: UIAccessibilityTraits { + get { + var value = UIAccessibilityTraitButton + + if selected { + value |= UIAccessibilityTraitSelected + } + + return value + } + set {} + } +} diff --git a/Antidote/TabBarBadgeItem.swift b/Antidote/TabBarBadgeItem.swift new file mode 100644 index 0000000..6561286 --- /dev/null +++ b/Antidote/TabBarBadgeItem.swift @@ -0,0 +1,184 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let ImageAndTextContainerYOffset = 2.0 + static let ImageAndTextOffset = 3.0 + + static let BadgeTopOffset = -5.0 + static let BadgeHorizontalOffset = 5.0 + static let BadgeMinimumWidth = 22.0 + static let BadgeHeight: CGFloat = 18.0 +} + +class TabBarBadgeItem: TabBarAbstractItem { + override var selected: Bool { + didSet { + let color = theme.colorForType(selected ? .TabItemActive : .TabItemInactive) + + textLabel.textColor = color + imageView.tintColor = color + } + } + + var image: UIImage? { + didSet { + imageView.image = image?.withRenderingMode(.alwaysTemplate) + } + } + var text: String? { + didSet { + textLabel.text = text + } + } + + var badgeText: String? { + didSet { + badgeTextWasUpdated() + } + } + + /// If there is any badge text, accessibilityValue will be set to + var badgeAccessibilityEnding: String? + + fileprivate let theme: Theme + + fileprivate var imageAndTextContainer: UIView! + fileprivate var imageView: UIImageView! + fileprivate var textLabel: UILabel! + + fileprivate var badgeContainer: UIView! + fileprivate var badgeLabel: UILabel! + + fileprivate var button: UIButton! + + init(theme: Theme) { + self.theme = theme + + super.init(frame: CGRect.zero) + + backgroundColor = .clear + + createViews() + installConstraints() + + badgeTextWasUpdated() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// Accessibility +extension TabBarBadgeItem { + override var accessibilityLabel: String? { + get { + return text + } + set {} + } + + override var accessibilityValue: String? { + get { + guard var result = badgeText else { + return nil + } + + if let ending = badgeAccessibilityEnding { + result += " " + ending + } + + return result + } + set {} + } +} + +// Actions +extension TabBarBadgeItem { + @objc func buttonPressed() { + didTapHandler?() + } +} + +private extension TabBarBadgeItem { + func createViews() { + imageAndTextContainer = UIView() + imageAndTextContainer.backgroundColor = .clear + addSubview(imageAndTextContainer) + + imageView = UIImageView() + imageView.backgroundColor = .clear + imageAndTextContainer.addSubview(imageView) + + textLabel = UILabel() + textLabel.textColor = theme.colorForType(.NormalText) + textLabel.textAlignment = .center + textLabel.backgroundColor = .clear + textLabel.font = UIFont.systemFont(ofSize: 10.0) + imageAndTextContainer.addSubview(textLabel) + + badgeContainer = UIView() + badgeContainer.backgroundColor = theme.colorForType(.TabBadgeBackground) + badgeContainer.layer.masksToBounds = true + badgeContainer.layer.cornerRadius = Constants.BadgeHeight / 2 + addSubview(badgeContainer) + + badgeLabel = UILabel() + badgeLabel.textColor = theme.colorForType(.TabBadgeText) + badgeLabel.textAlignment = .center + badgeLabel.backgroundColor = .clear + badgeLabel.font = UIFont.antidoteFontWithSize(14.0, weight: .light) + badgeContainer.addSubview(badgeLabel) + + button = UIButton() + button.backgroundColor = .clear + button.addTarget(self, action: #selector(TabBarBadgeItem.buttonPressed), for: .touchUpInside) + addSubview(button) + } + + func installConstraints() { + imageAndTextContainer.snp.makeConstraints { + $0.centerX.equalTo(self) + $0.centerY.equalTo(self).offset(Constants.ImageAndTextContainerYOffset) + } + + imageView.snp.makeConstraints { + $0.top.equalTo(imageAndTextContainer) + $0.centerX.equalTo(imageAndTextContainer) + } + + textLabel.snp.makeConstraints { + $0.top.equalTo(imageView.snp.bottom).offset(Constants.ImageAndTextOffset) + $0.centerX.equalTo(imageAndTextContainer) + $0.bottom.equalTo(imageAndTextContainer) + } + + badgeContainer.snp.makeConstraints { + $0.leading.equalTo(imageAndTextContainer.snp.leading) + $0.top.equalTo(imageAndTextContainer.snp.top).offset(Constants.BadgeTopOffset) + $0.width.greaterThanOrEqualTo(Constants.BadgeMinimumWidth) + $0.height.equalTo(Constants.BadgeHeight) + } + + badgeLabel.snp.makeConstraints { + $0.leading.equalTo(badgeContainer).offset(Constants.BadgeHorizontalOffset) + $0.trailing.equalTo(badgeContainer).offset(-Constants.BadgeHorizontalOffset) + $0.centerY.equalTo(badgeContainer) + } + + button.snp.makeConstraints { + $0.edges.equalTo(self) + } + } + + func badgeTextWasUpdated() { + badgeLabel.text = badgeText + badgeContainer.isHidden = (badgeText == nil) || badgeText!.isEmpty + } +} diff --git a/Antidote/TabBarController.swift b/Antidote/TabBarController.swift new file mode 100644 index 0000000..890e0ad --- /dev/null +++ b/Antidote/TabBarController.swift @@ -0,0 +1,163 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let HorizontalLineHeight = 0.5 +} + +class TabBarController: UITabBarController { + override var selectedIndex: Int { + didSet { + guard let navigation = viewControllers?[selectedIndex] as? UINavigationController else { + return + } + + navigation.delegate = self + + if oldValue == selectedIndex { + navigation.popToRootViewController(animated: true) + } + + updateSelectedItems() + } + } + + fileprivate let items: [TabBarAbstractItem] + + fileprivate let theme: Theme + + fileprivate var customTabBarView: UIView! + + fileprivate var customTabBarViewVisibleConstraint: Constraint! + fileprivate var customTabBarViewHiddenConstraint: Constraint! + + init(theme: Theme, controllers: [UINavigationController], tabBarItems: [TabBarAbstractItem]) { + self.theme = theme + self.items = tabBarItems + + super.init(nibName: nil, bundle: nil) + + viewControllers = controllers + + delegate = self + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + createCustomTabBarView() + addItems() + installConstraints() + + updateSelectedItems() + } +} + +extension TabBarController: UITabBarControllerDelegate { + func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { + guard let navigation = viewController as? UINavigationController else { + return + } + + navigation.delegate = self + } +} + +extension TabBarController: UINavigationControllerDelegate { + func navigationController( + _ navigationController: UINavigationController, + willShow viewController: UIViewController, + animated: Bool) { + tabBar.isHidden = true + + if viewController.hidesBottomBarWhenPushed { + customTabBarViewVisibleConstraint.deactivate() + customTabBarViewHiddenConstraint.activate() + } + else { + customTabBarViewHiddenConstraint.deactivate() + customTabBarViewVisibleConstraint.activate() + } + } +} + +extension TabBarController { + func createCustomTabBarView() { + customTabBarView = UIView() + customTabBarView.backgroundColor = theme.colorForType(.NormalBackground) + view.addSubview(customTabBarView) + + let horizontalLine = UIView() + horizontalLine.backgroundColor = theme.colorForType(.SeparatorsAndBorders) + customTabBarView.addSubview(horizontalLine) + + horizontalLine.snp.makeConstraints { + $0.top.leading.trailing.equalTo(customTabBarView) + $0.height.equalTo(Constants.HorizontalLineHeight) + } + } + + func addItems() { + for (index, item) in items.enumerated() { + item.didTapHandler = { [weak self] in + self?.selectedIndex = index + } + + customTabBarView.addSubview(item) + } + } + + func installConstraints() { + customTabBarView.snp.makeConstraints { + customTabBarViewVisibleConstraint = $0.bottom.equalTo(view.snp.bottom).constraint + customTabBarViewHiddenConstraint = $0.top.equalTo(view.snp.bottom).constraint + $0.leading.trailing.equalTo(view) + $0.height.equalTo(tabBar.frame.size.height) + // TODO: this moves the view a bit more to the top, because the home button "line" is in the way otherwise + // please fix me properly in the future + if #available(iOS 11.0, *) { + let keyWindow = UIApplication.shared.keyWindow + let b = keyWindow?.safeAreaInsets.bottom + customTabBarViewVisibleConstraint?.update(offset: -(b ?? 20)) + } + + } + + customTabBarViewHiddenConstraint.deactivate() + + var previous: TabBarAbstractItem? + + for item in items { + item.snp.makeConstraints { + $0.top.bottom.equalTo(customTabBarView) + + if previous != nil { + $0.leading.equalTo(previous!.snp.trailing) + $0.width.equalTo(previous!) + } + else { + $0.leading.equalTo(customTabBarView) + } + } + + previous = item + } + previous!.snp.makeConstraints { + $0.trailing.equalTo(customTabBarView) + } + } + + func updateSelectedItems() { + for (index, item) in items.enumerated() { + item.selected = (index == selectedIndex) + } + } +} diff --git a/Antidote/TabBarProfileItem.swift b/Antidote/TabBarProfileItem.swift new file mode 100644 index 0000000..88dbcd4 --- /dev/null +++ b/Antidote/TabBarProfileItem.swift @@ -0,0 +1,110 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +import SnapKit + +private struct Constants { + static let ImageSize = 32.0 +} + +class TabBarProfileItem: TabBarAbstractItem { + override var selected: Bool { + didSet { + imageViewWithStatus.imageView.tintColor = theme.colorForType(selected ? .TabItemActive : .TabItemInactive) + } + } + + var userStatus: UserStatus = .offline { + didSet { + imageViewWithStatus.userStatusView.userStatus = userStatus + } + } + + var connectionStatus: ConnectionStatus = .none { + didSet { + imageViewWithStatus.userStatusView.connectionStatus = connectionStatus + } + } + + var userImage: UIImage? { + didSet { + if let image = userImage { + imageViewWithStatus.imageView.image = image + } + else { + imageViewWithStatus.imageView.image = UIImage.templateNamed("tab-bar-profile") + } + } + } + + fileprivate let theme: Theme + + fileprivate var imageViewWithStatus: ImageViewWithStatus! + fileprivate var button: UIButton! + + init(theme: Theme) { + self.theme = theme + + super.init(frame: CGRect.zero) + + backgroundColor = .clear + + createViews() + installConstraints() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// Accessibility +extension TabBarProfileItem { + override var accessibilityLabel: String? { + get { + return String(localized: "profile_title") + } + set {} + } + + override var accessibilityValue: String? { + get { + return userStatus.toString() + } + set {} + } +} + +// Actions +extension TabBarProfileItem { + @objc func buttonPressed() { + didTapHandler?() + } +} + +private extension TabBarProfileItem { + func createViews() { + imageViewWithStatus = ImageViewWithStatus() + imageViewWithStatus.userStatusView.theme = theme + addSubview(imageViewWithStatus) + + button = UIButton() + button.backgroundColor = .clear + button.addTarget(self, action: #selector(TabBarProfileItem.buttonPressed), for: .touchUpInside) + addSubview(button) + } + + func installConstraints() { + imageViewWithStatus.snp.makeConstraints { + $0.center.equalTo(self) + $0.size.equalTo(Constants.ImageSize) + } + + button.snp.makeConstraints { + $0.edges.equalTo(self) + } + } +} + diff --git a/Antidote/TextEditController.swift b/Antidote/TextEditController.swift new file mode 100644 index 0000000..9af31cc --- /dev/null +++ b/Antidote/TextEditController.swift @@ -0,0 +1,93 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let Offset = 20.0 + static let FieldHeight = 40.0 +} + +class TextEditController: UIViewController { + fileprivate let theme: Theme + + fileprivate let defaultValue: String? + fileprivate let changeTextHandler: (String) -> Void + fileprivate let userFinishedEditing: () -> Void + + fileprivate var textField: UITextField! + + /** + Creates controller for editing single text field. + + - Parameters: + - theme: Theme controller will use. + - changeTextHandler: Handler called when user have changed the text. + - userFinishedEditing: Handler called when user have finished editing. + */ + init(theme: Theme, title: String, defaultValue: String?, changeTextHandler: @escaping (String) -> Void, userFinishedEditing: @escaping () -> Void) { + self.theme = theme + self.defaultValue = defaultValue + self.changeTextHandler = changeTextHandler + self.userFinishedEditing = userFinishedEditing + + super.init(nibName: nil, bundle: nil) + + self.title = title + edgesForExtendedLayout = UIRectEdge() + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(theme.colorForType(.NormalBackground)) + + createTextField() + installConstraints() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + textField.becomeFirstResponder() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + changeTextHandler(textField.text ?? "") + } +} + +extension TextEditController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + changeTextHandler(textField.text ?? "") + userFinishedEditing() + + return false + } +} + +private extension TextEditController { + func createTextField() { + textField = UITextField() + textField.text = defaultValue + textField.delegate = self + textField.returnKeyType = .done + textField.borderStyle = .roundedRect + view.addSubview(textField) + } + + func installConstraints() { + textField.snp.makeConstraints { + $0.top.equalTo(view).offset(Constants.Offset) + $0.leading.equalTo(view).offset(Constants.Offset) + $0.trailing.equalTo(view).offset(-Constants.Offset) + $0.height.equalTo(Constants.FieldHeight) + } + } +} diff --git a/Antidote/TextViewController.swift b/Antidote/TextViewController.swift new file mode 100644 index 0000000..f69d492 --- /dev/null +++ b/Antidote/TextViewController.swift @@ -0,0 +1,82 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let Offset = 10.0 + static let TitleColorKey = "TITLE_COLOR" + static let TextColorKey = "TEXT_COLOR" +} + +class TextViewController: UIViewController { + fileprivate let resourceName: String + fileprivate let backgroundColor: UIColor + fileprivate let titleColor: UIColor + fileprivate let textColor: UIColor + + fileprivate var textView: UITextView! + + init(resourceName: String, backgroundColor: UIColor, titleColor: UIColor, textColor: UIColor) { + self.resourceName = resourceName + self.backgroundColor = backgroundColor + self.titleColor = titleColor + self.textColor = textColor + + super.init(nibName: nil, bundle: nil) + } + + required convenience init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + loadViewWithBackgroundColor(backgroundColor) + + createTextView() + installConstraints() + + loadHtml() + } +} + +private extension TextViewController { + func createTextView() { + textView = UITextView() + textView.isEditable = false + textView.backgroundColor = .clear + view.addSubview(textView) + } + + func installConstraints() { + textView.snp.makeConstraints { + $0.leading.top.equalTo(view).offset(Constants.Offset) + $0.trailing.bottom.equalTo(view).offset(-Constants.Offset) + } + } + + func loadHtml() { + do { + struct FakeError: Error {} + guard let htmlFilePath = Bundle.main.path(forResource: resourceName, ofType: "html") else { + throw FakeError() + } + + var htmlString = try NSString(contentsOfFile: htmlFilePath, encoding: String.Encoding.utf8.rawValue) + htmlString = htmlString.replacingOccurrences(of: Constants.TitleColorKey, with: titleColor.hexString()) as NSString + htmlString = htmlString.replacingOccurrences(of: Constants.TextColorKey, with: textColor.hexString()) as NSString + + guard let data = htmlString.data(using: String.Encoding.unicode.rawValue) else { + throw FakeError() + } + let options = [ NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html ] + + try textView.attributedText = NSAttributedString(data: data, options: options, documentAttributes: nil) + } + catch { + handleErrorWithType(.cannotLoadHTML) + } + } +} diff --git a/Antidote/Theme.swift b/Antidote/Theme.swift new file mode 100644 index 0000000..c6ca205 --- /dev/null +++ b/Antidote/Theme.swift @@ -0,0 +1,242 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import Yaml + +enum ErrorTheme: Error { + case cannotParseFile(String) + case wrongVersion(String) + + func debugDescription() -> String { + switch self { + case .cannotParseFile(let string): + return "Parse error: \(string)" + case .wrongVersion(let string): + return "Version error: \(string)" + } + } +} + +class Theme { + enum ColorType: String { + case LoginBackground = "login-background" + case LoginGradient = "login-gradient" + case LoginToxLogo = "login-tox-logo" + case LoginButtonText = "login-button-text" + case LoginButtonBackground = "login-button-background" + case LoginDescriptionLabel = "login-description-label" + case LoginFormBackground = "login-form-background" + case LoginFormText = "login-form-text" + case LoginLinkColor = "login-link-color" + + case TranslucentBackground = "translucent-background" + + case NormalBackground = "normal-background" + case NormalText = "normal-text" + case LinkText = "link-text" + case ConnectingBackground = "connecting-background" + case ConnectingText = "connecting-text" + case SeparatorsAndBorders = "separators-and-borders" + case OfflineStatus = "offline-status" + case OnlineStatus = "online-status" + case AwayStatus = "away-status" + case BusyStatus = "busy-status" + case StatusBackground = "status-background" + case FriendCellStatus = "friend-cell-status" + case ChatListCellMessage = "chat-list-cell-message" + case ChatListCellUnreadBackground = "chat-list-cell-unread-background" + case ChatInputBackground = "chat-input-background" + case ChatIncomingBubble = "chat-incoming-bubble" + case ChatOutgoingBubble = "chat-outgoing-bubble" + case ChatOutgoingUnreadBubble = "chat-outgoing-unread-bubble" + case ChatOutgoingSentPushBubble = "chat-outgoing-sentpush-bubble" + case ChatInformationText = "chat-information-text" + case TabBadgeBackground = "tab-badge-background" + case TabBadgeText = "tab-badge-text" + case TabItemActive = "tab-item-active" + case TabItemInactive = "tab-item-inactive" + case NotificationBackground = "notification-background" + case NotificationText = "notification-text" + case SettingsBackground = "settings-background" + case CallTextColor = "call-text-color" + case CallDeclineButtonBackground = "call-decline-button-background" + case CallAnswerButtonBackground = "call-answer-button-background" + case CallControlSelectedBackground = "call-control-selected-background" + case CallControlBackground = "call-control-background" + case CallButtonIconColor = "call-button-icon-color" + case CallButtonSelectedIconColor = "call-button-selected-icon-color" + case CallVideoPreviewBackground = "call-video-preview-background" + case RoundedButtonText = "rounded-button-text" + case RoundedPositiveButtonBackground = "rounded-positive-button-background" + case RoundedNegativeButtonBackground = "rounded-negative-button-background" + case EmptyScreenPlaceholderText = "empty-screen-placeholder-text" + case FileImageBackgroundActive = "file-image-background-active" + case FileImageCancelledText = "file-image-cancelled-text" + case FileImageAcceptButtonTint = "file-image-accept-button-tint" + case FileImageCancelButtonTint = "file-image-cancel-button-tint" + case LockGradientTop = "lock-gradient-top" + case LockGradientBottom = "lock-gradient-bottom" + case ChatListCellUnreadArrowBackground = "chat-list-cell-arrow-unread-background" + + // Because enums don't support enumerations we have to do this hack. Phew. + static let allValues = [ + LoginBackground, + LoginGradient, + LoginToxLogo, + LoginButtonText, + LoginButtonBackground, + LoginDescriptionLabel, + LoginFormBackground, + LoginFormText, + LoginLinkColor, + TranslucentBackground, + NormalBackground, + NormalText, + LinkText, + ConnectingBackground, + ConnectingText, + SeparatorsAndBorders, + OfflineStatus, + OnlineStatus, + AwayStatus, + BusyStatus, + StatusBackground, + FriendCellStatus, + ChatListCellMessage, + ChatListCellUnreadBackground, + ChatInputBackground, + ChatIncomingBubble, + ChatOutgoingBubble, + ChatOutgoingUnreadBubble, + ChatOutgoingSentPushBubble, + ChatInformationText, + TabBadgeBackground, + TabBadgeText, + TabItemActive, + TabItemInactive, + NotificationBackground, + NotificationText, + SettingsBackground, + CallTextColor, + CallDeclineButtonBackground, + CallAnswerButtonBackground, + CallControlBackground, + CallControlSelectedBackground, + CallButtonIconColor, + CallButtonSelectedIconColor, + CallVideoPreviewBackground, + RoundedButtonText, + RoundedPositiveButtonBackground, + RoundedNegativeButtonBackground, + EmptyScreenPlaceholderText, + FileImageBackgroundActive, + FileImageCancelledText, + FileImageAcceptButtonTint, + FileImageCancelButtonTint, + LockGradientTop, + LockGradientBottom, + ChatListCellUnreadArrowBackground, + ] + } + + init(yamlString: String) throws { + guard let dictionary = try Yaml.load(yamlString).dictionary else { + throw ErrorTheme.cannotParseFile(String(localized:"theme_error_cannot_open")) + } + + try checkVersion(dictionary) + + mappedColors = try createMappedColors(fromDictionary: dictionary) + try validateMappedColors(mappedColors) + } + + func colorForType(_ type: ColorType) -> UIColor { + return mappedColors[type.rawValue]! + } + + var loginNavigationBarColor: UIColor { + // https://developer.apple.com/library/ios/qa/qa1808/_index.html + let colorDelta: CGFloat = 0.08 + + var (red, green, blue, alpha) = colorForType(.LoginButtonBackground).components() + + red = max(0.0, red - colorDelta) + green = max(0.0, green - colorDelta) + blue = max(0.0, blue - colorDelta) + + return UIColor(red: red, green: green, blue: blue, alpha: alpha) + } + + fileprivate var mappedColors: [String: UIColor]! +} + +private extension Theme { + struct Constants { + static let VersionValue = 1 + static let VersionKey = "version" + static let ColorsKey = "colors" + static let ValuesKey = "values" + } + + func checkVersion(_ dictionary: [Yaml: Yaml]) throws { + guard let version = dictionary[Yaml.string(Constants.VersionKey)]?.int else { + throw ErrorTheme.cannotParseFile(String(localized:"theme_error_cannot_open")) + } + + guard version == Constants.VersionValue else { + throw ErrorTheme.wrongVersion(String(localized: "theme_error_cannot_open")) + } + } + + func createMappedColors(fromDictionary dictionary: [Yaml: Yaml]) throws -> [String: UIColor] { + let colorsDict = try parseDictionary(dictionary, forKey: Constants.ColorsKey) { (string: String) -> UIColor? in + return UIColor(hexString: string) + } + let valuesDict = try parseDictionary(dictionary, forKey: Constants.ValuesKey) { (string: String) -> String? in + return string + } + + var mappedColors = [String: UIColor]() + + for (key, value) in valuesDict { + guard let color = colorsDict[value] else { + throw ErrorTheme.cannotParseFile(String(localized: "theme_error_cannot_open", value)) + } + + mappedColors[key] = color + } + + return mappedColors + } + + func parseDictionary(_ dictionary: [Yaml: Yaml], forKey key: String, modifyValue: (String) -> T?) throws -> [String: T] { + guard let yamlDict = dictionary[Yaml.string(key)]?.dictionary else { + throw ErrorTheme.cannotParseFile(String(localized: "theme_error_cannot_open", key)) + } + + var resultDict = [String: T]() + + for (keyYaml, valueYaml) in yamlDict { + guard let key = keyYaml.string, + let originalValue = valueYaml.string, + let valueToSet = modifyValue(originalValue) else { + throw ErrorTheme.cannotParseFile(String(localized: "theme_error_cannot_open", keyYaml.description, valueYaml.description)) + } + + resultDict[key] = valueToSet + } + + return resultDict + } + + func validateMappedColors(_ dictionary: [String: UIColor]) throws { + for type in ColorType.allValues { + guard let _ = dictionary[type.rawValue] else { + throw ErrorTheme.cannotParseFile(String(localized: "theme_error_cannot_open", type.rawValue)) + } + } + } +} + diff --git a/Antidote/TopCoordinatorProtocol.swift b/Antidote/TopCoordinatorProtocol.swift new file mode 100644 index 0000000..c1e6351 --- /dev/null +++ b/Antidote/TopCoordinatorProtocol.swift @@ -0,0 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +protocol TopCoordinatorProtocol: CoordinatorProtocol { + /** + Handle local notification. + + - Parameters: + - notification: Notification to handle + */ + func handleLocalNotification(_ notification: UILocalNotification) + + /** + Handle openURL request. + + - Parameters: + - url: URL to handle. + */ + func handleInboxURL(_ url: URL) +} diff --git a/Antidote/ToxFactory.swift b/Antidote/ToxFactory.swift new file mode 100644 index 0000000..0f646f0 --- /dev/null +++ b/Antidote/ToxFactory.swift @@ -0,0 +1,22 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +struct ToxFactory { + static func createToxWithConfiguration(_ configuration: OCTManagerConfiguration, + encryptPassword: String, + successBlock: @escaping (OCTManager) -> Void, + failureBlock: @escaping (Error) -> Void) { + if ProcessInfo.processInfo.arguments.contains("UI_TESTING") { + successBlock(OCTManagerMock()) + return + } + + OCTManagerFactory.manager(with: configuration, + encryptPassword: encryptPassword, + successBlock: successBlock, + failureBlock: failureBlock) + } +} diff --git a/Antidote/UIAlertControllerExtension.swift b/Antidote/UIAlertControllerExtension.swift new file mode 100644 index 0000000..c0d1385 --- /dev/null +++ b/Antidote/UIAlertControllerExtension.swift @@ -0,0 +1,63 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +extension UIAlertController { + class func showErrorWithMessage(_ message: String, retryBlock: (() -> Void)?) { + showWithTitle(String(localized: "error_title"), message: message, retryBlock: retryBlock) + } + + class func showWithTitle(_ title: String, message: String? = nil, retryBlock: (() -> Void)?) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + + + if let retryBlock = retryBlock { + alert.addAction(UIAlertAction(title: String(localized: "error_retry_button"), style: .default) { _ in + retryBlock() + }) + + alert.addAction(UIAlertAction(title: String(localized: "alert_cancel"), style: .cancel, handler: nil)) + } + else { + alert.addAction(UIAlertAction(title: String(localized: "error_ok_button"), style: .cancel, handler: nil)) + } + + guard let visible = visibleViewController() else { + return + } + + visible.present(alert, animated: true, completion: nil) + } + + fileprivate class func visibleViewController(_ rootViewController: UIViewController? = nil) -> UIViewController? { + var root: UIViewController + + if let rootViewController = rootViewController { + root = rootViewController + } + else { + let appDelegate = UIApplication.shared.delegate as? AppDelegate + + guard let controller = appDelegate?.window?.rootViewController else { + return nil + } + + root = controller + } + + guard let presented = root.presentedViewController else { + return root + } + + if let navigation = presented as? UINavigationController { + return visibleViewController(navigation.topViewController) + } + if let tabBar = presented as? UITabBarController { + return visibleViewController(tabBar.selectedViewController) + } + + return presented + } +} diff --git a/Antidote/UIApplicationExtension.swift b/Antidote/UIApplicationExtension.swift new file mode 100644 index 0000000..7a4d5fa --- /dev/null +++ b/Antidote/UIApplicationExtension.swift @@ -0,0 +1,20 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +extension UIApplication { + class var isActive: Bool { + get { + switch shared.applicationState { + case .active: + return true + case .inactive: + return false + case .background: + return false + } + } + } +} diff --git a/Antidote/UIColorExtension.swift b/Antidote/UIColorExtension.swift new file mode 100644 index 0000000..dc0235b --- /dev/null +++ b/Antidote/UIColorExtension.swift @@ -0,0 +1,59 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +extension UIColor { + convenience init?(hexString: String) { + var red: CGFloat = 0.0 + var green: CGFloat = 0.0 + var blue: CGFloat = 0.0 + var alpha: CGFloat = 1.0 + + guard let number = CLongLong(hexString, radix: 16) else { + return nil + } + + switch(hexString.count) { + case 6: + red = CGFloat((number & 0xFF0000) >> 16) / 255.0 + green = CGFloat((number & 0x00FF00) >> 8) / 255.0 + blue = CGFloat((number & 0x0000FF) >> 0) / 255.0 + case 8: + red = CGFloat((number & 0xFF000000) >> 24) / 255.0 + green = CGFloat((number & 0x00FF0000) >> 16) / 255.0 + blue = CGFloat((number & 0x0000FF00) >> 8) / 255.0 + alpha = CGFloat((number & 0x000000FF) >> 0) / 255.0 + default: + return nil + } + + self.init(red: red, green: green, blue: blue, alpha: alpha) + } + + func components() -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { + var red: CGFloat = 0.0 + var green: CGFloat = 0.0 + var blue: CGFloat = 0.0 + var alpha: CGFloat = 0.0 + + getRed(&red, green: &green, blue: &blue, alpha: &alpha) + + return (red, green, blue, alpha) + } + + func hexString() -> String { + let (red, green, blue, _) = components() + + return String(format: "%02x%02x%02x", Int(255 * red), Int(255 * green), Int(255 * blue)) + } + + func darkerColor() -> UIColor { + let (red, green, blue, alpha) = components() + let delta: CGFloat = 0.1 + + return UIColor(red: max(red - delta, 0.0), green: max(green - delta, 0.0), blue: max(blue - delta, 0.0), alpha: alpha) + } +} + diff --git a/Antidote/UIFontExtension.swift b/Antidote/UIFontExtension.swift new file mode 100644 index 0000000..c5236f8 --- /dev/null +++ b/Antidote/UIFontExtension.swift @@ -0,0 +1,47 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +extension UIFont { + enum AWeight { + case light + case medium + case bold + + func w() -> UIFont.Weight { + if #available(iOS 8.2, *) { + switch self { + case .light: + return UIFont.Weight.light + case .medium: + return UIFont.Weight.medium + case .bold: + return UIFont.Weight.bold + } + } + + return UIFont.Weight(0.0) + } + + func name() -> String { + switch self { + case .light: + return "HelveticaNeue-Light" + case .medium: + return "HelveticaNeue-Medium" + case .bold: + return "HelveticaNeue-Bold" + } + } + } + + class func antidoteFontWithSize(_ size: CGFloat, weight: AWeight) -> UIFont { + if #available(iOS 8.2, *) { + return UIFont.systemFont(ofSize: size, weight: weight.w()) + } else { + return UIFont(name: weight.name(), size: size)! + } + } +} diff --git a/Antidote/UIImageExtension.swift b/Antidote/UIImageExtension.swift new file mode 100644 index 0000000..ae8b847 --- /dev/null +++ b/Antidote/UIImageExtension.swift @@ -0,0 +1,86 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +extension UIImage { + class func emptyImage() -> UIImage { + return imageWithColor(.clear, size: CGSize(width: 1, height: 1)) + } + + class func imageWithColor(_ color: UIColor, size: CGSize) -> UIImage { + let rect = CGRect(origin: CGPoint.zero, size: size) + + UIGraphicsBeginImageContext(rect.size) + let context = UIGraphicsGetCurrentContext() + + context?.setFillColor(color.cgColor) + context?.fill(rect) + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image! + } + + class func templateNamed(_ named: String) -> UIImage { + return UIImage(named: named)!.withRenderingMode(.alwaysTemplate) + } + + func scaleToSize(_ size: CGSize) -> UIImage { + UIGraphicsBeginImageContext(size) + draw(in: CGRect(origin: CGPoint.zero, size: size)) + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return newImage! + } + + func cropWithRect(_ rect: CGRect) -> UIImage { + var rect = rect + + switch imageOrientation { + case .up: + fallthrough + case .upMirrored: + fallthrough + case .down: + fallthrough + case .downMirrored: + break + + case .left: + fallthrough + case .leftMirrored: + fallthrough + case .right: + fallthrough + case .rightMirrored: + var temp = rect.origin.x + rect.origin.x = rect.origin.y + rect.origin.y = temp + + temp = rect.size.width + rect.size.width = rect.size.height + rect.size.height = rect.size.width + } + + if (scale > 1.0) { + rect.origin.x *= scale + rect.origin.y *= scale + rect.size.width *= scale + rect.size.height *= scale + } + + let imageRef = self.cgImage?.cropping(to: rect)! + return UIImage(cgImage: imageRef!, scale: scale, orientation: imageOrientation) + } + + func flippedToCorrectLayout() -> UIImage { + if #available(iOS 9.0, *) { + return imageFlippedForRightToLeftLayoutDirection() + } + return self + } +} diff --git a/Antidote/UIViewControllerExtension.swift b/Antidote/UIViewControllerExtension.swift new file mode 100644 index 0000000..1114d49 --- /dev/null +++ b/Antidote/UIViewControllerExtension.swift @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +extension UIViewController { + func loadViewWithBackgroundColor(_ backgroundColor: UIColor) { + let frame = CGRect(origin: CGPoint.zero, size: UIScreen.main.bounds.size) + + view = UIView(frame: frame) + view.backgroundColor = backgroundColor + } +} diff --git a/Antidote/UserDefaultsManager.swift b/Antidote/UserDefaultsManager.swift new file mode 100644 index 0000000..e531bb0 --- /dev/null +++ b/Antidote/UserDefaultsManager.swift @@ -0,0 +1,139 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +class UserDefaultsManager { + var lastActiveProfile: String? { + get { + return stringForKey(Keys.LastActiveProfile) + } + set { + setObject(newValue as AnyObject?, forKey: Keys.LastActiveProfile) + } + } + + var UDPEnabled: Bool { + get { + return boolForKey(Keys.UDPEnabled, defaultValue: false) + } + set { + setBool(newValue, forKey: Keys.UDPEnabled) + } + } + + var EchobotAdded: Bool { + get { + return boolForKey(Keys.EchobotAdded, defaultValue: false) + } + set { + setBool(newValue, forKey: Keys.EchobotAdded) + } + } + + var DebugMode: Bool { + get { + return boolForKey(Keys.DebugMode, defaultValue: false) + } + set { + setBool(newValue, forKey: Keys.DebugMode) + } + } + + var DateonmessageMode: Bool { + get { + return boolForKey(Keys.DateonmessageMode, defaultValue: true) + } + set { + setBool(newValue, forKey: Keys.DateonmessageMode) + } + } + + var LongerbgMode: Bool { + get { + return boolForKey(Keys.LongerbgMode, defaultValue: false) + } + set { + setBool(newValue, forKey: Keys.LongerbgMode) + } + } + + var showNotificationPreview: Bool { + get { + return boolForKey(Keys.ShowNotificationsPreview, defaultValue: true) + } + set { + setBool(newValue, forKey: Keys.ShowNotificationsPreview) + } + } + + enum AutodownloadImages: String { + case Never + case UsingWiFi + case Always + } + + var autodownloadImages: AutodownloadImages { + get { + let defaultValue = AutodownloadImages.Never //Easy enough to reach option for users. Reverting change since there is an extremely high risk of people getting in trouble since an attacked can get you in prison by sending you cp. + + guard let string = stringForKey(Keys.AutodownloadImages) else { + return defaultValue + } + return AutodownloadImages(rawValue: string) ?? defaultValue + } + set { + setObject(newValue.rawValue as AnyObject?, forKey: Keys.AutodownloadImages) + } + } + + func resetUDPEnabled() { + removeObjectForKey(Keys.UDPEnabled) + } +} + +private extension UserDefaultsManager { + struct Keys { + static let LastActiveProfile = "user-info/last-active-profile" + static let EchobotAdded = "user-info/echobot-added" + static let DebugMode = "user-info/debug-mode" + static let DateonmessageMode = "user-info/dateonmessage-mode" + static let UDPEnabled = "user-info/udp-enabled" + static let ShowNotificationsPreview = "user-info/snow-notification-preview" + static let LongerbgMode = "user-info/longerbg-mode" + static let AutodownloadImages = "user-info/autodownload-images" + } + + func setObject(_ object: AnyObject?, forKey key: String) { + let defaults = UserDefaults.standard + defaults.set(object, forKey:key) + defaults.synchronize() + } + + func stringForKey(_ key: String) -> String? { + let defaults = UserDefaults.standard + return defaults.string(forKey: key) + } + + func setBool(_ value: Bool, forKey key: String) { + let defaults = UserDefaults.standard + defaults.set(value, forKey: key) + defaults.synchronize() + } + + func boolForKey(_ key: String, defaultValue: Bool) -> Bool { + let defaults = UserDefaults.standard + + if let result = defaults.object(forKey: key) { + return (result as AnyObject).boolValue + } + else { + return defaultValue + } + } + + func removeObjectForKey(_ key: String) { + let defaults = UserDefaults.standard + defaults.removeObject(forKey: key) + defaults.synchronize() + } +} diff --git a/Antidote/UserStatus.swift b/Antidote/UserStatus.swift new file mode 100644 index 0000000..5afdedf --- /dev/null +++ b/Antidote/UserStatus.swift @@ -0,0 +1,38 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +enum UserStatus { + case offline + case online + case away + case busy + + init(connectionStatus: OCTToxConnectionStatus, userStatus: OCTToxUserStatus) { + switch (connectionStatus, userStatus) { + case (.none, _): + self = .offline + case (_, .none): + self = .online + case (_, .away): + self = .away + case (_, .busy): + self = .busy + } + } + + func toString() -> String { + switch self { + case .offline: + return String(localized: "status_offline") + case .online: + return String(localized: "status_online") + case .away: + return String(localized: "status_away") + case .busy: + return String(localized: "status_busy") + } + } +} diff --git a/Antidote/UserStatusView.swift b/Antidote/UserStatusView.swift new file mode 100644 index 0000000..aaa18a6 --- /dev/null +++ b/Antidote/UserStatusView.swift @@ -0,0 +1,114 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +class UserStatusView: StaticBackgroundView { + struct Constants { + static let DefaultSize = 14.0 + } + + fileprivate var roundView: StaticBackgroundView? + + var theme: Theme? { + didSet { + userStatusWasUpdated() + } + } + + var showExternalCircle: Bool = true { + didSet { + userStatusWasUpdated() + } + } + + var userStatus: UserStatus = .offline { + didSet { + userStatusWasUpdated() + } + } + + var connectionStatus: ConnectionStatus = .none { + didSet { + userStatusWasUpdated() + } + } + + init() { + super.init(frame: CGRect.zero) + + createRoundView() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + userStatusWasUpdated() + } + + override var frame: CGRect { + didSet { + userStatusWasUpdated() + } + } +} + +private extension UserStatusView { + func createRoundView() { + roundView = StaticBackgroundView() + roundView!.layer.masksToBounds = true + addSubview(roundView!) + + roundView!.snp.makeConstraints { + $0.center.equalTo(self) + $0.size.equalTo(self).offset(-4.0) + } + } + + func userStatusWasUpdated() { + if let theme = theme { + + // TODO: show userstatus as well as connectionstatus + // Currently showing the userstatus when debug mode is off, otherwise the connection status + + if (UserDefaultsManager().DebugMode == false) { + //Default user status indicator + switch userStatus { + case .offline: + roundView?.setStaticBackgroundColor(theme.colorForType(.OfflineStatus)) + case .online: + roundView?.setStaticBackgroundColor(theme.colorForType(.OnlineStatus)) + case .away: + roundView?.setStaticBackgroundColor(theme.colorForType(.AwayStatus)) + case .busy: + roundView?.setStaticBackgroundColor(theme.colorForType(.BusyStatus)) + } + } else { + //Debug connection status indicator + switch connectionStatus { + case .tcp: + roundView?.setStaticBackgroundColor(theme.colorForType(.AwayStatus)) + case .udp: + roundView?.setStaticBackgroundColor(theme.colorForType(.OnlineStatus)) + case .none: + fallthrough + default: + roundView?.setStaticBackgroundColor(theme.colorForType(.OfflineStatus)) + } + } + + let background = showExternalCircle ? theme.colorForType(.StatusBackground) : .clear + setStaticBackgroundColor(background) + } + + layer.cornerRadius = frame.size.width / 2 + + roundView?.layer.cornerRadius = roundView!.frame.size.width / 2 + } +} diff --git a/Antidote/ViewPassingGestures.swift b/Antidote/ViewPassingGestures.swift new file mode 100644 index 0000000..4177afd --- /dev/null +++ b/Antidote/ViewPassingGestures.swift @@ -0,0 +1,19 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit + +class ViewPassingGestures: UIView { + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + for subview in subviews { + let converted = convert(point, to: subview) + + if subview.hitTest(converted, with: event) != nil { + return true + } + } + + return false + } +} diff --git a/Antidote/antidote-acknowledgements.html b/Antidote/antidote-acknowledgements.html new file mode 100644 index 0000000..e783f73 --- /dev/null +++ b/Antidote/antidote-acknowledgements.html @@ -0,0 +1,578 @@ + + + + + +Acknowledgements + + + +

Acknowledgements

+ +

This application makes use of the following third party libraries:

+ +

CocoaLumberjack

+ +

Software License Agreement (BSD License)

+ +

Copyright (c) 2010, Deusty, LLC +All rights reserved.

+ +

Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met:

+ +
    +
  • Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer.

  • +
  • Neither the name of Deusty nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of Deusty, LLC.

  • +
+ + +

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ +

JGProgressHUD

+ +

The MIT License (MIT)

+ +

Copyright (c) 2014-2016 Jonas Gessner

+ +

Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions:

+ +

The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ +

LNNotificationsUI

+ +

The MIT License (MIT)

+ +

Copyright (c) 2014 Leo Natan

+ +

Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:

+ +

The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.

+ +

RBBAnimation

+ +

Copyright (c) 2013 Robert Böhnke.

+ +

Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions:

+ +

The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ +

Realm

+ +

TABLE OF CONTENTS

+ +
    +
  1. Apache License version 2.0
  2. +
  3. Realm Components
  4. +
  5. Export Compliance
  6. +
+ + +
+ +
                             Apache License
+                       Version 2.0, January 2004
+                    http://www.apache.org/licenses/
+
+ +

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

+ +
    +
  1. . Definitions.

    + +

    "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document.

    + +

    "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License.

    + +

    "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity.

    + +

    "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License.

    + +

    "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files.

    + +

    "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types.

    + +

    "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below).

    + +

    "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof.

    + +

    "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution."

    + +

    "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work.

  2. +
  3. . Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form.

  4. +
  5. . Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed.

  6. +
  7. . Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions:

    + +

    (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and

    + +

    (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and

    + +

    (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and

    + +

    (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License.

    + +

    You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License.

  8. +
  9. . Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions.

  10. +
  11. . Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file.

  12. +
  13. . Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License.

  14. +
  15. . Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages.

  16. +
  17. . Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability.

  18. +
+ + +

END OF TERMS AND CONDITIONS

+ +

APPENDIX: How to apply the Apache License to your work.

+ +
  To apply the Apache License to your work, attach the following
+  boilerplate notice, with the fields enclosed by brackets "{}"
+  replaced with your own identifying information. (Don't include
+  the brackets!)  The text should be enclosed in the appropriate
+  comment syntax for the file format. We also recommend that a
+  file or class name and description of purpose be included on the
+  same "printed page" as the copyright notice for easier
+  identification within third-party archives.
+
+ +

Copyright {yyyy} {name of copyright owner}

+ +

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.

+ +

REALM COMPONENTS

+ +

This software contains components with separate copyright and license terms. +Your use of these components is subject to the terms and conditions of the +following licenses.

+ +

For the Realm Core component

+ +

Realm Core Binary License

+ +

Copyright (c) 2011-2014 Realm Inc All rights reserved

+ +

Redistribution and use in binary form, with or without modification, is + permitted provided that the following conditions are met:

+ +
    +
  1. You agree not to attempt to decompile, disassemble, reverse engineer or +otherwise discover the source code from which the binary code was derived. +You may, however, access and obtain a separate license for most of the +source code from which this Software was created, at +http://realm.io/pricing/.

  2. +
  3. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution.

  4. +
  5. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from this +software without specific prior written permission.

  6. +
+ + +

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE.

+ +

EXPORT COMPLIANCE

+ +

You understand that the Software may contain cryptographic functions that may be +subject to export restrictions, and you represent and warrant that you are not +located in a country that is subject to United States export restriction or embargo, +including Cuba, Iran, North Korea, Sudan, Syria or the Crimea region, and that you +are not on the Department of Commerce list of Denied Persons, Unverified Parties, +or affiliated with a Restricted Entity.

+ +

You agree to comply with all export, re-export and import restrictions and +regulations of the Department of Commerce or other agency or authority of the +United States or other applicable countries. You also agree not to transfer, or +authorize the transfer of, directly or indirectly, the Software to any prohibited +country, including Cuba, Iran, North Korea, Sudan, Syria or the Crimea region, +or to any person or organization on or affiliated with the Department of +Commerce lists of Denied Persons, Unverified Parties or Restricted Entities, or +otherwise in violation of any such restrictions or regulations.

+ +

SDCAlertView

+ +

The MIT License (MIT)

+ +

Copyright (c) 2013 Scott Berrevoets

+ +

Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:

+ +

The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.

+ +

SDCAutoLayout

+ +

Copyright (c) 2013 Scott Berrevoets

+ +

Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions:

+ +

The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ +

TPCircularBuffer

+ +

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

+ +

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."

+ +

UITextView+Placeholder

+ +

The MIT License (MIT)

+ +

Copyright (c) 2014 Suyeol Jeon (http://xoul.kr)

+ +

Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:

+ +

The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.

+ +

libopus-patched-config

+ +

Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic, + Jean-Marc Valin, Timothy B. Terriberry, + CSIRO, Gregory Maxwell, Mark Borgerding, + Erik de Castro Lopo

+ +

Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met:

+ +
    +
  • Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer.

  • +
  • Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution.

  • +
  • Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission.

  • +
+ + +

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ +

Opus is subject to the royalty-free patent licenses which are +specified at:

+ +

Xiph.Org Foundation: +https://datatracker.ietf.org/ipr/1524/

+ +

Microsoft Corporation: +https://datatracker.ietf.org/ipr/1914/

+ +

Broadcom Corporation: +https://datatracker.ietf.org/ipr/1526/

+ +

libsodium

+ +

Copyright © 2013\nFrank Denis \n\nPermission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n

+ +

objcTox

+ +

The MIT License (MIT)

+ +

Copyright (c) 2014 Dmitry Vorobyov

+ +

Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions:

+ +

The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ +

SnapKit

+ +

Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit

+ +

Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:

+ +

The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.

+ +

YamlSwift

+ +

The MIT License (MIT)

+ +

Copyright (c) 2015 Behrang Noruzi Niya

+ +

Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:

+ +

The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.

+ + \ No newline at end of file diff --git a/Antidote/appstore-512_orig.png b/Antidote/appstore-512_orig.png new file mode 100644 index 0000000..612d601 Binary files /dev/null and b/Antidote/appstore-512_orig.png differ diff --git a/Antidote/appstore.png b/Antidote/appstore.png new file mode 100644 index 0000000..d92a11b Binary files /dev/null and b/Antidote/appstore.png differ diff --git a/Antidote/appstore.svg b/Antidote/appstore.svg new file mode 100644 index 0000000..aae303c --- /dev/null +++ b/Antidote/appstore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Antidote/ar.lproj/InfoPlist.strings b/Antidote/ar.lproj/InfoPlist.strings new file mode 100644 index 0000000..6c333a8 Binary files /dev/null and b/Antidote/ar.lproj/InfoPlist.strings differ diff --git a/Antidote/ar.lproj/Localizable.strings b/Antidote/ar.lproj/Localizable.strings new file mode 100644 index 0000000..15a04f1 --- /dev/null +++ b/Antidote/ar.lproj/Localizable.strings @@ -0,0 +1,415 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* + Translation is done with Transifex service. + See https://www.transifex.com/antidote/antidote-ios/ +*/ + +/* Login screen text */ +"login_or_label" = "أو"; +/* Login screen text */ +"create_account" = "حساب جديد"; +/* Login screen text */ +"import_profile" = "نقل الحساب"; +/* Password field */ +"password" = "كلمة المرور"; +/* Login button */ +"log_in" = "دخول"; +/* Login screen text */ +"create_profile" = "إنشاء حساب"; +/* Login screen text */ +"import_to_antidote" = "إنتقال الى انتيدوت"; +/* Login screen text */ +"create_account_username_title" = "كيف يتم مشاهدتك في قائمة الاتصال؟"; +/* Login screen text */ +"create_account_username_placeholder" = "إسم المستخدم"; +/* Login screen text */ +"create_account_profile_title" = "كيف تتصل بهذا الحساب؟"; +/* Login screen text */ +"create_account_profile_placeholder" = "إسم الحساب"; +/* Login screen text */ +"create_account_profile_hint" = "على سبيل المثال المنزل، الايفون"; +/* Login screen text */ +"set_password_title" = "ضع كلمة مرور"; +/* Login screen text */ +"set_password_hint" = "الحاجة في كلمة المرور هي لحماية البيانات الخاصة. أحتفظ بها - بفقدانها لا يمكن استعادة الحساب."; +/* Login button */ +"create_account_next_button" = "التالي"; +/* Login button */ +"create_account_go_button" = "إبدأ"; +/* Default status message */ +"default_user_status_message" = "اتواصل بواسطة انتيدوت"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "أدخل رقم PIN الخاص بك للفتح"; +/* PIN screen text */ +"pin_set" = "تعيين PIN"; +/* PIN screen text */ +"pin_confirm" = "تأكيد PIN"; +/* PIN screen error */ +"pin_do_not_match" = "الأرقام غير مطابقة. حاول ثانية"; +/* PIN screen error */ +"pin_incorrect" = "PIN غير صحيح"; +/* PIN screen error details */ +"pin_failed_attempts" = "المحاولات الفاشلة: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "الكثير من المحاولات الفاشلة. لقد تم تسجيل خروجك."; +/* PIN screen screen */ +"pin_enabled" = "تفعيل PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "تفعيل الهوية باللمس"; +/* PIN screen screen */ +"pin_description" = "منع الوصول غير المصرح به إلى أنتيدوت مع PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "استخدام بصمة الإصبع كبديل لإدخال رمز PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "إقفال عند نفاذ الوقت"; +/* PIN screen screen */ +"pin_lock_immediately" = "فورا"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 ثانية"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 دقيقة"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 دقيقة"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 دقيقة"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "على قيد الإتصال..."; + +/* Tab name and screen name */ +"contacts_title" = "القائمة"; +/* Tab name and screen name */ +"chats_title" = "المحادثات"; +/* Tab name and screen name */ +"settings_title" = "الإعدادات"; +/* Tab name and screen name */ +"profile_title" = "الحساب"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "لا توجد جهات اتصال"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "اضافة حساب\nاو\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "مشاركة حساب التوكس الخاص بك"; +/* Contact request section title */ +"contact_requests_section" = "قائمة الإضافات"; +/* Contact last seen status */ +"contact_last_seen" = "نهايتها: %@"; +/* Screen name */ +"contact_request" = "طلب إضافة"; +/* Contact request button */ +"contact_request_decline" = "رفض"; +/* Contact request button */ +"contact_request_accept" = "قبول"; +/* Contact request confirmation */ +"contact_request_delete_title" = "إزالة طلب الإضافة؟"; +/* Text shown when contact was deleted */ +"contact_deleted" = "جهة اتصال محذوفة"; + +/* Share Tox ID text */ +"show_qr_code" = "عرض رمز الاستجابة السريع او الباركود"; +/* Share Tox ID text */ +"copy" = "نسخ"; + +/* Add contat screen name */ +"add_contact_title" = "إضافة حساب"; +/* Add contat button */ +"add_contact_send" = "إرسال"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "أدخل عنوان حساب التوكس"; +/* Add contat placeholder text */ +"add_contact_or_label" = "أو"; +/* Add contat button */ +"add_contact_use_qr" = "إستخدام رمز الاستجابة السريع او الباركود"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "رسالة"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "مرحباً! هل من الممكن أن تقبل إضافتي في قائمتك؟"; +/* Add contat error */ +"add_contact_wrong_qr" = "رمز الاستجابة السريع او الباركود خطأ. يجب توفر عنوان حساب التوكس"; + +/* User name text */ +"name" = "الإسم"; +/* User nickname text */ +"nickname" = "اللقب"; +/* User status message text */ +"status_message" = "وضع الحالة"; +/* User status text */ +"status_title" = "الحالة"; +/* User Tox ID text */ +"my_tox_id" = "عنوان حساب التوكس"; +/* User public key text */ +"public_key" = "مفتاح المعلن المشفر"; +/* Share Tox ID text */ +"show_qr" = "عرض رمز الاستجابة السريع او الباركود"; +/* Profile menu item / screen name */ +"profile_details" = "بيانات الحساب"; +/* Profile button */ +"logout_button" = "تسجيل خروج"; + +/* QR code screen button */ +"qr_close_button" = "إغلاق"; + +/* Chat screen placeholder */ +"chat_no_chats" = "لا توجد محادثات"; +/* Chats screen message text */ +"chat_outgoing_file" = "الملف الصادر:"; +/* Chats screen message text */ +"chat_incoming_file" = "الملف الوارد:"; +/* Chats screen message text */ +"chat_call_finished" = "انتهت المكالمة"; +/* Chats screen message text */ +"chat_unanwered_call" = "لم تتم الإستجابة على المكالمة"; +/* Chat button */ +"chat_send_button" = "إرسال"; +/* Chat notification toast */ +"chat_new_messages" = "رسائل جديدة"; +/* Chat call information */ +"chat_call_message" = "اتصل، "; +/* Chat call information */ +"chat_missed_call_message" = "مكالمة لم يرد عليها"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "المزيد"; + +/* Status message */ +"status_offline" = "غير متصل"; +/* Status message */ +"status_online" = "متصل"; +/* Status message */ +"status_away" = "بالخارج"; +/* Status message */ +"status_busy" = "مشغول"; + +/* Notification text */ +"notification_new_message" = "رسالة جديدة"; +/* Notification text */ +"notification_incoming_contact_request" = "طلب دعوة إضافة"; +/* Notification text */ +"notification_is_calling" = "يتصل"; +/* Notification text */ +"notification_incoming_file" = "وصول ملف"; + +/* Settings menu / screen name */ +"settings_about" = "نبذة عن"; +/* Settings menu / screen name */ +"settings_faq" = "التعليمات"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "خصائص الاعدادات"; +/* About screen menu */ +"settings_antidote_version" = "إصدار الانتيدوت"; +/* About screen menu */ +"settings_antidote_build" = "بناء الانتيدوت"; +/* About screen menu */ +"settings_toxcore_version" = "إصدار نواة التوكس او التوكس كور"; +/* About screen menu */ +"settings_acknowledgements" = "شكر وتنويه"; +/* Settings screen menu */ +"settings_autodownload_images" = "ملفات التنزيل التلقائي"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "تنزيل الملفات الواردة تلقائيا."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "عرض الإشعارات"; +/* Settings screen menu */ +"settings_notifications_description" = "حينما يتم عمل البرنامج في النظام، سيتم عرض الإشعارات حتى 10 دقائق."; +/* Settings screen menu */ +"settings_udp_enabled" = "تفعيل UDP"; +/* Settings screen menu */ +"settings_restore_default" = "إسترجاع الإعدادات التلقائية"; +/* Settings screen autodownload images option */ +"settings_never" = "لا"; +/* Settings screen autodownload images option */ +"settings_wifi" = "إتصال لاسلكي او واي-فاي"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "إستخدام إتصال لاسلكي او واي-فاي"; +/* Settings screen autodownload images option */ +"settings_always" = "دائماً"; + +/* Profile settings menu */ +"change_password" = "تغيير كلمة المرور"; +/* Profile settings menu */ +"delete_password" = "إزالة كلمة المرور"; +/* Profile settings menu */ +"old_password" = "كلمة المرور القديمة"; +/* Change password text */ +"new_password" = "كلمة المرور الجديدة"; +/* Change password text */ +"repeat_password" = "كرر كلمة المرور"; +/* Change password button */ +"change_password_done" = "انتهى"; +/* Change password error */ +"password_is_empty_error" = "كلمة المرور يجب أن تكون غير فارغة"; +/* Change password error */ +"wrong_old_password" = "كلمة المرور خطأ"; +/* Change password error */ +"passwords_do_not_match" = "كلمة المرور غير مطابقة"; + +/* Source of photo to take */ +"photo_from_camera" = "تصوير"; +/* Source of photo to take */ +"photo_from_photo_library" = "قائمة الصور"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "لا يمكن تحويل الصورة"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "إرسال إلى جهة اتصال"; + +/* Profile menu item */ +"export_profile" = "حفظ الحساب الشخصي"; +/* Profile menu item */ +"delete_profile" = "حذف الحساب الشخصي"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "هل تريد حذف هذا الحساب؟"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "هل أنت متأكد؟"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "هذه العملية لا يمكن التراجع عنها"; + +/* Call screen text */ +"call_incoming" = "مكالمة واردة"; +/* Call screen text */ +"call_ended" = "انتهت المكالمة"; +/* Call screen text */ +"call_reaching" = "على قيد الوصول..."; + +/* File message text */ +"chat_file_cancelled" = "تم الإلغاء"; +/* File message text */ +"chat_waiting" = "إنتظار..."; +/* File message text */ +"chat_paused" = "إيقاف مؤقت"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "حذف هذه المحادثة والرسائل المحفوظة بها؟"; +/* Deleting contat confirmation */ +"delete_contact_title" = "إزالة هذا الحساب؟\nسيتم فقدان سجل المحادثة."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "إزالة الدعوة لهذا الحساب؟"; +/* Deleting single message in chat */ +"delete_single_message" = "حذف الرسالة"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "حذف الرسائل"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "حذف الكل"; +/* Delete button */ +"alert_delete" = "إزالة"; +/* Delete button */ +"alert_cancel" = "إلغاء الامر"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "الرجاء إدخال كل من اسم المستخدم واسم الحساب."; +/* Error while creating new profile */ +"login_profile_already_exists" = "إسم الحساب موجود مسبقاً"; + +/* General error title */ +"error_title" = "خطأ"; +/* General error button */ +"error_ok_button" = "موافق"; +/* General error button */ +"error_retry_button" = "إعادة المحاولة"; +/* Error */ +"error_contact_not_connected" = "الحساب غير متصل"; +/* Error */ +"error_too_many_files" = "هناك عدد كبير من الملفات المفعلة على قيد النقل"; +/* Error */ +"error_internal_message" = "حدث خطأ داخلي"; +/* Error */ +"error_wrong_password_title" = "كلمة المرور خطأ"; +/* Error */ +"error_wrong_password_message" = "كلمة المرور تحتوي على رموز خاطئة."; +/* Error */ +"error_import_not_exist_title" = "لا يمكن نقل ملف التوكس المحفوظ"; +/* Error */ +"error_import_not_exist_message" = "الملف غير موجود."; +/* Error */ +"error_decrypt_title" = "لا يمكن فك تشفير ملف التوكس المحفوظ"; +/* Error */ +"error_decrypt_empty_data_message" = "بعض بيانات الإدخال كانت فارغة."; +/* Error */ +"error_decrypt_bad_format_message" = "الملف يحتوي على تنسيق سيء أو تالف."; +/* Error */ +"error_decrypt_wrong_password_message" = "كلمة السر غير صحيحة أو قد يكون الملف تالف."; +/* Error */ +"error_general_unknown_message" = "حدث خطأ مجهول."; +/* Error */ +"error_general_no_memory_message" = "الذاكرة لا تكفي."; +/* Error */ +"error_general_bind_port_message" = "لم يتمكن لربط المنفذ."; +/* Error */ +"error_general_profile_encrypted_message" = "تم تشفير الحساب."; +/* Error */ +"error_general_bad_format_message" = "الملف يحتوي على تنسيق سيء أو تالف."; +/* Error */ +"error_proxy_title" = "خطأ في الخادم الوكيل او البروكسي"; +/* Error */ +"error_proxy_invalid_address_message" = "عنوان الوكيل او البروكسي غير صالح."; +/* Error */ +"error_proxy_invalid_port_message" = "منفذ الوكيل او البروكسي غير صالح."; +/* Error */ +"error_proxy_host_not_resolved_message" = "لا يمكن حل استضافة الوكيل او البروكسي."; + +/* Error */ +"error_name_too_long" = "الإسم طويل جداً."; +/* Error */ +"error_status_message_too_long" = "الرسالة في الحالة طويلة جداً"; + +/* Error */ +"error_contact_request_too_long" = "الرسالة طويلة جداً"; +/* Error */ +"error_contact_request_no_message" = "لا يوجد رسائل محددة"; +/* Error */ +"error_contact_request_own_key" = "لا يمكن إضافة نفس حسابي في قائمة الاتصال الخاصة بي"; +/* Error */ +"error_contact_request_already_sent" = "طلب الإضافة قد تم ارسالها مسبقاً"; +/* Error */ +"error_contact_request_bad_checksum" = "المجموع الاختباري باطل، يرجى التحقق من هوية Tox المدخلة"; +/* Error */ +"error_contact_request_new_nospam" = "قيمة تعطيل الإزعاج nospam باطلة، يرجى التحقق من هوية Tox المدخلة."; + +/* Call error */ +"call_error_already_in_call" = "حالياً في مكالمة"; +/* Call error */ +"call_error_contact_is_offline" = "الحساب غير متصل"; +/* Call error */ +"call_error_no_active_call" = "ليس هناك مكالمة أخرى"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "لا يمكن فتح السمة، التنسيق خاطئ"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "المحادثات الغير مقروء"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "الصورة الرمزية"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "تحديد أو إزالة الصورة الرمزية."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "محادثة"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "يفتح حوار المحادثة."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "مكالمة صوتية"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "لطلب مكالمة صوتية."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "مكالمة مرئية"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "لطلب مكالمة مرئية."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "يظهر نسخة القائمة."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "تعديلات القيمة."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "رسالة"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "رسالتك"; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "سيتم إرسال الرسائل التي لم يتم تسليمها عندما تكون أنت وصديقك متصلين بالإنترنت."; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "يكتب..."; diff --git a/Antidote/ar.lproj/import-profile.html b/Antidote/ar.lproj/import-profile.html new file mode 100644 index 0000000..fecedc1 --- /dev/null +++ b/Antidote/ar.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

لنقل حساب التوكس الخاص بك:

+ +
    +
  1. ارسال ملف ".tox" للجهاز الخاص بك بواسطة اي برنامج (بريد الكتروني، دروبوكس، الخ).
  2. +
  3. استخدام قائمة "فتح" لهذا الملف.
  4. +
  5. اختر انتيدوت في قائمة البرامج الرئيسية.
  6. +
  7. أختر الاسم لحسابك الجديد وبعدها انقر على موافق.
  8. +
diff --git a/Antidote/br.lproj/InfoPlist.strings b/Antidote/br.lproj/InfoPlist.strings new file mode 100644 index 0000000..b8e9656 Binary files /dev/null and b/Antidote/br.lproj/InfoPlist.strings differ diff --git a/Antidote/br.lproj/Localizable.strings b/Antidote/br.lproj/Localizable.strings new file mode 100644 index 0000000..da5d0c0 --- /dev/null +++ b/Antidote/br.lproj/Localizable.strings @@ -0,0 +1,415 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* + Translation is done with Transifex service. + See https://www.transifex.com/antidote/antidote-ios/ +*/ + +/* Login screen text */ +"login_or_label" = "pe"; +/* Login screen text */ +"create_account" = "Krouiñ ur Gont"; +/* Login screen text */ +"import_profile" = "Emporzhiañ ur Profil"; +/* Password field */ +"password" = "Ger-kuzh"; +/* Login button */ +"log_in" = "Kevreañ"; +/* Login screen text */ +"create_profile" = "Krouiñ ur profil"; +/* Login screen text */ +"import_to_antidote" = "Emporzhiañ e-barzh Antidote"; +/* Login screen text */ +"create_account_username_title" = "Penaos e viot gwelet gant ho tarempredoù ?"; +/* Login screen text */ +"create_account_username_placeholder" = "Anv-Implijer"; +/* Login screen text */ +"create_account_profile_title" = "Penaos pellgomz d'ar profil-mañ ?"; +/* Login screen text */ +"create_account_profile_placeholder" = "Anv ar profil"; +/* Login screen text */ +"create_account_profile_hint" = "d.sk. Home, iPhone"; +/* Login screen text */ +"set_password_title" = "Termeniñ ur Ger-Kuzh"; +/* Login screen text */ +"set_password_hint" = "Ret eo da gaout gerioù-kuzh da wareziñ ho roadennoù. Mirit anezhañ sur - ur wezh kollet ne c'hallo bet bezañ adroet."; +/* Login button */ +"create_account_next_button" = "Goude"; +/* Login button */ +"create_account_go_button" = "Mont"; +/* Default status message */ +"default_user_status_message" = "Toksi a ran war Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Ebarzhiñ ho PIN da zibrennañ"; +/* PIN screen text */ +"pin_set" = "Termenit ur PIN"; +/* PIN screen text */ +"pin_confirm" = "Kadarnaat ar PIN"; +/* PIN screen error */ +"pin_do_not_match" = "Ne glot ket ar PINoù. Klaskit adarre"; +/* PIN screen error */ +"pin_incorrect" = "PIN Direizh"; +/* PIN screen error details */ +"pin_failed_attempts" = "C'hwitet ez eus bet gant ar glaskadenn : %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "C'hwitet ez eus bet war re a glaskadennoù. Digevreet oc'h bet."; +/* PIN screen screen */ +"pin_enabled" = "Pin Gweredeakaet"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Gweredekaat Touchenn an ID"; +/* PIN screen screen */ +"pin_description" = "Mirout ouzh an haezennoù diaotreet war Antidote gant ur c'hod PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "Ober gant ur roudennviz evel un hent-biou da ebarzhañ ur PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "Prennañ an Diamzer"; +/* PIN screen screen */ +"pin_lock_immediately" = "Diouzhtu-dak"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 Eilenn"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 Vunutenn"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 Vunutenn"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 Munutenn"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "O kevreiñ..."; + +/* Tab name and screen name */ +"contacts_title" = "Darempredoù"; +/* Tab name and screen name */ +"chats_title" = "Flapoùva"; +/* Tab name and screen name */ +"settings_title" = "Arventennoù"; +/* Tab name and screen name */ +"profile_title" = "Profil"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Darempred ebet"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Ouzhpenniñ un darempred\npe\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "Rannañ hoc'h identelezh Tox"; +/* Contact request section title */ +"contact_requests_section" = "Goulennoù darempred"; +/* Contact last seen status */ +"contact_last_seen" = "Gwelet da : %@"; +/* Screen name */ +"contact_request" = "Goulenn darempred"; +/* Contact request button */ +"contact_request_decline" = "Nac'hañ"; +/* Contact request button */ +"contact_request_accept" = "Asantiñ"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Lemmel ar goulenn darempred kuit ?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Lemel an darempred kuit"; + +/* Share Tox ID text */ +"show_qr_code" = "Diskouez ar c'hod QR"; +/* Share Tox ID text */ +"copy" = "Eilañ"; + +/* Add contat screen name */ +"add_contact_title" = "Ouzhpenniñ Darempred"; +/* Add contat button */ +"add_contact_send" = "Kas"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Ebarzhiañ identelezh Tox"; +/* Add contat placeholder text */ +"add_contact_or_label" = "pe"; +/* Add contat button */ +"add_contact_use_qr" = "Implijout ar c'hod QR"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Kemennadenn"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Demat ! Ha gellout a rafec'h ouzhpenniñ ac'hanon d'ho listenn darempredoù ?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Kod QR fall. Rankout a rafe kaout en ennañ an identelezh Tox"; + +/* User name text */ +"name" = "Anv"; +/* User nickname text */ +"nickname" = "Lezanv"; +/* User status message text */ +"status_message" = "Statud ar gemennadenn"; +/* User status text */ +"status_title" = "Statud"; +/* User Tox ID text */ +"my_tox_id" = "Ma identelezh Tox"; +/* User public key text */ +"public_key" = "Alc'hwez Foran"; +/* Share Tox ID text */ +"show_qr" = "Diskouez QR"; +/* Profile menu item / screen name */ +"profile_details" = "Munudoù ar profil"; +/* Profile button */ +"logout_button" = "Digevreañ"; + +/* QR code screen button */ +"qr_close_button" = "Serriñ"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Flapoù ebet"; +/* Chats screen message text */ +"chat_outgoing_file" = "Restr o vont maez :"; +/* Chats screen message text */ +"chat_incoming_file" = "Restr o vont e-barzh :"; +/* Chats screen message text */ +"chat_call_finished" = "Pellgomzadenn echuet"; +/* Chats screen message text */ +"chat_unanwered_call" = "Pellgomzadenn chomet direspont"; +/* Chat button */ +"chat_send_button" = "Kas"; +/* Chat notification toast */ +"chat_new_messages" = "↓ Kemennadennoù nevez"; +/* Chat call information */ +"chat_call_message" = "Pellgomz, "; +/* Chat call information */ +"chat_missed_call_message" = "Pellgomzadennoù c'hwitet"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Muioc'h"; + +/* Status message */ +"status_offline" = "Maezlinenn"; +/* Status message */ +"status_online" = "Enlinenn"; +/* Status message */ +"status_away" = "Ezvezant"; +/* Status message */ +"status_busy" = "Ac'hubet"; + +/* Notification text */ +"notification_new_message" = "Kemennadennoù nevez"; +/* Notification text */ +"notification_incoming_contact_request" = "Goulenn darempred nevez"; +/* Notification text */ +"notification_is_calling" = "O pellgomz"; +/* Notification text */ +"notification_incoming_file" = "Restr o vont e-barzh"; + +/* Settings menu / screen name */ +"settings_about" = "Diwar-benn"; +/* Settings menu / screen name */ +"settings_faq" = "FAG"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Arventennoù araokaet"; +/* About screen menu */ +"settings_antidote_version" = "Stumm Antidote"; +/* About screen menu */ +"settings_antidote_build" = "Sevel Antidote"; +/* About screen menu */ +"settings_toxcore_version" = "Stumm Toxcore"; +/* About screen menu */ +"settings_acknowledgements" = "Trugarekadurioù"; +/* Settings screen menu */ +"settings_autodownload_images" = "Pellgargañ skeudennoù emgefreek"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Pellgargañ en un doare emgefreek ar skeudennoù o tont e-barzh."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Diskouez ar C'hemennadennoù"; +/* Settings screen menu */ +"settings_notifications_description" = "Pa vo an arload war an drekleur, resev a rit kemennadennoù e-pad dek munud c'hoazh."; +/* Settings screen menu */ +"settings_udp_enabled" = "Gweredekaat an UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Resteurel an arventennoù dre ziouer"; +/* Settings screen autodownload images option */ +"settings_never" = "Morse"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-fi"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Oc'h implijout ar Wi-fi"; +/* Settings screen autodownload images option */ +"settings_always" = "Dalc'hmat"; + +/* Profile settings menu */ +"change_password" = "Cheñch ar Ger-Kuzh"; +/* Profile settings menu */ +"delete_password" = "Dilemmel ar Ger-Kuzh"; +/* Profile settings menu */ +"old_password" = "Ger-Kuzh kozh"; +/* Change password text */ +"new_password" = "Ger-Kuzh nevez"; +/* Change password text */ +"repeat_password" = "Ar Ger-Kuzh c'hoazh"; +/* Change password button */ +"change_password_done" = "Graet"; +/* Change password error */ +"password_is_empty_error" = "Ne rankfe ket bezañ goullo ar Ger-Kuzh"; +/* Change password error */ +"wrong_old_password" = "Ger-Kuzh Fall"; +/* Change password error */ +"passwords_do_not_match" = "Ne Glot ket ar Gerioù-Kuzh"; + +/* Source of photo to take */ +"photo_from_camera" = "Kamera"; +/* Source of photo to take */ +"photo_from_photo_library" = "Ar Skeudennaoueg"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Dibosupl eo da emdreiñ ar skeudenn"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Kas d'an darempred"; + +/* Profile menu item */ +"export_profile" = "Ezporzhiañ ar Profil"; +/* Profile menu item */ +"delete_profile" = "Lemmel kuit ar Profil"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Lammel Kuit ar Profil ?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Ha sur oc'h ?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Ne c'hellec'h ket distreiñ war an oberiadenn-mañ"; + +/* Call screen text */ +"call_incoming" = "Pellgomzadenn o tont-tre"; +/* Call screen text */ +"call_ended" = "Pellgomzadenn echuet"; +/* Call screen text */ +"call_reaching" = "O c'hortoz..."; + +/* File message text */ +"chat_file_cancelled" = "Nulet"; +/* File message text */ +"chat_waiting" = "O C'hortoz..."; +/* File message text */ +"chat_paused" = "Ehanet"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Lemmel ar flap hag ar c'hemennadennoù istorel-mañ kuit ?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Lemmel an darempred-mañ kuit ?\nHo flap istorel a vo kollet."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Lemmel ar goulenn darempred kuit ?"; +/* Deleting single message in chat */ +"delete_single_message" = "Dilemmel ar Gemennadenn"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Dilemmel ar C'hemennadennoù"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Lemmel kuit an Holl"; +/* Delete button */ +"alert_delete" = "Lemmel kuit"; +/* Delete button */ +"alert_cancel" = "Nulañ"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Ebarzhit un anv implijer hag un anv profil."; +/* Error while creating new profile */ +"login_profile_already_exists" = "Graet e vez gant an anv profil-mañ dija"; + +/* General error title */ +"error_title" = "Fazi"; +/* General error button */ +"error_ok_button" = "Mat eo"; +/* General error button */ +"error_retry_button" = "Adklask"; +/* Error */ +"error_contact_not_connected" = "N'emañ ket maez-linenn an darempred"; +/* Error */ +"error_too_many_files" = "Re a dreuskasoù restr gweredekaet"; +/* Error */ +"error_internal_message" = "Fazi Diabarzh"; +/* Error */ +"error_wrong_password_title" = "Ger-Kuzh Fall"; +/* Error */ +"error_wrong_password_message" = "Ar ger-kuzh a embarzh arouezhioù fall."; +/* Error */ +"error_import_not_exist_title" = "Dibosupl da embarzhañ ar restr tox enrollet"; +/* Error */ +"error_import_not_exist_message" = "N'eus ket eus ar restr."; +/* Error */ +"error_decrypt_title" = "Dibosupl da zirinegañ ar restr tox enrollet"; +/* Error */ +"error_decrypt_empty_data_message" = "Lod eus ar maeziennoù stlenn a oa goullo."; +/* Error */ +"error_decrypt_bad_format_message" = "Ar restr a zo er stumm fall pe trefoet."; +/* Error */ +"error_decrypt_wrong_password_message" = "Fall eo ar ger-kuzh pe trefoet eo ar restr."; +/* Error */ +"error_general_unknown_message" = "C'hoarvezet ez eus ur fazi dianavezet."; +/* Error */ +"error_general_no_memory_message" = "N'eus ket trawalc'h a vemor."; +/* Error */ +"error_general_bind_port_message" = "Dibosubl d'en em lugañ ouzh ur porzh."; +/* Error */ +"error_general_profile_encrypted_message" = "Rineget eo ar Profil."; +/* Error */ +"error_general_bad_format_message" = "Ar restr a zo er stumm fall pe trefoet."; +/* Error */ +"error_proxy_title" = "Fazi a-berzh ar Proxy"; +/* Error */ +"error_proxy_invalid_address_message" = "Ur stumm fall a zo gant chomlec'h ar Proxy."; +/* Error */ +"error_proxy_invalid_port_message" = "Porzh Proxy a zo direizh."; +/* Error */ +"error_proxy_host_not_resolved_message" = "N'en deus ket gallet bezañ digoublet hostiz Proxy."; + +/* Error */ +"error_name_too_long" = "An anv a zo re hir."; +/* Error */ +"error_status_message_too_long" = "Statud ar gemennadenn a zo re hir"; + +/* Error */ +"error_contact_request_too_long" = "Ar gemennadenn a zo re hir"; +/* Error */ +"error_contact_request_no_message" = "Kemennadenn ebet spisaet"; +/* Error */ +"error_contact_request_own_key" = "Ne c'hallan ket ma ouzhpennañ d'am listenn darempredoù"; +/* Error */ +"error_contact_request_already_sent" = "Goulenn mignoniezh kaset dija"; +/* Error */ +"error_contact_request_bad_checksum" = "Checksum fall, gwiriekait mar-plij ho ped ebarzhet an identelezh Tox mat"; +/* Error */ +"error_contact_request_new_nospam" = "Nospam fall, gwiriekait mar-plij ho ped ebarzhet an identelezh Tox mat"; + +/* Call error */ +"call_error_already_in_call" = "Oc'h ober ur bellgomzadenn dija"; +/* Call error */ +"call_error_contact_is_offline" = "E maez-linenn emañ an darempred"; +/* Call error */ +"call_error_no_active_call" = "N'eus pellgomzadenn gweredakaet ebet"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Dibosupl eo da zigeriñ an tem, stumm fall"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "flapoù amlennet"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Lakaat pe dilemel an avatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Flapva"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Digeriñ ar flapva."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Pellgomzadenn gant ar vouezh"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Pellgomz gant ar vouezh."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Pellgomzadenn gant ar Video"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Pellgomz gant ar Video."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Diskouez ar roll eilañ."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Kemm an talvoudegezh."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Kemennadenn"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Ho kemennadenn"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "'zo o skrivañ..."; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Kemennadoù nan-dasparzhet a vo kaset pa e vi kevreet kenkoulz hag ho vignon."; diff --git a/Antidote/br.lproj/import-profile.html b/Antidote/br.lproj/import-profile.html new file mode 100644 index 0000000..afb06b5 --- /dev/null +++ b/Antidote/br.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

Evit emporzhiañ ho profil Tox :

+ +
    +
  1. Kas ar restr ".tox" betek hoc'h ardivink diouzh forzh peseurt arload (Mail, Dropbox, etc.).
  2. +
  3. Implijout ar roll "Digeriñ E-Barzh" evit ar restr-ma.
  4. +
  5. Diuzit Antidote e-barzh listenn an arloadoù da bellgarga.
  6. +
  7. Gwiriekait anv ho profil nevez ha pouezit war OK.
  8. +
diff --git a/Antidote/ca.lproj/AppStoreLocalizable.strings b/Antidote/ca.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..d6fbb04 --- /dev/null +++ b/Antidote/ca.lproj/AppStoreLocalizable.strings @@ -0,0 +1,54 @@ +/* + AppStoreLocalizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/02/17. + Copyright © 2017 dvor. All rights reserved. +*/ + +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_1" = "Mary Cokley"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_2" = "Shirley Knox"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_3" = "Jennifer Smith"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_4" = "Marina Dixon"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_5" = "Carol Ortega"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_1" = "Michael Sharpe"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_2" = "Charles Donahue"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_3" = "Lee Murdock"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_4" = "Wayne Henderson"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_5" = "Robert Newton"; + +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_1" = "Is Antidote really that secure?"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_2" = "sure, it is peer-to-peer"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_3" = "And what does that mean? Peer-to-peer? 😄"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_4" = "you text me directly, the are no servers or things like that"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_5" = "+ it's encrypted 🔐😎"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_6" = "Cool!"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_7" = "I'll give it a go then"; + +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_1" = "😂😂😂"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_2" = "dinner tonight?"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_3" = "I think I know what you are talking about"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_4" = "Sure, thanks!"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_5" = "yep"; diff --git a/Antidote/ca.lproj/InfoPlist.strings b/Antidote/ca.lproj/InfoPlist.strings new file mode 100644 index 0000000..b79a4d0 --- /dev/null +++ b/Antidote/ca.lproj/InfoPlist.strings @@ -0,0 +1,16 @@ +/* + InfoPlist.strings + Antidote + + Created by Dmytro Vorobiov on 15/11/16. + Copyright © 2016 dvor. All rights reserved. +*/ + +/* Camera usage alert description */ +"NSCameraUsageDescription" = "You can use video calls, send photos and videos, scan QR codes."; + +/* Microphone usage alert description */ +"NSMicrophoneUsageDescription" = "You can use audio and video calls."; + +/* Photo library usage alert description */ +"NSPhotoLibraryUsageDescription" = "You can send photos and videos."; \ No newline at end of file diff --git a/Antidote/ca.lproj/Localizable.strings b/Antidote/ca.lproj/Localizable.strings new file mode 100644 index 0000000..bdccfe3 --- /dev/null +++ b/Antidote/ca.lproj/Localizable.strings @@ -0,0 +1,404 @@ + + +/* Contact request button */ +"contact_request_accept" = "Acceptar"; +/* Login screen text */ +"login_or_label" = "o"; +/* Login button */ +"log_in" = "Iniciar sessió"; +/* Login screen text */ +"create_account" = "Crear compte"; +/* Password field */ +"password" = "Contrasenya"; +/* Login screen text */ +"create_profile" = "Crear perfil"; +/* Login screen text */ +"create_account_username_title" = "Com et veuran els contactes?"; +/* Login screen text */ +"create_account_username_placeholder" = "Nom d'usuari"; +/* Login screen text */ +"create_account_profile_placeholder" = "Nom del perfil"; +/* Login screen text */ +"import_profile" = "Importar perfil"; +/* Login screen text */ +"import_to_antidote" = "Importar a Antidote"; +/* Login screen text */ +"create_account_profile_hint" = "per exemple: Casa, iPhone"; +/* Login screen text */ +"set_password_title" = "Establir contrasenya"; +/* PIN screen error */ +"pin_do_not_match" = "Els PIN no coincideixen. Torna-ho a provar"; +/* PIN screen error */ +"pin_incorrect" = "PIN incorrecte"; +/* PIN screen error details */ +"pin_failed_attempts" = "Intents fallits: %@"; +/* PIN screen screen */ +"pin_lock_immediately" = "Immediatament"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 segons"; +/* Login button */ +"create_account_go_button" = "Anar"; +/* PIN screen text */ +"pin_set" = "Establir el PIN"; +/* PIN screen text */ +"pin_confirm" = "Confirmar el PIN"; +/* PIN screen screen */ +"pin_enabled" = "Activar el PIN"; +/* PIN screen screen */ +"pin_description" = "Evitar l'accés no autoritzat a Antidote amb un PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "Utilitzar l'empremta com a alternativa al PIN."; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Activar Touch ID"; +/* PIN screen screen */ +"pin_lock_timeout" = "Temps d'autobloqueig"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 minuts"; + +/* Tab name and screen name */ +"contacts_title" = "Contactes"; +/* Tab name and screen name */ +"chats_title" = "Xats"; +/* Tab name and screen name */ +"settings_title" = "Configuració"; +/* Tab name and screen name */ +"profile_title" = "Perfil"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Sense contactes"; +/* Contact request section title */ +"contact_requests_section" = "Sol·licituds de contacte"; +/* Contact last seen status */ +"contact_last_seen" = "Última visualització: %@"; +/* Contact request button */ +"contact_request_decline" = "Rebutjar"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Afegir un contacte\no\n"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 minuts"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "compartir el teu Tox ID"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Contacte eliminat"; + +/* Share Tox ID text */ +"show_qr_code" = "Mostrar el codi QR"; +/* Share Tox ID text */ +"copy" = "Copiar"; + +/* Add contat screen name */ +"add_contact_title" = "Afegir contacte"; +/* Add contat button */ +"add_contact_send" = "Enviar"; +/* Add contat placeholder text */ +"add_contact_or_label" = "o"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Missatge"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Hola! Vols afegir-me?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Codi QR erroni. Ha de contenir el Tox ID"; + +/* User name text */ +"name" = "Nom"; +/* User nickname text */ +"nickname" = "Sobrenom"; +/* User status message text */ +"status_message" = "Missatge d'estat"; +/* User status text */ +"status_title" = "Estat"; +/* User public key text */ +"public_key" = "Clau pública"; +/* Profile menu item / screen name */ +"profile_details" = "Detalls del perfil"; +/* Profile button */ +"logout_button" = "Sortir"; + +/* QR code screen button */ +"qr_close_button" = "Tancar"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Sense xats"; +/* Chats screen message text */ +"chat_outgoing_file" = "Fitxer sortint:"; +/* Chats screen message text */ +"chat_incoming_file" = "Fitxer entrant:"; +/* Chat button */ +"chat_send_button" = "Enviar"; +/* Chat notification toast */ +"chat_new_messages" = "↓ Missatges nous"; +/* Chat call information */ +"chat_call_message" = "Trucada, "; +/* Chat call information */ +"chat_missed_call_message" = "Trucada perduda"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Més"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "està escrivint..."; +/* Status message */ +"status_online" = "Connectat"; + +/* Status message */ +"status_offline" = "No connectat"; +/* Status message */ +"status_away" = "Lluny"; +/* Status message */ +"status_busy" = "Ocupat"; + +/* Notification text */ +"notification_new_message" = "Missatge nou"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Introduïr el Tox ID"; +/* Add contat button */ +"add_contact_use_qr" = "Utilitzar el codi QR"; +/* Share Tox ID text */ +"show_qr" = "Mostrar el QR"; +/* Chats screen message text */ +"chat_unanwered_call" = "Trucada sense resposta"; +/* Notification text */ +"notification_is_calling" = "està trucant"; +/* Notification text */ +"notification_incoming_file" = "Fitxer entrant"; + +/* Settings menu / screen name */ +"settings_about" = "Quant a"; +/* Settings menu / screen name */ +"settings_faq" = "Preguntes freqüents"; +/* About screen menu */ +"settings_antidote_version" = "Versió d'Antidote"; +/* About screen menu */ +"settings_antidote_build" = "Compilació d'Antidote"; +/* About screen menu */ +"settings_toxcore_version" = "Versió de Toxcore"; +/* Settings screen menu */ +"settings_autodownload_images" = "Descàrrega automàtica de fitxers"; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Previsualitzar les notificacions"; +/* Settings screen menu */ +"settings_notifications_description" = "Quan l'aplicació passa a segon pla, rebràs notificacions durant 10 minuts."; +/* Settings screen menu */ +"settings_udp_enabled" = "Activar UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Restaurar la configuració predeterminada"; +/* Settings screen autodownload images option */ +"settings_never" = "Mai"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Utilitzant Wi-Fi"; +/* Profile settings menu */ +"delete_password" = "Eliminar contrasenya"; + +/* Profile settings menu */ +"change_password" = "Canviar contrasenya"; +/* Profile settings menu */ +"old_password" = "Contrasenya antiga"; +/* Change password text */ +"new_password" = "Contrasenya nova"; +/* Change password text */ +"repeat_password" = "Repetir contrasenya"; +/* Change password button */ +"change_password_done" = "Fet"; +/* Change password error */ +"password_is_empty_error" = "La contrasenya no pot de ser buida"; +/* Change password error */ +"wrong_old_password" = "Contrasenya incorrecta"; +/* Change password error */ +"passwords_do_not_match" = "Les contrasenyes no coincideixen"; + +/* Source of photo to take */ +"photo_from_camera" = "Càmera"; +/* Source of photo to take */ +"photo_from_photo_library" = "Fototeca"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "No es pot convertir la imatge"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Enviar a un contacte"; + +/* Profile menu item */ +"export_profile" = "Exportar perfil"; +/* Profile menu item */ +"delete_profile" = "Eliminar perfil"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Estàs segur?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Aquesta operació no es pot desfer"; + +/* Call screen text */ +"call_incoming" = "Trucada entrant"; +/* Call screen text */ +"call_ended" = "Trucada finalitzada"; +/* About screen menu */ +"settings_acknowledgements" = "Agraïments"; + +/* File message text */ +"chat_file_cancelled" = "Cancel·lat"; +/* File message text */ +"chat_waiting" = "En espera..."; +/* File message text */ +"chat_paused" = "En pausa"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Eliminar missatges"; +/* Delete button */ +"alert_delete" = "Eliminar"; +/* Delete button */ +"alert_cancel" = "Cancel·lar"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Introduir el nom d'usuari i el nom del perfil."; +/* Error while creating new profile */ +"login_profile_already_exists" = "Ja existeix un perfil amb aquest nom"; + +/* General error title */ +"error_title" = "Error"; +/* General error button */ +"error_ok_button" = "OK"; +/* Error */ +"error_contact_not_connected" = "El contacte no està connectat"; +/* Error */ +"error_too_many_files" = "Massa transferències de fitxers actives"; +/* Error */ +"error_internal_message" = "Error intern"; +/* Error */ +"error_wrong_password_title" = "Contrasenya incorrecta"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Eliminar aquest xat i els missatges?"; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Eliminar aquesta sol·licitud de contacte?"; +/* Error */ +"error_import_not_exist_message" = "El fitxer no existeix."; +/* Error */ +"error_decrypt_empty_data_message" = "Algunes dades d'entrada estaven buides."; +/* Error */ +"error_decrypt_bad_format_message" = "El fitxer té un format incorrecte o està corrupte."; +/* Error */ +"error_decrypt_wrong_password_message" = "La contrasenya és incorrecta o el fitxer està corrupte."; +/* Error */ +"error_general_unknown_message" = "Error desconegut."; +/* Error */ +"error_general_no_memory_message" = "Memòria insuficient."; +/* Error */ +"error_general_bind_port_message" = "Error en la connexió al port."; +/* Error */ +"error_general_profile_encrypted_message" = "El perfil està xifrat."; +/* Error */ +"error_proxy_title" = "Error de proxy"; +/* Error */ +"error_proxy_invalid_address_message" = "L'adreça de proxy té un format no vàlid."; +/* Error */ +"error_proxy_invalid_port_message" = "El port del proxy no és vàlid."; +/* Error */ +"error_proxy_host_not_resolved_message" = "No s'ha pogut resoldre el proxy."; + +/* Error */ +"error_name_too_long" = "El nom és massa llarg."; +/* Error */ +"error_status_message_too_long" = "El missatge d'estat és massa llarg"; + +/* Error */ +"error_contact_request_too_long" = "Missatge massa llarg"; +/* Error */ +"error_contact_request_no_message" = "No s'ha especificat cap missatge"; +/* Error */ +"error_contact_request_own_key" = "No em puc afegir a la llista de contactes"; +/* Error */ +"error_contact_request_already_sent" = "La sol·licitud de contacte ja s'ha enviat"; +/* Error */ +"error_contact_request_new_nospam" = "Valor de no spam incorrecte, comprova el Tox ID introduït"; +/* Call error */ +"call_error_contact_is_offline" = "El contacte està desconnectat"; +/* Call error */ +"call_error_no_active_call" = "No hi ha cap trucada activa"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "No es pot obrir el tema, format incorrecte"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "xats no llegits"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Establir o eliminar l'avatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Xat"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Obrir el diàleg de xat."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Trucada d'àudio"; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Trucada de vídeo"; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Mostrar el menú de còpia."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Modificar el valor."; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "El teu missatge"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Fer una trucada de vídeo."; +/* Error */ +"error_general_bad_format_message" = "El fitxer té un format incorrecte o està corrupte."; +/* Error */ +"error_contact_request_bad_checksum" = "Suma de comprovació incorrecta, comprova el Tox ID introduït"; +/* Login screen text */ +"create_account_profile_title" = "Nom d'aquest perfil?"; +/* Login screen text */ +"set_password_hint" = "La contrasenya és necessària per a protegir les teves dades. Guarda-la bé: un cop perduda, no es pot recuperar."; +/* Default status message */ +"default_user_status_message" = "Toxing a Antidote"; +/* Login button */ +"create_account_next_button" = "Següent"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Massa intents fallits. La sessió s'ha tancat."; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 minut"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Connectant..."; +/* Screen name */ +"contact_request" = "Sol·licitud de contacte"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Introduïr el PIN per a desbloquejar"; +/* User Tox ID text */ +"my_tox_id" = "El meu Tox ID"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Eliminar la sol·licitud de contacte?"; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Els missatges no lliurats s'enviaran quan el teu amic i tu estigueu connectats."; +/* Chats screen message text */ +"chat_call_finished" = "Trucada finalitzada"; +/* Notification text */ +"notification_incoming_contact_request" = "Sol·licitud de contacte entrant"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Configuració avançada"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Descàrregar automàticament fitxers entrants."; +/* Settings screen autodownload images option */ +"settings_always" = "Sempre"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Eliminar aquest perfil?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Eliminar aquest contacte?\nEs perdrà l'historial de xat."; +/* Call screen text */ +"call_reaching" = "Contactant..."; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Eliminar-ho tot"; +/* Error */ +"error_wrong_password_message" = "La contrasenya conté símbols no permesos."; +/* Error */ +"error_decrypt_title" = "No s'ha pogut desxifrar el fitxer tox"; +/* General error button */ +"error_retry_button" = "Reintentar"; + +/* Call error */ +"call_error_already_in_call" = "Ja en una trucada"; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Missatge"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Fer una trucada d'àudio."; +/* Deleting single message in chat */ +"delete_single_message" = "Eliminar missatge"; +/* Error */ +"error_import_not_exist_title" = "No s'ha pogut importar el fitxer tox"; diff --git a/Antidote/ca.lproj/import-profile.html b/Antidote/ca.lproj/import-profile.html new file mode 100644 index 0000000..7f2b2f3 --- /dev/null +++ b/Antidote/ca.lproj/import-profile.html @@ -0,0 +1,10 @@ + +

To import your Tox profile:

+ +
    +
  1. Send the ".tox" file to your device using any app (Mail, Dropbox, etc.).
  2. +
  3. Use "Open In" menu for this file.
  4. +
  5. Select Antidote in a list of available apps.
  6. +
  7. Check the name of your new profile and press OK.
  8. +
+
diff --git a/Antidote/cs.lproj/InfoPlist.strings b/Antidote/cs.lproj/InfoPlist.strings new file mode 100644 index 0000000..df6a0c0 Binary files /dev/null and b/Antidote/cs.lproj/InfoPlist.strings differ diff --git a/Antidote/cs.lproj/Localizable.strings b/Antidote/cs.lproj/Localizable.strings new file mode 100644 index 0000000..4f3fcf1 --- /dev/null +++ b/Antidote/cs.lproj/Localizable.strings @@ -0,0 +1,411 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* + Translation is done with Transifex service. + See https://www.transifex.com/antidote/antidote-ios/ +*/ + +/* Login screen text */ +"login_or_label" = "nebo"; +/* Login screen text */ +"create_account" = "Vytvořit účet"; +/* Login screen text */ +"import_profile" = "Importovat profil"; +/* Password field */ +"password" = "Heslo"; +/* Login button */ +"log_in" = "Přihlásit"; +/* Login screen text */ +"create_profile" = "Vytvořit profil"; +/* Login screen text */ +"import_to_antidote" = "Importovat do Antidote"; +/* Login screen text */ +"create_account_username_title" = "Jak Vás kontakty uvidí?"; +/* Login screen text */ +"create_account_username_placeholder" = "Uživatelské jméno"; +/* Login screen text */ +"create_account_profile_title" = "Jak nazvete tento profil?"; +/* Login screen text */ +"create_account_profile_placeholder" = "Název profilu"; +/* Login screen text */ +"create_account_profile_hint" = "např. Domací, iPhone"; +/* Login screen text */ +"set_password_title" = "Nastavit heslo"; +/* Login screen text */ +"set_password_hint" = "Heslo je vyžadováno k ochraně Vašich dat. Uložte si jej na bezpečné místo - v budoucnu již nejde obnovit."; +/* Login button */ +"create_account_next_button" = "Další"; +/* Login button */ +"create_account_go_button" = "Založit"; +/* Default status message */ +"default_user_status_message" = "Toxuju přes Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Vložte kód pro odemčení"; +/* PIN screen text */ +"pin_set" = "Nastavit kód"; +/* PIN screen text */ +"pin_confirm" = "Potvrdit kód"; +/* PIN screen error */ +"pin_do_not_match" = "Zadané kódy nesouhlasí. Zkuste to znovu"; +/* PIN screen error */ +"pin_incorrect" = "Špatný kód"; +/* PIN screen error details */ +"pin_failed_attempts" = "Špatných pokusů: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Příliš mnoho špatných pokusů. Účet byl odhlášen."; +/* PIN screen screen */ +"pin_enabled" = "Aktivovat PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Aktivovat Touch ID"; +/* PIN screen screen */ +"pin_description" = "Chraňte Antidote kódem před neoprávněným přístupem."; +/* PIN screen screen */ +"pin_touch_id_description" = "Můžete použít Váš otisk prstu jako alternativu k zadání kódu."; +/* PIN screen screen */ +"pin_lock_timeout" = "Požadovat kód"; +/* PIN screen screen */ +"pin_lock_immediately" = "Ihned"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 sekund"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 minuta"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 minuty"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 minut"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Připojuji..."; + +/* Tab name and screen name */ +"contacts_title" = "Kontakty"; +/* Tab name and screen name */ +"chats_title" = "Konverzace"; +/* Tab name and screen name */ +"settings_title" = "Nastavení"; +/* Tab name and screen name */ +"profile_title" = "Profil"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Žádné kontakty"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Přidat kontakt\nnebo\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "sdílet Vaše Tox ID"; +/* Contact request section title */ +"contact_requests_section" = "Žádosti o kontakt"; +/* Contact last seen status */ +"contact_last_seen" = "Naposledy viděn: %@"; +/* Screen name */ +"contact_request" = "Žádost o kontakt"; +/* Contact request button */ +"contact_request_decline" = "Zamítnout"; +/* Contact request button */ +"contact_request_accept" = "Přijmout"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Smazat žádost?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Smazaný kontakt"; + +/* Share Tox ID text */ +"show_qr_code" = "Zobrazit QR kód"; +/* Share Tox ID text */ +"copy" = "Kopírovat"; + +/* Add contat screen name */ +"add_contact_title" = "Přidat kontakt"; +/* Add contat button */ +"add_contact_send" = "Odeslat"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Vložte Tox ID"; +/* Add contat placeholder text */ +"add_contact_or_label" = "nebo"; +/* Add contat button */ +"add_contact_use_qr" = "načtěte QR kód"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Zpráva"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Ahoj! Můžeš si mě prosím přidat do kontaktů?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Špatný QR kód. Neobsahuje Tox ID"; + +/* User name text */ +"name" = "Jméno"; +/* User nickname text */ +"nickname" = "Přezdívka"; +/* User status message text */ +"status_message" = "Stav"; +/* User status text */ +"status_title" = "Stav"; +/* User Tox ID text */ +"my_tox_id" = "Moje Tox ID"; +/* User public key text */ +"public_key" = "Veřejný klíč"; +/* Share Tox ID text */ +"show_qr" = "Zobrazit QR"; +/* Profile menu item / screen name */ +"profile_details" = "Detaily profilu"; +/* Profile button */ +"logout_button" = "Odhlásit"; + +/* QR code screen button */ +"qr_close_button" = "Zavřít"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Žádné konverzace"; +/* Chats screen message text */ +"chat_outgoing_file" = "Odchozí soubor:"; +/* Chats screen message text */ +"chat_incoming_file" = "Příchozí soubor:"; +/* Chats screen message text */ +"chat_call_finished" = "Hovor ukončen"; +/* Chats screen message text */ +"chat_unanwered_call" = "Nepřijatý hovor"; +/* Chat button */ +"chat_send_button" = "Poslat"; +/* Chat notification toast */ +"chat_new_messages" = "↓ Nová zpráva"; +/* Chat call information */ +"chat_call_message" = "Hovor,"; +/* Chat call information */ +"chat_missed_call_message" = "Zmeškaný hovor"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Více"; + +/* Status message */ +"status_offline" = "Odpojen"; +/* Status message */ +"status_online" = "Připojen"; +/* Status message */ +"status_away" = "Pryč"; +/* Status message */ +"status_busy" = "Nedostupný"; + +/* Notification text */ +"notification_new_message" = "Nová zpráva"; +/* Notification text */ +"notification_incoming_contact_request" = "Příchozí žádost o kontakt"; +/* Notification text */ +"notification_is_calling" = "telefonuje"; +/* Notification text */ +"notification_incoming_file" = "Příchozí soubor"; + +/* Settings menu / screen name */ +"settings_about" = "O Antidote"; +/* Settings menu / screen name */ +"settings_faq" = "FAQ"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Pokročilé nastavení"; +/* About screen menu */ +"settings_antidote_version" = "Verze Antidote"; +/* About screen menu */ +"settings_antidote_build" = "Sestavení Antidote"; +/* About screen menu */ +"settings_toxcore_version" = "Verze Toxcore"; +/* About screen menu */ +"settings_acknowledgements" = "Poděkování"; +/* Settings screen menu */ +"settings_autodownload_images" = "Automaticky stahovat obrázky"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Automaticky stahovat příchozí obrázky"; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Náhled notifikací"; +/* Settings screen menu */ +"settings_notifications_description" = "Jakmile přepnete aplikaci do pozadí, budete dostavát notifikace dalších 10 minut."; +/* Settings screen menu */ +"settings_udp_enabled" = "Aktivovat UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Obnovit výchozí nastavení"; +/* Settings screen autodownload images option */ +"settings_never" = "Nikdy"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Pouze přes Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_always" = "Vždy"; + +/* Profile settings menu */ +"change_password" = "Změnit heslo"; +/* Profile settings menu */ +"delete_password" = "Smazat heslo"; +/* Profile settings menu */ +"old_password" = "Staré heslo"; +/* Change password text */ +"new_password" = "Nové heslo"; +/* Change password text */ +"repeat_password" = "Zopakujte nové heslo"; +/* Change password button */ +"change_password_done" = "Změnit"; +/* Change password error */ +"password_is_empty_error" = "Heslo by němělo být prázdné"; +/* Change password error */ +"wrong_old_password" = "Špatné heslo"; +/* Change password error */ +"passwords_do_not_match" = "Hesla nesedí"; + +/* Source of photo to take */ +"photo_from_camera" = "Pořídit snímek/video"; +/* Source of photo to take */ +"photo_from_photo_library" = "Knihovna obrázků"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Nelze konvertovat obrázek"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Odeslat kontakt"; + +/* Profile menu item */ +"export_profile" = "Exportovat profil"; +/* Profile menu item */ +"delete_profile" = "Smazat profil"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Smazat tento profil?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Jste si jistý?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Tato operace nemůže být vrácena"; + +/* Call screen text */ +"call_incoming" = "Příchozí hovor"; +/* Call screen text */ +"call_ended" = "Hovor ukončen"; +/* Call screen text */ +"call_reaching" = "Vytáčím..."; + +/* File message text */ +"chat_file_cancelled" = "Zrušeno"; +/* File message text */ +"chat_waiting" = "Čekám..."; +/* File message text */ +"chat_paused" = "Pozastaveno"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Smazat tuto konverzaci a její historii?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Smazat tento kontakt?\nHistorie bude ztracena."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Smazat tuto žádost?"; +/* Deleting single message in chat */ +"delete_single_message" = "Smazat zprávu"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Smazané zprávy"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Smazat vše"; +/* Delete button */ +"alert_delete" = "Smazat"; +/* Delete button */ +"alert_cancel" = "Zrušit"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Prosím zadejte uživatelské jméno a název profilu"; +/* Error while creating new profile */ +"login_profile_already_exists" = "Profil s tímto jménem již existuje"; + +/* General error title */ +"error_title" = "Chyba"; +/* General error button */ +"error_ok_button" = "OK"; +/* General error button */ +"error_retry_button" = "Zkusit znovu"; +/* Error */ +"error_contact_not_connected" = "Kontakt není online"; +/* Error */ +"error_too_many_files" = "Příliš mnoho aktivních přenosů"; +/* Error */ +"error_internal_message" = "Interní chyba"; +/* Error */ +"error_wrong_password_title" = "Špatné heslo"; +/* Error */ +"error_wrong_password_message" = "Heslo nemůže obsahovat symboly."; +/* Error */ +"error_import_not_exist_title" = "Nelze importovat tox profil"; +/* Error */ +"error_import_not_exist_message" = "Soubor neexistuje."; +/* Error */ +"error_decrypt_title" = "Nelze rozšifrovat tox profil"; +/* Error */ +"error_decrypt_empty_data_message" = "Některá vstupní data jsou prázdná."; +/* Error */ +"error_decrypt_bad_format_message" = "Soubor má špatný formát nebo je poškozen."; +/* Error */ +"error_decrypt_wrong_password_message" = "Heslo je špatné nebo je poškozen soubor."; +/* Error */ +"error_general_unknown_message" = "Vyskytla se neznámý chyba."; +/* Error */ +"error_general_no_memory_message" = "Nedostatek paměti."; +/* Error */ +"error_general_bind_port_message" = "Nebylo možné se připojit na port"; +/* Error */ +"error_general_profile_encrypted_message" = "Profil je zašifrován."; +/* Error */ +"error_general_bad_format_message" = "Soubor má špatný formát nebo je poškozen."; +/* Error */ +"error_proxy_title" = "Chyba proxy"; +/* Error */ +"error_proxy_invalid_address_message" = "Proxy adresa má špatný formát"; +/* Error */ +"error_proxy_invalid_port_message" = "Port proxy je špatný"; +/* Error */ +"error_proxy_host_not_resolved_message" = "Proxy host nemůže být rozpoznán."; + +/* Error */ +"error_name_too_long" = "Jméno je moc dlouhé"; +/* Error */ +"error_status_message_too_long" = "Stav je moc dlouhý"; + +/* Error */ +"error_contact_request_too_long" = "Zpráva je moc dlouhá"; +/* Error */ +"error_contact_request_no_message" = "Žádná zpráva není specifikována"; +/* Error */ +"error_contact_request_own_key" = "Nemůžu se sám přidat mezi kontakty"; +/* Error */ +"error_contact_request_already_sent" = "Žádost o kontakt byla již odeslána"; +/* Error */ +"error_contact_request_bad_checksum" = "Špatný kontrolní součet, prosím zkontrolujte vložené Tox ID"; +/* Error */ +"error_contact_request_new_nospam" = "Špatná hodnota nospam, prosím zkontrolujte vložené Tox ID"; + +/* Call error */ +"call_error_already_in_call" = "Již hovoří"; +/* Call error */ +"call_error_contact_is_offline" = "Kontakt je offline"; +/* Call error */ +"call_error_no_active_call" = "Není žádný aktivní hovor"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Nemůžu otevřít motiv, špatný formát"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "nepřečtené zprávy"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Nastavit nebo odebrat avatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Zpráva"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Otevře konverzaci"; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Zavolat"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Vytvoří hovor"; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Video"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Vytvoří videohovor"; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Zobrazí menu ke kopírování"; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Upraví položku"; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Zpráva"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Vaše zpráva"; \ No newline at end of file diff --git a/Antidote/cs.lproj/import-profile.html b/Antidote/cs.lproj/import-profile.html new file mode 100644 index 0000000..70837ff --- /dev/null +++ b/Antidote/cs.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

Pro import Tox profilu:

+ +
    +
  1. Přeneste soubor do telefonu (např. emailem, Dropboxem apod.).
  2. +
  3. Použijte "Otevřít v" menu pro tento soubor.
  4. +
  5. Vyberte Antidote v seznamu aplikací.
  6. +
  7. Zkontrolujte jméno vašeho profilu a zvolte OK.
  8. +
diff --git a/Antidote/da.lproj/AppStoreLocalizable.strings b/Antidote/da.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..03d6b2f Binary files /dev/null and b/Antidote/da.lproj/AppStoreLocalizable.strings differ diff --git a/Antidote/da.lproj/InfoPlist.strings b/Antidote/da.lproj/InfoPlist.strings new file mode 100644 index 0000000..ebfec20 Binary files /dev/null and b/Antidote/da.lproj/InfoPlist.strings differ diff --git a/Antidote/da.lproj/Localizable.strings b/Antidote/da.lproj/Localizable.strings new file mode 100644 index 0000000..e240f5b --- /dev/null +++ b/Antidote/da.lproj/Localizable.strings @@ -0,0 +1,411 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* + Translation is done with Transifex service. + See https://www.transifex.com/antidote/antidote-ios/ +*/ + +/* Login screen text */ +"login_or_label" = "eller"; +/* Login screen text */ +"create_account" = "Opret en konto"; +/* Login screen text */ +"import_profile" = "Importer profil"; +/* Password field */ +"password" = "Adgangskode"; +/* Login button */ +"log_in" = "Log ind"; +/* Login screen text */ +"create_profile" = "Opret profil"; +/* Login screen text */ +"import_to_antidote" = "Importer til Antidote"; +/* Login screen text */ +"create_account_username_title" = "Hvordan kontakter vil se dig?"; +/* Login screen text */ +"create_account_username_placeholder" = "Brugernavn"; +/* Login screen text */ +"create_account_profile_title" = "Hvordan du ringer til denne profil?"; +/* Login screen text */ +"create_account_profile_placeholder" = "Profilnavn"; +/* Login screen text */ +"create_account_profile_hint" = "f.eks. Hjem, iPhone"; +/* Login screen text */ +"set_password_title" = "Indtast adgangskode"; +/* Login screen text */ +"set_password_hint" = "Adgangskoden er krævet for at beskytte dine data. Opbevar det et sikkert sted - hvis du mister det kan det ikke gendannes."; +/* Login button */ +"create_account_next_button" = "Næste"; +/* Login button */ +"create_account_go_button" = "Gå til"; +/* Default status message */ +"default_user_status_message" = "Toxer på Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Indtast din PIN-kode for at låse op"; +/* PIN screen text */ +"pin_set" = "Indstil PIN-kode"; +/* PIN screen text */ +"pin_confirm" = "Bekræft PIN-kode"; +/* PIN screen error */ +"pin_do_not_match" = "PIN-koderne er ikke ens. Prøv igen"; +/* PIN screen error */ +"pin_incorrect" = "Forkert PIN-kode"; +/* PIN screen error details */ +"pin_failed_attempts" = "Mislykkedes forsøg: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Det var for mange mislykkede forsøg. Du er blevet logget ud."; +/* PIN screen screen */ +"pin_enabled" = "Aktiver PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Aktiver Touch ID"; +/* PIN screen screen */ +"pin_description" = "Forhindre uautoriseret adgang til Antidote med en PIN-kode."; +/* PIN screen screen */ +"pin_touch_id_description" = "Brug dit fingeraftryk som et alternativ til indtastning af en PIN-kode."; +/* PIN screen screen */ +"pin_lock_timeout" = "Låsnings-timeout"; +/* PIN screen screen */ +"pin_lock_immediately" = "Øjeblikkeligt"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 sekunder"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 minut"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 minutter"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 minutter"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Forbinder..."; + +/* Tab name and screen name */ +"contacts_title" = "Kontakter"; +/* Tab name and screen name */ +"chats_title" = "Samtaler"; +/* Tab name and screen name */ +"settings_title" = "Indstillinger"; +/* Tab name and screen name */ +"profile_title" = "Profil"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Ingen kontakter"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Tilføj en kontakt\neller\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "del dit Tox-ID"; +/* Contact request section title */ +"contact_requests_section" = "Kontakt-anmodninger"; +/* Contact last seen status */ +"contact_last_seen" = "Sidst set: %@"; +/* Screen name */ +"contact_request" = "Kontakt-anmodning"; +/* Contact request button */ +"contact_request_decline" = "Afvis"; +/* Contact request button */ +"contact_request_accept" = "Accepter"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Slet kontakt-anmodning?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Kontaktperson slettet"; + +/* Share Tox ID text */ +"show_qr_code" = "Vis QR-kode"; +/* Share Tox ID text */ +"copy" = "Kopier"; + +/* Add contat screen name */ +"add_contact_title" = "Tilføj kontakt"; +/* Add contat button */ +"add_contact_send" = "Send"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Indtast Tox-ID"; +/* Add contat placeholder text */ +"add_contact_or_label" = "eller"; +/* Add contat button */ +"add_contact_use_qr" = "Brug QR-kode"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Besked"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Hej! Vil du være så venlig at føje mig til din kontakt-liste?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Forkert QR-kode. Det skal indeholde et Tox-ID"; + +/* User name text */ +"name" = "Navn"; +/* User nickname text */ +"nickname" = "Kaldenavn"; +/* User status message text */ +"status_message" = "Status-besked"; +/* User status text */ +"status_title" = "Status"; +/* User Tox ID text */ +"my_tox_id" = "Mit Tox-ID"; +/* User public key text */ +"public_key" = "Offentlig nøgle"; +/* Share Tox ID text */ +"show_qr" = "Vis QR"; +/* Profile menu item / screen name */ +"profile_details" = "Profil-detaljer"; +/* Profile button */ +"logout_button" = "Log ud"; + +/* QR code screen button */ +"qr_close_button" = "Luk"; + +/* Chat screen placeholder */ +"chat_no_chats" = "ingen samtaler"; +/* Chats screen message text */ +"chat_outgoing_file" = "Udgående fil:"; +/* Chats screen message text */ +"chat_incoming_file" = "Indkommende fil:"; +/* Chats screen message text */ +"chat_call_finished" = "Opkald afsluttet"; +/* Chats screen message text */ +"chat_unanwered_call" = "Ubesvaret opkald"; +/* Chat button */ +"chat_send_button" = "Send"; +/* Chat notification toast */ +"chat_new_messages" = "↓ Nye beskeder"; +/* Chat call information */ +"chat_call_message" = "Opkald,"; +/* Chat call information */ +"chat_missed_call_message" = "Mistet opkald"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Mere"; + +/* Status message */ +"status_offline" = "Offline"; +/* Status message */ +"status_online" = "Online"; +/* Status message */ +"status_away" = "Ikke til stede"; +/* Status message */ +"status_busy" = "Optaget"; + +/* Notification text */ +"notification_new_message" = "Ny besked"; +/* Notification text */ +"notification_incoming_contact_request" = "Indkommende kontakt-anmodning"; +/* Notification text */ +"notification_is_calling" = "ringer til dig"; +/* Notification text */ +"notification_incoming_file" = "Indkommende fil"; + +/* Settings menu / screen name */ +"settings_about" = "Om"; +/* Settings menu / screen name */ +"settings_faq" = "FAQ"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Avancerede indstillinger"; +/* About screen menu */ +"settings_antidote_version" = "Antidote version"; +/* About screen menu */ +"settings_antidote_build" = "Antidote Build"; +/* About screen menu */ +"settings_toxcore_version" = "Toxcore Version"; +/* About screen menu */ +"settings_acknowledgements" = "Taksigelser"; +/* Settings screen menu */ +"settings_autodownload_images" = "Hent billeder automatisk"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Hent automatisk indkommende billeder."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Notifikations-forevisning"; +/* Settings screen menu */ +"settings_notifications_description" = "Når appen er i baggrunden vil du stadig modtage notifikationer i op til 10 minutter."; +/* Settings screen menu */ +"settings_udp_enabled" = "Aktiver UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Gendan standardindstillinger"; +/* Settings screen autodownload images option */ +"settings_never" = "Aldrig"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Ved brug af Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_always" = "Altid"; + +/* Profile settings menu */ +"change_password" = "Skift adgangskode"; +/* Profile settings menu */ +"delete_password" = "Slet adgangskode"; +/* Profile settings menu */ +"old_password" = "Gammel adgangskode"; +/* Change password text */ +"new_password" = "Ny adgangskode"; +/* Change password text */ +"repeat_password" = "Gentag ny adgangskode"; +/* Change password button */ +"change_password_done" = "Færdig"; +/* Change password error */ +"password_is_empty_error" = "Adgangskoden skulle ikke være tom"; +/* Change password error */ +"wrong_old_password" = "Forkert adgangskode"; +/* Change password error */ +"passwords_do_not_match" = "Adgangskoderne er ikke ens"; + +/* Source of photo to take */ +"photo_from_camera" = "Kamera"; +/* Source of photo to take */ +"photo_from_photo_library" = "Foto-bibliotek"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Kan ikke konvertere billede"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Send til kontakt"; + +/* Profile menu item */ +"export_profile" = "Eksporter profil"; +/* Profile menu item */ +"delete_profile" = "Slet profil"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Slet denne profil?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Er du sikker?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Denne handling kan ikke fortrydes"; + +/* Call screen text */ +"call_incoming" = "Indkommende opkald"; +/* Call screen text */ +"call_ended" = "Opkald afsluttet"; +/* Call screen text */ +"call_reaching" = "Rækker ud..."; + +/* File message text */ +"chat_file_cancelled" = "Annulleret"; +/* File message text */ +"chat_waiting" = "Venter..."; +/* File message text */ +"chat_paused" = "Sat på pause"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Slet denne chat og besked-historikken?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Slet denne kontakt?\nDin chat-historik vil gå tabt."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Slet denne kontakt-anmodning?"; +/* Deleting single message in chat */ +"delete_single_message" = "Slet besked"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Slet beskeder"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Slet alle"; +/* Delete button */ +"alert_delete" = "Slet"; +/* Delete button */ +"alert_cancel" = "Annuller"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Indtast venligst både brugernavn og profil-navn."; +/* Error while creating new profile */ +"login_profile_already_exists" = "En profil med det angivne navn eksisterer allerede"; + +/* General error title */ +"error_title" = "Fejl"; +/* General error button */ +"error_ok_button" = "OK"; +/* General error button */ +"error_retry_button" = "Prøv igen"; +/* Error */ +"error_contact_not_connected" = "Kontakten er ikke online"; +/* Error */ +"error_too_many_files" = "For mange aktive fil-overførsler"; +/* Error */ +"error_internal_message" = "Intern fejl"; +/* Error */ +"error_wrong_password_title" = "Forkert adgangskode"; +/* Error */ +"error_wrong_password_message" = "Adgangskode indeholder forkerte symboler."; +/* Error */ +"error_import_not_exist_title" = "Tox-arkivfil kan ikke importeres"; +/* Error */ +"error_import_not_exist_message" = "Filen eksisterer ikke."; +/* Error */ +"error_decrypt_title" = "Kan ikke dekryptere Tox-arkivfilen"; +/* Error */ +"error_decrypt_empty_data_message" = "Nogle indtastnings-data var tomme."; +/* Error */ +"error_decrypt_bad_format_message" = "Filen er i et dårligt format, eller beskadiget."; +/* Error */ +"error_decrypt_wrong_password_message" = "Adgangskoden er forkert eller filen er beskadiget."; +/* Error */ +"error_general_unknown_message" = "Der opstod en ukendt fejl."; +/* Error */ +"error_general_no_memory_message" = "Ikke nok hukommelse."; +/* Error */ +"error_general_bind_port_message" = "Kunne ikke binde til en port."; +/* Error */ +"error_general_profile_encrypted_message" = "Profilen er krypteret."; +/* Error */ +"error_general_bad_format_message" = "Filen er i et dårligt format, eller beskadiget."; +/* Error */ +"error_proxy_title" = "Proxy-fejl"; +/* Error */ +"error_proxy_invalid_address_message" = "Proxy-adressen har et ugyldigt format."; +/* Error */ +"error_proxy_invalid_port_message" = "Proxy-porten er ugyldig."; +/* Error */ +"error_proxy_host_not_resolved_message" = "Proxy-værten kunne ikke nås."; + +/* Error */ +"error_name_too_long" = "Navnet er for langt."; +/* Error */ +"error_status_message_too_long" = "Status-beskeden er for lang"; + +/* Error */ +"error_contact_request_too_long" = "Beskeden er for lang"; +/* Error */ +"error_contact_request_no_message" = "Ingen besked specificeret"; +/* Error */ +"error_contact_request_own_key" = "Kan ikke føje mig selv til kontakt-listen"; +/* Error */ +"error_contact_request_already_sent" = "Kontakt-anmodningen var allerede sendt"; +/* Error */ +"error_contact_request_bad_checksum" = "Forkert checksum, tjek venligst indtastet Tox-ID"; +/* Error */ +"error_contact_request_new_nospam" = "Dårlig nospam-værdi, tjek venligst indtasted Tox-ID"; + +/* Call error */ +"call_error_already_in_call" = "Allerede i et opkald"; +/* Call error */ +"call_error_contact_is_offline" = "Kontakten er offline"; +/* Call error */ +"call_error_no_active_call" = "Der er intet aktivt opkald"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Tema kan ikke åbnes, forkert format"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "Ulæste samtaler"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Indstiller eller fjerner avatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Chat"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Åbner chat-samtale."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Taleopkald"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Foretager taleopkald."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Videoopkald"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Foretager videoopkald."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Viser kopier-menu."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Redigerer værdi."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Besked"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Din besked"; \ No newline at end of file diff --git a/Antidote/da.lproj/import-profile.html b/Antidote/da.lproj/import-profile.html new file mode 100644 index 0000000..ca99079 --- /dev/null +++ b/Antidote/da.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

For at importere din Tox-profil:

+ +
    +
  1. Send ".tox"-filen til din enhed ved brug af enhver app (Mail, Dropbox, etc.).
  2. +
  3. Brug "Åben i"-menuen til denne fil.
  4. +
  5. Vælg Antidote på en liste over tilgængelige apps.
  6. +
  7. Tjek navnet på din nye profil og tryk på OK.
  8. +
diff --git a/Antidote/de.lproj/AppStoreLocalizable.strings b/Antidote/de.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..6ab4986 Binary files /dev/null and b/Antidote/de.lproj/AppStoreLocalizable.strings differ diff --git a/Antidote/de.lproj/InfoPlist.strings b/Antidote/de.lproj/InfoPlist.strings new file mode 100644 index 0000000..16732b9 Binary files /dev/null and b/Antidote/de.lproj/InfoPlist.strings differ diff --git a/Antidote/de.lproj/Localizable.strings b/Antidote/de.lproj/Localizable.strings new file mode 100644 index 0000000..4fa95eb --- /dev/null +++ b/Antidote/de.lproj/Localizable.strings @@ -0,0 +1,411 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + + +/* Login screen text */ +"login_or_label" = "oder"; +/* Login screen text */ +"create_account" = "Konto erstellen"; +/* Login screen text */ +"import_profile" = "Profil importieren"; +/* Password field */ +"password" = "Passwort"; +/* Login button */ +"log_in" = "Anmelden"; +/* Login screen text */ +"create_profile" = "Profil erstellen"; +/* Login screen text */ +"import_to_antidote" = "Profil importieren"; +/* Login screen text */ +"create_account_username_title" = "Wie sollen dich Kontakte sehen?"; +/* Login screen text */ +"create_account_username_placeholder" = "Nutzername"; +/* Login screen text */ +"create_account_profile_title" = "Wie soll dieses Profil heißen?"; +/* Login screen text */ +"create_account_profile_placeholder" = "Profilname"; +/* Login screen text */ +"create_account_profile_hint" = "Bsp.: Zu Hause, iPhone"; +/* Login screen text */ +"set_password_title" = "Passwort setzen"; +/* Login screen text */ +"set_password_hint" = "Ein Passwort wird benötigt, um deine Daten zu schützen. Bewahre es gut auf – es kann nicht wiederhergestellt werden wenn es verloren gegangen ist."; +/* Login button */ +"create_account_next_button" = "Weiter"; +/* Login button */ +"create_account_go_button" = "Los"; +/* Default status message */ +"default_user_status_message" = "Toxing mit Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Gibt deine PIN ein zum entsprren"; +/* PIN screen text */ +"pin_set" = "PIN setzen"; +/* PIN screen text */ +"pin_confirm" = "PIN wiederholen"; +/* PIN screen error */ +"pin_do_not_match" = "PIN ist falsch. Probier es noch mal"; +/* PIN screen error */ +"pin_incorrect" = "falsche PIN"; +/* PIN screen error details */ +"pin_failed_attempts" = "Fehlversuche: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Zu viele Fehlversuche. Du wurdest ausgeloggt."; +/* PIN screen screen */ +"pin_enabled" = "Aktiviere Pin"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Aktiviere Touch-ID"; +/* PIN screen screen */ +"pin_description" = "Verhindere unberechtigten Zugriff auf Antidote mittels einer PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "Benutze deinen Fingerabdruck als Alternative zur PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "Zeitsperre"; +/* PIN screen screen */ +"pin_lock_immediately" = "sofort"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 Sekunden"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 Minute"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 Minuten"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 Minuten"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Verbinde ..."; + +/* Tab name and screen name */ +"contacts_title" = "Kontakte"; +/* Tab name and screen name */ +"chats_title" = "Chats"; +/* Tab name and screen name */ +"settings_title" = "Einstellungen"; +/* Tab name and screen name */ +"profile_title" = "Profil"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Keine Kontakte"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Füge einen Kontakt hinzu\noder\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "Teile deine Tox-ID"; +/* Contact request section title */ +"contact_requests_section" = "Kontaktanfragen"; +/* Contact last seen status */ +"contact_last_seen" = "Zuletzt online: %@"; +/* Screen name */ +"contact_request" = "Kontaktanfrage"; +/* Contact request button */ +"contact_request_decline" = "Ablehnen"; +/* Contact request button */ +"contact_request_accept" = "Akzeptieren"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Kontaktanfrage löschen?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Gelöschter Kontakt"; + +/* Share Tox ID text */ +"show_qr_code" = "Zeige QR-Code"; +/* Share Tox ID text */ +"copy" = "Kopieren"; + +/* Add contat screen name */ +"add_contact_title" = "Kontakt hinzufügen"; +/* Add contat button */ +"add_contact_send" = "Hinzufügen"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Tox-ID eingeben"; +/* Add contat placeholder text */ +"add_contact_or_label" = "oder"; +/* Add contat button */ +"add_contact_use_qr" = "Scanne QR-Code"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Nachricht"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Hallo! Würdest du mich bitte zu deiner Kontaktliste hinzufügen?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Falscher QR-Code. Es sollte eine Tox-ID enthalten sein"; + +/* User name text */ +"name" = "Name"; +/* User nickname text */ +"nickname" = "Spitzname"; +/* User status message text */ +"status_message" = "Status Nachricht"; +/* User status text */ +"status_title" = "Status"; +/* User Tox ID text */ +"my_tox_id" = "Meine Tox-ID"; +/* User public key text */ +"public_key" = "Öffentlicher Schlüssel"; +/* Share Tox ID text */ +"show_qr" = "Zeige QR-Code"; +/* Profile menu item / screen name */ +"profile_details" = "Profil Details"; +/* Profile button */ +"logout_button" = "Abmelden"; + +/* QR code screen button */ +"qr_close_button" = "Schließen"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Keine Chats"; +/* Chats screen message text */ +"chat_outgoing_file" = "Ausgehende Datei:"; +/* Chats screen message text */ +"chat_incoming_file" = "Eingehende Datei:"; +/* Chats screen message text */ +"chat_call_finished" = "Anruf beendet"; +/* Chats screen message text */ +"chat_unanwered_call" = "Unbeantworteter Anruf"; +/* Chat button */ +"chat_send_button" = "Senden"; +/* Chat notification toast */ +"chat_new_messages" = "↓ Neue Nachrichten"; +/* Chat call information */ +"chat_call_message" = "Anruf, "; +/* Chat call information */ +"chat_missed_call_message" = "Verpasster Anruf"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Mehr"; + +/* Status message */ +"status_offline" = "Offline"; +/* Status message */ +"status_online" = "Online"; +/* Status message */ +"status_away" = "Abwesend"; +/* Status message */ +"status_busy" = "Beschäftigt"; + +/* Notification text */ +"notification_new_message" = "Neue Nachricht"; +/* Notification text */ +"notification_incoming_contact_request" = "Eingehende Kontaktanfrage"; +/* Notification text */ +"notification_is_calling" = "ruft an"; +/* Notification text */ +"notification_incoming_file" = "Eingehende Datei"; + +/* Settings menu / screen name */ +"settings_about" = "Über Antidote"; +/* Settings menu / screen name */ +"settings_faq" = "FAQ"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Erweiterte Einstellungen"; +/* About screen menu */ +"settings_antidote_version" = "Antidote Version"; +/* About screen menu */ +"settings_antidote_build" = "Antidote-Build"; +/* About screen menu */ +"settings_toxcore_version" = "Toxcore-Version"; +/* About screen menu */ +"settings_acknowledgements" = "Danksagungen"; +/* Settings screen menu */ +"settings_autodownload_images" = "Dateien automatisch herunterladen"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Eingehende Dateien automatisch herunterladen."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Mitteilungsvorschau"; +/* Settings screen menu */ +"settings_notifications_description" = "Wenn die App im Hintergrund ist, wirst du noch für bis zu 10 Minuten Benachrichtigungen erhalten."; +/* Settings screen menu */ +"settings_udp_enabled" = "Aktiviere UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Standardeinstellungen wiederherstellen"; +/* Settings screen autodownload images option */ +"settings_never" = "Nie"; +/* Settings screen autodownload images option */ +"settings_wifi" = "WLAN"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Nur im WLAN"; +/* Settings screen autodownload images option */ +"settings_always" = "Immer"; + +/* Profile settings menu */ +"change_password" = "Passwort ändern"; +/* Profile settings menu */ +"delete_password" = "Passwort löschen"; +/* Profile settings menu */ +"old_password" = "Altes Passwort"; +/* Change password text */ +"new_password" = "Neues Passwort"; +/* Change password text */ +"repeat_password" = "Passwort wiederholen"; +/* Change password button */ +"change_password_done" = "Fertig"; +/* Change password error */ +"password_is_empty_error" = "Passwort darf nicht leer sein"; +/* Change password error */ +"wrong_old_password" = "Falsches passwort"; +/* Change password error */ +"passwords_do_not_match" = "Passwörter stimmen nicht überein"; + +/* Source of photo to take */ +"photo_from_camera" = "Kamera"; +/* Source of photo to take */ +"photo_from_photo_library" = "Fotogalerie"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Kann Bild nicht konvertieren"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Zu einem Kontakt senden"; + +/* Profile menu item */ +"export_profile" = "Profil exportieren"; +/* Profile menu item */ +"delete_profile" = "Profil löschen"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Dieses Profil löschen?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Bist du sicher?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Diese Operation kann nicht rückgängig gemacht werden"; + +/* Call screen text */ +"call_incoming" = "Eingehender Anruf"; +/* Call screen text */ +"call_ended" = "Anruf beendet"; +/* Call screen text */ +"call_reaching" = "Anrufen ..."; + +/* File message text */ +"chat_file_cancelled" = "Abgebrochen"; +/* File message text */ +"chat_waiting" = "Warten ..."; +/* File message text */ +"chat_paused" = "Pausiert"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Diesen Chat und den Chatverlauf löschen?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Diesen Kontakt löschen?\nDer Chatverlauf wird ebenfalls gelöscht."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Diese Kontaktanfrage löschen?"; +/* Deleting single message in chat */ +"delete_single_message" = "Nachricht löschen"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Nachrichten löschen"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Alles löschen"; +/* Delete button */ +"alert_delete" = "Löschen"; +/* Delete button */ +"alert_cancel" = "Abbrechen"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Bitte gib einen Usernamen und einen Profilnamen ein."; +/* Error while creating new profile */ +"login_profile_already_exists" = "Ein Profil mit diesem Namen existiert bereits"; + +/* General error title */ +"error_title" = "Fehler"; +/* General error button */ +"error_ok_button" = "OK"; +/* General error button */ +"error_retry_button" = "Erneut versuchen"; +/* Error */ +"error_contact_not_connected" = "Kontakt ist nicht online"; +/* Error */ +"error_too_many_files" = "Zu viele aktive Dateiübertragungen"; +/* Error */ +"error_internal_message" = "Interner Fehler"; +/* Error */ +"error_wrong_password_title" = "Falsches Passwort"; +/* Error */ +"error_wrong_password_message" = "Passwort enthält ungültige Zeichen."; +/* Error */ +"error_import_not_exist_title" = "Kann Tox-Profil-Datei nicht importieren"; +/* Error */ +"error_import_not_exist_message" = "Datei existiert nicht."; +/* Error */ +"error_decrypt_title" = "Kann Tox-Profil-Datei nicht entschlüsseln"; +/* Error */ +"error_decrypt_empty_data_message" = "Manche Eingabedaten waren leer."; +/* Error */ +"error_decrypt_bad_format_message" = "Datei hat falsches Format oder ist beschädigt."; +/* Error */ +"error_decrypt_wrong_password_message" = "Passwort ist falsch oder Datei ist beschädigt."; +/* Error */ +"error_general_unknown_message" = "Unbekannter Fehler."; +/* Error */ +"error_general_no_memory_message" = "Nicht genügend Arbeitsspeicher."; +/* Error */ +"error_general_bind_port_message" = "Konnte nicht an Port binden."; +/* Error */ +"error_general_profile_encrypted_message" = "Profil ist verschlüsselt."; +/* Error */ +"error_general_bad_format_message" = "Datei hat falsches Format oder ist beschädigt."; +/* Error */ +"error_proxy_title" = "Proxy Fehler"; +/* Error */ +"error_proxy_invalid_address_message" = "Proxy-Adresse hat ein falsches Format."; +/* Error */ +"error_proxy_invalid_port_message" = "Proxy-Port ist ungültig."; +/* Error */ +"error_proxy_host_not_resolved_message" = "Proxy-Host konnte nicht aufgelöst werden."; + +/* Error */ +"error_name_too_long" = "Name ist zu lang."; +/* Error */ +"error_status_message_too_long" = "Status Nachricht ist zu lang"; + +/* Error */ +"error_contact_request_too_long" = "Nachricht ist zu lang"; +/* Error */ +"error_contact_request_no_message" = "Keine Nachricht angegeben"; +/* Error */ +"error_contact_request_own_key" = "Man kann sich nicht selbst zur Kontaktliste hinzufügen"; +/* Error */ +"error_contact_request_already_sent" = "Kontaktanfrage wurde bereits verschickt"; +/* Error */ +"error_contact_request_bad_checksum" = "Falsche Prüfsumme, bitte eingegebene Tox-ID überprüfen"; +/* Error */ +"error_contact_request_new_nospam" = "Falscher No-Spam-Wert, bitte eingegebene Tox-ID überprüfen"; + +/* Call error */ +"call_error_already_in_call" = "Bereits im Gespräch"; +/* Call error */ +"call_error_contact_is_offline" = "Kontakt ist offline"; +/* Call error */ +"call_error_no_active_call" = "Es gibt keinen aktiven Anruf"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Kann Farbschema nicht öffnen, falsches Format"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "ungelesene Chats"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Avatar hinzufügen oder löschen."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Chat"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Öffnet Chat."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Sprachanruf"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Startet Sprachanruf."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Videoanruf"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Startet Videoanruf."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Zeigt Kopiermenü."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Bearbeitet den Wert."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Nachricht"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Deine Nachricht"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "schreibt …"; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Nicht zugestellte Nachrichten werden gesendet, wenn sowohl du als auch dein Freund online sind."; diff --git a/Antidote/de.lproj/import-profile.html b/Antidote/de.lproj/import-profile.html new file mode 100644 index 0000000..13d3441 --- /dev/null +++ b/Antidote/de.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

Um dein Tox-Profil zu importieren:

+ +
    +
  1. Sende die „.tox“-Datei an dein Gerät mit einer beliebigen App (Mail, Dropbox, etc.).
  2. +
  3. Benutze das „Öffnen in“-Menü für diese Datei.
  4. +
  5. Wähle Antidote in der Liste der verfügbaren Apps.
  6. +
  7. Überprüfe den Namen von deinem Profil und klicke auf „OK“.
  8. +
diff --git a/Antidote/default-theme.yaml b/Antidote/default-theme.yaml new file mode 100644 index 0000000..41b7c47 --- /dev/null +++ b/Antidote/default-theme.yaml @@ -0,0 +1,86 @@ +version: 1 + +colors: + white: FFFFFF + transparent_white: FFFFFF55 + transparent_dark_gray: 413839DD + black: "000000" + gray: CCCCCC + lightgraypink: EFEFF4 + darkgray: AAAAAA + color_offline_status: "A3A3A3" + darkgray2: 8C8C8C + lightgrayblue: EDF3F5 + simpleblue: 2E76D3 + mediumblue: 00A7E9 + lightblue: 9CBDE9 + purple: 575CCF + green: 85B452 + green_bright: 00D116 + yellow: FFF300 + pushyellow: FFC533 + red: E94F42 + palered: C56258 + rust: B7410E + +values: + login-background: white + login-gradient: lightblue + login-tox-logo: simpleblue + login-button-text: white + login-button-background: simpleblue + login-description-label: white + login-form-background: white + login-form-text: black + login-link-color: white + + # Color used for fullscreen translucent views background, e.g. fullscreen picker + translucent-background: transparent_white + + # normal background color used all over application + normal-background: white + normal-text: black + link-text: simpleblue + connecting-background: simpleblue + connecting-text: white + separators-and-borders: gray + rounded-button-text: white + rounded-positive-button-background: simpleblue + rounded-negative-button-background: gray + offline-status: color_offline_status + online-status: green_bright + away-status: yellow + busy-status: palered + status-background: white + friend-cell-status: darkgray2 + chat-list-cell-message: darkgray + chat-list-cell-unread-background: lightgrayblue + chat-input-background: lightgrayblue + chat-incoming-bubble: lightgrayblue + chat-outgoing-bubble: purple + chat-outgoing-unread-bubble: rust + chat-outgoing-sentpush-bubble: pushyellow + chat-information-text: darkgray2 + tab-badge-background: palered + tab-badge-text: white + tab-item-active: simpleblue + tab-item-inactive: darkgray2 + notification-background: transparent_dark_gray + notification-text: white + settings-background: lightgraypink + call-text-color: white + call-decline-button-background: red + call-answer-button-background: green + call-control-background: simpleblue + call-control-selected-background: white + call-button-icon-color: white + call-button-selected-icon-color: simpleblue + call-video-preview-background: black + empty-screen-placeholder-text: darkgray + file-image-background-active: darkgray + file-image-cancelled-text: white + file-image-accept-button-tint: white + file-image-cancel-button-tint: black + lock-gradient-top: mediumblue + lock-gradient-bottom: purple + chat-list-cell-arrow-unread-background: red diff --git a/Antidote/dummy-photo.jpg b/Antidote/dummy-photo.jpg new file mode 100644 index 0000000..6c4915f Binary files /dev/null and b/Antidote/dummy-photo.jpg differ diff --git a/Antidote/el.lproj/Localizable.strings b/Antidote/el.lproj/Localizable.strings new file mode 100644 index 0000000..d32a220 --- /dev/null +++ b/Antidote/el.lproj/Localizable.strings @@ -0,0 +1,404 @@ + + +/* Notification text */ +"notification_incoming_contact_request" = "Εισερχόμενο αίτημα επικοινωνίας"; +/* Notification text */ +"notification_is_calling" = "καλεί"; +/* Settings menu / screen name */ +"settings_faq" = "Συχνές ερωτήσεις"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Ρυθμίσεις για προχωρημένους"; +/* About screen menu */ +"settings_antidote_version" = "Έκδοση Antidote"; +/* About screen menu */ +"settings_toxcore_version" = "Έκδοση Toxcore"; +/* About screen menu */ +"settings_acknowledgements" = "Ευχαριστίες"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Αυτόματη λήψη εισερχομένων αρχείων."; +/* Settings screen menu */ +"settings_notifications_description" = "Όταν η εφαρμογή πηγαίνει στο παρασκήνιο, θα εξακολουθείτε να λαμβάνετε ειδοποιήσεις για έως και 10 λεπτά."; +/* Settings screen autodownload images option */ +"settings_never" = "Ποτέ"; +/* Settings screen autodownload images option */ +"settings_always" = "Πάντα"; +/* Call screen text */ +"call_ended" = "Η κλήση τερματίστηκε"; +/* Settings screen menu */ +"settings_autodownload_images" = "Αυτόματη λήψη αρχείων"; +/* Error */ +"error_wrong_password_message" = "Ο κωδικός πρόσβασης περιέχει λάθος σύμβολα."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Προεπισκόπηση ειδοποιήσεων"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Change password text */ +"repeat_password" = "Επαναλάβετε τον κωδικό πρόσβασης"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Μόνο μέσω Wi-Fi"; + +/* Profile settings menu */ +"change_password" = "Αλλαγή κωδικού πρόσβασης"; +/* Profile settings menu */ +"delete_password" = "Διαγραφή κωδικού πρόσβασης"; +/* Change password text */ +"new_password" = "Νέος κωδικός πρόσβασης"; +/* Change password error */ +"password_is_empty_error" = "Ο κωδικός πρόσβασης δεν πρέπει να είναι κενός"; +/* Change password error */ +"wrong_old_password" = "Λάθος κωδικός"; +/* Change password error */ +"passwords_do_not_match" = "Οι κωδικοί πρόσβασης δεν ταιριάζουν"; + +/* Source of photo to take */ +"photo_from_camera" = "Κάμερα"; +/* Profile settings menu */ +"old_password" = "Παλιός κωδικός πρόσβασης"; +/* Change password button */ +"change_password_done" = "Ολοκλήρωση"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Αποστολή σε επαφή"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Να γίνει διαγραφή αυτού του προφίλ;"; + +/* Call screen text */ +"call_incoming" = "Εισερχόμενη κλήση"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Αυτή η λειτουργία δεν μπορεί να αναιρεθεί"; + +/* File message text */ +"chat_file_cancelled" = "Ακυρώθηκε"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Να διαγραφεί αυτό το ιστορικό συνομιλιών και μηνυμάτων;"; +/* Error while creating new profile */ +"login_profile_already_exists" = "Το προφίλ με το συγκεκριμένο όνομα υπάρχει ήδη"; + +/* General error title */ +"error_title" = "Σφάλμα"; +/* General error button */ +"error_ok_button" = "Εντάξει"; +/* Error */ +"error_too_many_files" = "Πάρα πολλές ενεργές μεταφορές αρχείων"; +/* Error */ +"error_import_not_exist_message" = "Το αρχείο δεν υπάρχει."; +/* Error */ +"error_decrypt_bad_format_message" = "Το αρχείο έχει κακή μορφή ή είναι κατεστραμμένο."; +/* Error */ +"error_decrypt_wrong_password_message" = "Ο κωδικός πρόσβασης είναι λάθος ή το αρχείο είναι κατεστραμμένο."; + +/* Error */ +"error_name_too_long" = "Το όνομα είναι πολύ μεγάλο."; +/* Source of photo to take */ +"photo_from_photo_library" = "Βιβλιοθήκη φωτογραφιών"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Δεν είναι δυνατή η μετατροπή της εικόνας"; + +/* Profile menu item */ +"export_profile" = "Εξαγωγή προφίλ"; +/* Profile menu item */ +"delete_profile" = "Διαγραφή προφίλ"; +/* Deleting single message in chat */ +"delete_single_message" = "Διαγραφή μηνύματος"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Διαγραφή μηνυμάτων"; +/* Delete button */ +"alert_cancel" = "Ακύρωση"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Εισαγάγετε όνομα χρήστη και όνομα προφίλ."; +/* General error button */ +"error_retry_button" = "Επανάληψη"; +/* Error */ +"error_contact_not_connected" = "Η επαφή δεν είναι συνδεδεμένη"; +/* Error */ +"error_internal_message" = "Εσωτερικό σφάλμα"; +/* Error */ +"error_wrong_password_title" = "Λάθος κωδικός"; +/* Error */ +"error_import_not_exist_title" = "Δεν είναι δυνατή η εισαγωγή αρχείου αποθήκευσης tox"; +/* Error */ +"error_status_message_too_long" = "Το μήνυμα κατάστασης είναι πολύ μεγάλο"; + +/* Error */ +"error_contact_request_too_long" = "Το μήνυμα είναι πολύ μεγάλο"; +/* Error */ +"error_contact_request_no_message" = "Δεν έχει καθοριστεί μήνυμα"; +/* Error */ +"error_contact_request_bad_checksum" = "Εσφαλμένο άθροισμα ελέγχου, ελέγξτε το εισαγόμενο Tox ID"; +/* File message text */ +"chat_waiting" = "Αναμονή..."; +/* Deleting contat confirmation */ +"delete_contact_title" = "Διαγραφή αυτής της επαφής;\nΤο ιστορικό συνομιλιών σας θα χαθεί."; +/* Delete button */ +"alert_delete" = "Διαγραφή"; +/* Error */ +"error_decrypt_title" = "Δεν είναι δυνατή η αποκρυπτογράφηση του αρχείου αποθήκευσης tox"; +/* Error */ +"error_decrypt_empty_data_message" = "Ορισμένα δεδομένα εισόδου ήταν άδεια."; +/* Error */ +"error_contact_request_already_sent" = "Το αίτημα επικοινωνίας έχει ήδη σταλεί"; +/* Call error */ +"call_error_contact_is_offline" = "Η επαφή είναι εκτός σύνδεσης"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Είστε σίγουροι;"; +/* Call screen text */ +"call_reaching" = "Καλεί..."; +/* File message text */ +"chat_paused" = "Σε παύση"; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Διαγραφή αυτού του αιτήματος επικοινωνίας;"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Διαγραφή όλων"; +/* Error */ +"error_contact_request_own_key" = "Δεν μπορώ να προσθέσω τον εαυτό μου στη λίστα επαφών"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "αδιάβαστες συζητήσεις"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Ανοίγει το παράθυρο διαλόγου συνομιλίας."; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Πραγματοποιεί ηχητική κλήση."; +/* Error */ +"error_contact_request_new_nospam" = "Εσφαλμένη τιμή nospam, ελέγξτε το καταχωρισμένο Tox ID"; + +/* Call error */ +"call_error_already_in_call" = "Ήδη σε κλήση"; +/* Call error */ +"call_error_no_active_call" = "Δεν υπάρχει ενεργή κλήση"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Δεν είναι δυνατό το άνοιγμα του θέματος, λάθος μορφή"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Εικόνα χρήστη"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Ορίζει ή αφαιρεί την εικόνα χρήστη."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Συζήτηση"; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Βιντεοκλήση"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Πραγματοποιεί βιντεοκλήση."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Ηχητική κλήση"; +/* Login screen text */ +"login_or_label" = "ή"; +/* Login screen text */ +"create_account" = "Δημιουργία λογαριασμού"; +/* Login screen text */ +"import_profile" = "Εισαγωγή προφίλ"; +/* Password field */ +"password" = "Κωδικός πρόσβασης"; +/* Login button */ +"log_in" = "Σύνδεση"; +/* Login screen text */ +"create_profile" = "Δημιουργία προφίλ"; +/* Login screen text */ +"import_to_antidote" = "Εισαγωγή στο Antidote"; +/* Login screen text */ +"create_account_username_title" = "Πώς θα σας βλέπουν οι επαφές;"; +/* Login screen text */ +"create_account_username_placeholder" = "Όνομα χρήστη"; +/* Login screen text */ +"create_account_profile_title" = "Πώς ονομάζεται αυτό το προφίλ;"; +/* Login screen text */ +"create_account_profile_hint" = "π.χ. Σπίτι, iPhone"; +/* Login button */ +"create_account_next_button" = "Επόμενο"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Εισαγάγετε το PIN σας για ξεκλείδωμα"; +/* PIN screen text */ +"pin_set" = "Ορισμός PIN"; +/* PIN screen text */ +"pin_confirm" = "Επιβεβαίωση PIN"; +/* PIN screen error */ +"pin_do_not_match" = "Τα PIN δεν ταιριάζουν. Προσπάθησε ξανά"; +/* PIN screen error */ +"pin_incorrect" = "Λανθασμένο PIN"; +/* PIN screen error details */ +"pin_failed_attempts" = "Αποτυχημένες προσπάθειες: %@"; +/* PIN screen screen */ +"pin_enabled" = "Ενεργοποίηση PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Ενεργοποίηση Touch ID"; +/* PIN screen screen */ +"pin_description" = "Αποτρέψτε τη μη εξουσιοδοτημένη πρόσβαση στο Antidote με ένα PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "Λήξη χρονικού ορίου κλειδώματος"; +/* PIN screen screen */ +"pin_lock_immediately" = "Άμεσα"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 δευτερόλεπτα"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 λεπτό"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 λεπτά"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 λεπτά"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Γίνεται σύνδεση..."; + +/* Tab name and screen name */ +"contacts_title" = "Επαφές"; +/* Tab name and screen name */ +"chats_title" = "Συνομιλίες"; +/* Tab name and screen name */ +"settings_title" = "Ρυθμίσεις"; +/* Tab name and screen name */ +"profile_title" = "Προφίλ"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Δεν υπάρχουν επαφές"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "μοιραστείτε το Tox ID σας"; +/* Contact request section title */ +"contact_requests_section" = "Αιτήματα Επικοινωνίας"; +/* Contact last seen status */ +"contact_last_seen" = "Εθεάθη τελευταία: %@"; +/* Screen name */ +"contact_request" = "Αίτημα επικοινωνίας"; +/* Contact request button */ +"contact_request_decline" = "Παρακμή"; +/* Contact request button */ +"contact_request_accept" = "Αποδοχή"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Διαγραμμένη επαφή"; +/* Share Tox ID text */ +"copy" = "Αντιγραφή"; + +/* Add contat screen name */ +"add_contact_title" = "Προσθήκη επαφής"; +/* Login button */ +"create_account_go_button" = "Είσοδος"; +/* Default status message */ +"default_user_status_message" = "Toxing στο Antidote"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Μήνυμα"; +/* Add contat error */ +"add_contact_wrong_qr" = "Λάθος κωδικός QR. Θα πρέπει να περιέχει Tox ID"; + +/* User name text */ +"name" = "Όνομα"; +/* About screen menu */ +"settings_antidote_build" = "Έκδοση Antidote"; +/* Settings screen menu */ +"settings_udp_enabled" = "Ενεργοποίηση UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Επαναφορά αρχικών ρυθμίσεων"; +/* Error */ +"error_general_unknown_message" = "Παρουσιάστηκε άγνωστο σφάλμα."; +/* Error */ +"error_general_no_memory_message" = "Ανεπαρκής μνήμη."; +/* Error */ +"error_general_bind_port_message" = "Δεν ήταν δυνατή η δέσμευση σε μια θύρα."; +/* Error */ +"error_general_profile_encrypted_message" = "Το προφίλ είναι κρυπτογραφημένο."; +/* Error */ +"error_general_bad_format_message" = "Το αρχείο έχει κακή μορφή ή είναι κατεστραμμένο."; +/* Error */ +"error_proxy_title" = "Σφάλμα διακομιστή μεσολάβησης"; +/* Error */ +"error_proxy_invalid_address_message" = "Η διεύθυνση διακομιστή μεσολάβησης έχει μη έγκυρη μορφή."; +/* Error */ +"error_proxy_invalid_port_message" = "Η θύρα διακομιστή μεσολάβησης δεν είναι έγκυρη."; +/* Error */ +"error_proxy_host_not_resolved_message" = "Δεν ήταν δυνατή η επίλυση του διακομιστή μεσολάβησης."; +/* Login screen text */ +"set_password_title" = "Ορίστε τον κωδικό πρόσβασης"; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Εμφανίζει το μενού αντιγραφής."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Επεξεργάζεται την τιμή."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Μήνυμα"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Το μήνυμά σας"; +/* Login screen text */ +"create_account_profile_placeholder" = "Όνομα προφίλ"; +/* Login screen text */ +"set_password_hint" = "Απαιτείται κωδικός πρόσβασης για την προστασία των δεδομένων σας. Διατηρήστε το ασφαλές - μόλις χαθεί δεν μπορεί να αποκατασταθεί ο λογαριασμός σας."; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Πάρα πολλές αποτυχημένες προσπάθειες. Έχετε αποσυνδεθεί."; +/* PIN screen screen */ +"pin_touch_id_description" = "Χρησιμοποιήστε το δακτυλικό σας αποτύπωμα ως εναλλακτική για την εισαγωγή ενός PIN."; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Πρόσθεσε μια επαφή\nή\n"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Διαγραφή αιτήματος επικοινωνίας;"; + +/* Share Tox ID text */ +"show_qr_code" = "Εμφάνιση κωδικού QR"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Εισαγάγετε Tox ID"; +/* Add contat button */ +"add_contact_send" = "Αποστολή"; +/* Add contat placeholder text */ +"add_contact_or_label" = "ή"; +/* Add contat button */ +"add_contact_use_qr" = "Χρήση κωδικού QR"; +/* Status message */ +"status_away" = "Απών"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Γειά σας! Θα μπορούσατε παρακαλώ να με προσθέσετε στη λίστα επαφών σας;"; +/* Share Tox ID text */ +"show_qr" = "Εμφάνιση QR"; +/* Chat notification toast */ +"chat_new_messages" = "↓ Νέα μηνύματα"; +/* User nickname text */ +"nickname" = "Ψευδώνυμο"; +/* User status message text */ +"status_message" = "Μήνυμα κατάστασης"; +/* User status text */ +"status_title" = "Κατάσταση"; +/* User public key text */ +"public_key" = "Δημόσιο Κλειδί"; +/* User Tox ID text */ +"my_tox_id" = "Το Tox ID μου"; + +/* QR code screen button */ +"qr_close_button" = "Κλείσιμο"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Δεν υπάρχουν συνομιλίες"; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Τα μηνύματα που δεν έχουν παραδοθεί θα σταλούν όταν τόσο εσείς όσο και ο φίλος σας είστε συνδεδεμένοι."; +/* Profile menu item / screen name */ +"profile_details" = "Λεπτομέρειες Προφίλ"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Περισσότερα"; +/* Profile button */ +"logout_button" = "Αποσύνδεση"; +/* Chats screen message text */ +"chat_outgoing_file" = "Εξερχόμενο αρχείο:"; +/* Chats screen message text */ +"chat_incoming_file" = "Εισερχόμενο αρχείο:"; +/* Chats screen message text */ +"chat_call_finished" = "Η κλήση ολοκληρώθηκε"; +/* Chats screen message text */ +"chat_unanwered_call" = "Αναπάντητη κλήση"; +/* Chat button */ +"chat_send_button" = "Αποστολή"; +/* Chat call information */ +"chat_call_message" = "Κλήση, "; +/* Chat call information */ +"chat_missed_call_message" = "Αναπάντητη κλήση"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "πληκτρολογεί..."; + +/* Status message */ +"status_offline" = "Εκτός σύνδεσης"; +/* Notification text */ +"notification_incoming_file" = "Εισερχόμενο αρχείο"; + +/* Notification text */ +"notification_new_message" = "Νέο μήνυμα"; + +/* Settings menu / screen name */ +"settings_about" = "Σχετικά με"; +/* Status message */ +"status_online" = "Συνδεμένος/η"; +/* Status message */ +"status_busy" = "Απασχολημένος/η"; diff --git a/Antidote/en.lproj/AppStoreLocalizable.strings b/Antidote/en.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..d6fbb04 --- /dev/null +++ b/Antidote/en.lproj/AppStoreLocalizable.strings @@ -0,0 +1,54 @@ +/* + AppStoreLocalizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/02/17. + Copyright © 2017 dvor. All rights reserved. +*/ + +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_1" = "Mary Cokley"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_2" = "Shirley Knox"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_3" = "Jennifer Smith"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_4" = "Marina Dixon"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_5" = "Carol Ortega"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_1" = "Michael Sharpe"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_2" = "Charles Donahue"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_3" = "Lee Murdock"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_4" = "Wayne Henderson"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_5" = "Robert Newton"; + +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_1" = "Is Antidote really that secure?"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_2" = "sure, it is peer-to-peer"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_3" = "And what does that mean? Peer-to-peer? 😄"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_4" = "you text me directly, the are no servers or things like that"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_5" = "+ it's encrypted 🔐😎"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_6" = "Cool!"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_7" = "I'll give it a go then"; + +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_1" = "😂😂😂"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_2" = "dinner tonight?"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_3" = "I think I know what you are talking about"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_4" = "Sure, thanks!"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_5" = "yep"; diff --git a/Antidote/en.lproj/InfoPlist.strings b/Antidote/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..b79a4d0 --- /dev/null +++ b/Antidote/en.lproj/InfoPlist.strings @@ -0,0 +1,16 @@ +/* + InfoPlist.strings + Antidote + + Created by Dmytro Vorobiov on 15/11/16. + Copyright © 2016 dvor. All rights reserved. +*/ + +/* Camera usage alert description */ +"NSCameraUsageDescription" = "You can use video calls, send photos and videos, scan QR codes."; + +/* Microphone usage alert description */ +"NSMicrophoneUsageDescription" = "You can use audio and video calls."; + +/* Photo library usage alert description */ +"NSPhotoLibraryUsageDescription" = "You can send photos and videos."; \ No newline at end of file diff --git a/Antidote/en.lproj/Localizable.strings b/Antidote/en.lproj/Localizable.strings new file mode 100644 index 0000000..f4933e4 --- /dev/null +++ b/Antidote/en.lproj/Localizable.strings @@ -0,0 +1,410 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* Login screen text */ +"login_or_label" = "or"; +/* Login screen text */ +"create_account" = "Create Account"; +/* Login screen text */ +"import_profile" = "Import Profile"; +/* Password field */ +"password" = "Password"; +/* Login button */ +"log_in" = "Log In"; +/* Login screen text */ +"create_profile" = "Create profile"; +/* Login screen text */ +"import_to_antidote" = "Import to Antidote"; +/* Login screen text */ +"create_account_username_title" = "How contacts will see you?"; +/* Login screen text */ +"create_account_username_placeholder" = "Username"; +/* Login screen text */ +"create_account_profile_title" = "How to call this profile?"; +/* Login screen text */ +"create_account_profile_placeholder" = "Profile name"; +/* Login screen text */ +"create_account_profile_hint" = "e.g. Home, iPhone"; +/* Login screen text */ +"set_password_title" = "Set Password"; +/* Login screen text */ +"set_password_hint" = "Password is required to protect your data. Keep it safe - once lost it cannot be restored."; +/* Login button */ +"create_account_next_button" = "Next"; +/* Login button */ +"create_account_go_button" = "Go"; +/* Default status message */ +"default_user_status_message" = "Toxing on Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Enter your PIN to unlock"; +/* PIN screen text */ +"pin_set" = "Set PIN"; +/* PIN screen text */ +"pin_confirm" = "Confirm PIN"; +/* PIN screen error */ +"pin_do_not_match" = "PINs did not match. Try again"; +/* PIN screen error */ +"pin_incorrect" = "Incorrect PIN"; +/* PIN screen error details */ +"pin_failed_attempts" = "Failed attempts: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Too many failed attempts. You have been logged out."; +/* PIN screen screen */ +"pin_enabled" = "Enable PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Enable Touch ID"; +/* PIN screen screen */ +"pin_description" = "Prevent unauthorized access to Antidote with a PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "Use your fingerprint as an alternative to entering a PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "Lock Timeout"; +/* PIN screen screen */ +"pin_lock_immediately" = "Immediately"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 Seconds"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 Minute"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 Minutes"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 Minutes"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Connecting..."; + +/* Tab name and screen name */ +"contacts_title" = "Contacts"; +/* Tab name and screen name */ +"chats_title" = "Chats"; +/* Tab name and screen name */ +"settings_title" = "Settings"; +/* Tab name and screen name */ +"profile_title" = "Profile"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "No contacts"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Add a contact\nor\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "share your Tox ID"; +/* Contact request section title */ +"contact_requests_section" = "Contact Requests"; +/* Contact last seen status */ +"contact_last_seen" = "Last seen: %@"; +/* Screen name */ +"contact_request" = "Contact Request"; +/* Contact request button */ +"contact_request_decline" = "Decline"; +/* Contact request button */ +"contact_request_accept" = "Accept"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Delete contact request?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Deleted contact"; + +/* Share Tox ID text */ +"show_qr_code" = "Show QR Code"; +/* Share Tox ID text */ +"copy" = "Copy"; + +/* Add contat screen name */ +"add_contact_title" = "Add Contact"; +/* Add contat button */ +"add_contact_send" = "Send"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Enter Tox ID"; +/* Add contat placeholder text */ +"add_contact_or_label" = "or"; +/* Add contat button */ +"add_contact_use_qr" = "Use QR code"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Message"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Hello! Could you please add me to your contact list?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Wrong QR code. It should contain Tox ID"; + +/* User name text */ +"name" = "Name"; +/* User nickname text */ +"nickname" = "Nickname"; +/* User status message text */ +"status_message" = "Status Message"; +/* User status text */ +"status_title" = "Status"; +/* User Tox ID text */ +"my_tox_id" = "My Tox ID"; +/* User public key text */ +"public_key" = "Public Key"; +/* Share Tox ID text */ +"show_qr" = "Show QR"; +/* Profile menu item / screen name */ +"profile_details" = "Profile Details"; +/* Profile button */ +"logout_button" = "Log Out"; + +/* QR code screen button */ +"qr_close_button" = "Close"; + +/* Chat screen placeholder */ +"chat_no_chats" = "No chats"; +/* Chats screen message text */ +"chat_outgoing_file" = "Outgoing file:"; +/* Chats screen message text */ +"chat_incoming_file" = "Incoming file:"; +/* Chats screen message text */ +"chat_call_finished" = "Call finished"; +/* Chats screen message text */ +"chat_unanwered_call" = "Unanswered call"; +/* Chat button */ +"chat_send_button" = "Send"; +/* Chat notification toast */ +"chat_new_messages" = "↓ New messages"; +/* Chat call information */ +"chat_call_message" = "Call, "; +/* Chat call information */ +"chat_missed_call_message" = "Missed call"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "More"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "is typing..."; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Undelivered messages will be send when both you and friend are online."; + +/* Status message */ +"status_offline" = "Offline"; +/* Status message */ +"status_online" = "Online"; +/* Status message */ +"status_away" = "Away"; +/* Status message */ +"status_busy" = "Busy"; + +/* Notification text */ +"notification_new_message" = "New message"; +/* Notification text */ +"notification_incoming_contact_request" = "Incoming contact request"; +/* Notification text */ +"notification_is_calling" = "is calling"; +/* Notification text */ +"notification_incoming_file" = "Incoming file"; + +/* Settings menu / screen name */ +"settings_about" = "About"; +/* Settings menu / screen name */ +"settings_faq" = "FAQ"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Advanced Settings"; +/* About screen menu */ +"settings_antidote_version" = "Antidote Version"; +/* About screen menu */ +"settings_antidote_build" = "Antidote Build"; +/* About screen menu */ +"settings_toxcore_version" = "Toxcore Version"; +/* About screen menu */ +"settings_acknowledgements" = "Acknowledgements"; +/* Settings screen menu */ +"settings_autodownload_images" = "Autodownload files"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Automatically download incoming files."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Notification Preview"; +/* Settings screen menu */ +"settings_notifications_description" = "When application goes to background, you will still receive notifications up to 10 minutes."; +/* Settings screen menu */ +"settings_udp_enabled" = "Enable UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Restore Default Settings"; +/* Settings screen autodownload images option */ +"settings_never" = "Never"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Using Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_always" = "Always"; + +/* Profile settings menu */ +"change_password" = "Change Password"; +/* Profile settings menu */ +"delete_password" = "Delete Password"; +/* Profile settings menu */ +"old_password" = "Old Password"; +/* Change password text */ +"new_password" = "New Password"; +/* Change password text */ +"repeat_password" = "Repeat Password"; +/* Change password button */ +"change_password_done" = "Done"; +/* Change password error */ +"password_is_empty_error" = "Password should be non-empty"; +/* Change password error */ +"wrong_old_password" = "Wrong password"; +/* Change password error */ +"passwords_do_not_match" = "Passwords do not match"; + +/* Source of photo to take */ +"photo_from_camera" = "Camera"; +/* Source of photo to take */ +"photo_from_photo_library" = "Photo Library"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Cannot convert image"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Send to contact"; + +/* Profile menu item */ +"export_profile" = "Export Profile"; +/* Profile menu item */ +"delete_profile" = "Delete Profile"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Delete this profile?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Are you sure?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "This operation cannot be undone"; + +/* Call screen text */ +"call_incoming" = "Incoming call"; +/* Call screen text */ +"call_ended" = "Call ended"; +/* Call screen text */ +"call_reaching" = "Reaching..."; + +/* File message text */ +"chat_file_cancelled" = "Cancelled"; +/* File message text */ +"chat_waiting" = "Waiting..."; +/* File message text */ +"chat_paused" = "Paused"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Delete this chat and message history?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Delete this contact?\nYour chat history will be lost."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Delete this contact request?"; +/* Deleting single message in chat */ +"delete_single_message" = "Delete Message"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Delete Messages"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Delete All"; +/* Delete button */ +"alert_delete" = "Delete"; +/* Delete button */ +"alert_cancel" = "Cancel"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Please enter both username and profile name."; +/* Error while creating new profile */ +"login_profile_already_exists" = "Profile with given name already exists"; + +/* General error title */ +"error_title" = "Error"; +/* General error button */ +"error_ok_button" = "OK"; +/* General error button */ +"error_retry_button" = "Retry"; +/* Error */ +"error_contact_not_connected" = "Contact isn't online"; +/* Error */ +"error_too_many_files" = "Too many active file transfers"; +/* Error */ +"error_internal_message" = "Internal error"; +/* Error */ +"error_wrong_password_title" = "Wrong password"; +/* Error */ +"error_wrong_password_message" = "Password contains wrong symbols."; +/* Error */ +"error_import_not_exist_title" = "Cannot import tox save file"; +/* Error */ +"error_import_not_exist_message" = "File does not exist."; +/* Error */ +"error_decrypt_title" = "Cannot decrypt tox save file"; +/* Error */ +"error_decrypt_empty_data_message" = "Some input data was empty."; +/* Error */ +"error_decrypt_bad_format_message" = "File has bad format or is corrupted."; +/* Error */ +"error_decrypt_wrong_password_message" = "Password is wrong or file is corrupted."; +/* Error */ +"error_general_unknown_message" = "Unknown error occurred."; +/* Error */ +"error_general_no_memory_message" = "Not enough memory."; +/* Error */ +"error_general_bind_port_message" = "Was unable to bind to a port."; +/* Error */ +"error_general_profile_encrypted_message" = "Profile is encrypted."; +/* Error */ +"error_general_bad_format_message" = "File has bad format or is corrupted."; +/* Error */ +"error_proxy_title" = "Proxy error"; +/* Error */ +"error_proxy_invalid_address_message" = "Proxy address has invalid format."; +/* Error */ +"error_proxy_invalid_port_message" = "Proxy port is invalid."; +/* Error */ +"error_proxy_host_not_resolved_message" = "Proxy host could not be resolved."; + +/* Error */ +"error_name_too_long" = "Name is too long."; +/* Error */ +"error_status_message_too_long" = "Status message is too long"; + +/* Error */ +"error_contact_request_too_long" = "Message is too long"; +/* Error */ +"error_contact_request_no_message" = "No message specified"; +/* Error */ +"error_contact_request_own_key" = "Cannot add myself to contact list"; +/* Error */ +"error_contact_request_already_sent" = "Contact request was already sent"; +/* Error */ +"error_contact_request_bad_checksum" = "Bad checksum, please check entered Tox ID"; +/* Error */ +"error_contact_request_new_nospam" = "Bad nospam value, please check entered Tox ID"; + +/* Call error */ +"call_error_already_in_call" = "Already in a call"; +/* Call error */ +"call_error_contact_is_offline" = "Contact is offline"; +/* Call error */ +"call_error_no_active_call" = "There is no active call"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Cannot open theme, wrong format"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "unread chats"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Sets or removes avatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Chat"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Opens chat dialog."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Audio call"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Makes audio call."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Video call"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Makes video call."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Shows copy menu."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Edits value."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Message"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Your message"; diff --git a/Antidote/en.lproj/import-profile.html b/Antidote/en.lproj/import-profile.html new file mode 100644 index 0000000..7f2b2f3 --- /dev/null +++ b/Antidote/en.lproj/import-profile.html @@ -0,0 +1,10 @@ + +

To import your Tox profile:

+ +
    +
  1. Send the ".tox" file to your device using any app (Mail, Dropbox, etc.).
  2. +
  3. Use "Open In" menu for this file.
  4. +
  5. Select Antidote in a list of available apps.
  6. +
  7. Check the name of your new profile and press OK.
  8. +
+
diff --git a/Antidote/enm.lproj/Localizable.strings b/Antidote/enm.lproj/Localizable.strings new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Antidote/enm.lproj/Localizable.strings @@ -0,0 +1 @@ + diff --git a/Antidote/es.lproj/AppStoreLocalizable.strings b/Antidote/es.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..3f83883 Binary files /dev/null and b/Antidote/es.lproj/AppStoreLocalizable.strings differ diff --git a/Antidote/es.lproj/InfoPlist.strings b/Antidote/es.lproj/InfoPlist.strings new file mode 100644 index 0000000..6c333a8 Binary files /dev/null and b/Antidote/es.lproj/InfoPlist.strings differ diff --git a/Antidote/es.lproj/Localizable.strings b/Antidote/es.lproj/Localizable.strings new file mode 100644 index 0000000..29b4782 --- /dev/null +++ b/Antidote/es.lproj/Localizable.strings @@ -0,0 +1,415 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* + Translation is done with Transifex service. + See https://www.transifex.com/antidote/antidote-ios/ +*/ + +/* Login screen text */ +"login_or_label" = "o"; +/* Login screen text */ +"create_account" = "Crear cuenta"; +/* Login screen text */ +"import_profile" = "Importar perfil"; +/* Password field */ +"password" = "Contraseña"; +/* Login button */ +"log_in" = "Iniciar sesión"; +/* Login screen text */ +"create_profile" = "Crear perfil"; +/* Login screen text */ +"import_to_antidote" = "Importar a Antidote"; +/* Login screen text */ +"create_account_username_title" = "¿Cómo te verán los contactos?"; +/* Login screen text */ +"create_account_username_placeholder" = "Nombre de usuario"; +/* Login screen text */ +"create_account_profile_title" = "¿Qué nombre desea ponerle a este perfil?"; +/* Login screen text */ +"create_account_profile_placeholder" = "Nombre del perfil"; +/* Login screen text */ +"create_account_profile_hint" = "p.ej. Casa, iPhone"; +/* Login screen text */ +"set_password_title" = "Establecer contraseña"; +/* Login screen text */ +"set_password_hint" = "La contraseña es necesaria para protejer tus datos. Guárdala en un lugar seguro, si se pierde no se puede recuperar."; +/* Login button */ +"create_account_next_button" = "Siguiente"; +/* Login button */ +"create_account_go_button" = "Ir"; +/* Default status message */ +"default_user_status_message" = "\"Toxing\" en Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Introducir PIN para desbloquear"; +/* PIN screen text */ +"pin_set" = "Establecer PIN"; +/* PIN screen text */ +"pin_confirm" = "Confirmar PIN"; +/* PIN screen error */ +"pin_do_not_match" = "Los PIN son diferentes, inténtelo de nuevo"; +/* PIN screen error */ +"pin_incorrect" = "PIN incorrecto"; +/* PIN screen error details */ +"pin_failed_attempts" = "Intentos fallidos: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Demasiados intentos fallidos. Has sido desconectado."; +/* PIN screen screen */ +"pin_enabled" = "Habilitar PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Habilitar Touch ID"; +/* PIN screen screen */ +"pin_description" = "Evite el acceso no autorizado a Antidote con un PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "Use su huella digital como alternativa a ingresar un PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "Tiempo de autobloqueo"; +/* PIN screen screen */ +"pin_lock_immediately" = "Ahora mismo"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 segundos"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 minuto"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 minutos"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 minutos"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Conectando..."; + +/* Tab name and screen name */ +"contacts_title" = "Contactos"; +/* Tab name and screen name */ +"chats_title" = "Chatear"; +/* Tab name and screen name */ +"settings_title" = "Ajustes"; +/* Tab name and screen name */ +"profile_title" = "Perfil"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Sin contactos"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Añadir un contacto\no\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "compartir su Tox ID"; +/* Contact request section title */ +"contact_requests_section" = "Solicitudes de contacto"; +/* Contact last seen status */ +"contact_last_seen" = "Visto por última vez: %@"; +/* Screen name */ +"contact_request" = "Solicitud de contacto"; +/* Contact request button */ +"contact_request_decline" = "Rechazar"; +/* Contact request button */ +"contact_request_accept" = "Aceptar"; +/* Contact request confirmation */ +"contact_request_delete_title" = "¿Eliminar solicitud de contacto?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Contacto eliminado"; + +/* Share Tox ID text */ +"show_qr_code" = "Mostrar código QR"; +/* Share Tox ID text */ +"copy" = "Copiar"; + +/* Add contat screen name */ +"add_contact_title" = "Añadir contacto"; +/* Add contat button */ +"add_contact_send" = "Enviar"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Introducir Tox ID"; +/* Add contat placeholder text */ +"add_contact_or_label" = "o"; +/* Add contat button */ +"add_contact_use_qr" = "Utilizar código QR"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Mensaje"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "¡Hola! ¿Me añades a tu lista de contactos?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Código QR erróneo. El código debe contener un Tox ID"; + +/* User name text */ +"name" = "Nombre"; +/* User nickname text */ +"nickname" = "Nombre para mostrar"; +/* User status message text */ +"status_message" = "Mensaje de estado"; +/* User status text */ +"status_title" = "Estado"; +/* User Tox ID text */ +"my_tox_id" = "Mi Tox ID"; +/* User public key text */ +"public_key" = "Clave pública"; +/* Share Tox ID text */ +"show_qr" = "Mostrar QR"; +/* Profile menu item / screen name */ +"profile_details" = "Detalles del perfil"; +/* Profile button */ +"logout_button" = "Cerrar sesión"; + +/* QR code screen button */ +"qr_close_button" = "Cerrar"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Sin chats"; +/* Chats screen message text */ +"chat_outgoing_file" = "Archivo saliente:"; +/* Chats screen message text */ +"chat_incoming_file" = "Archivo entrante:"; +/* Chats screen message text */ +"chat_call_finished" = "Llamada finalizada"; +/* Chats screen message text */ +"chat_unanwered_call" = "Llamada sin respuesta"; +/* Chat button */ +"chat_send_button" = "Enviar"; +/* Chat notification toast */ +"chat_new_messages" = "↓ Nuevos mensajes"; +/* Chat call information */ +"chat_call_message" = "Llamada, "; +/* Chat call information */ +"chat_missed_call_message" = "Llamada perdida"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Más"; + +/* Status message */ +"status_offline" = "Sin conexión"; +/* Status message */ +"status_online" = "Conectado"; +/* Status message */ +"status_away" = "Ausente"; +/* Status message */ +"status_busy" = "Ocupado"; + +/* Notification text */ +"notification_new_message" = "Nuevo mensaje"; +/* Notification text */ +"notification_incoming_contact_request" = "Recibiendo solicitud de contacto"; +/* Notification text */ +"notification_is_calling" = "está llamando"; +/* Notification text */ +"notification_incoming_file" = "Archivo entrante"; + +/* Settings menu / screen name */ +"settings_about" = "Acerca de"; +/* Settings menu / screen name */ +"settings_faq" = "FAQ"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Ajustes avanzados"; +/* About screen menu */ +"settings_antidote_version" = "Versión de Antidote"; +/* About screen menu */ +"settings_antidote_build" = "Compilación de Antidote"; +/* About screen menu */ +"settings_toxcore_version" = "Versión de Toxcore"; +/* About screen menu */ +"settings_acknowledgements" = "Reconocimientos"; +/* Settings screen menu */ +"settings_autodownload_images" = "Descarga automática de archivos"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Descarga automáticamente los archivos entrantes."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Previsualización de notificaciones"; +/* Settings screen menu */ +"settings_notifications_description" = "Cuando la aplicación pasa a segundo plano, se seguirán recibiendo notificaciones hasta 10 minutos después."; +/* Settings screen menu */ +"settings_udp_enabled" = "Habilitar UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Restaurar ajustes por defecto"; +/* Settings screen autodownload images option */ +"settings_never" = "Nunca"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Utilizando Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_always" = "Siempre"; + +/* Profile settings menu */ +"change_password" = "Cambiar contraseña"; +/* Profile settings menu */ +"delete_password" = "Eliminar contraseña"; +/* Profile settings menu */ +"old_password" = "Contraseña anterior"; +/* Change password text */ +"new_password" = "Contraseña nueva"; +/* Change password text */ +"repeat_password" = "Repetir contraseña"; +/* Change password button */ +"change_password_done" = "Hecho"; +/* Change password error */ +"password_is_empty_error" = "La contraseña no puede estar vacía"; +/* Change password error */ +"wrong_old_password" = "Contraseña errónea"; +/* Change password error */ +"passwords_do_not_match" = "Las contraseñas no concuerdan"; + +/* Source of photo to take */ +"photo_from_camera" = "Cámara"; +/* Source of photo to take */ +"photo_from_photo_library" = "Biblioteca fotográfica"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "No es posible convertir la imagen"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Enviar a contacto"; + +/* Profile menu item */ +"export_profile" = "Exportar perfil"; +/* Profile menu item */ +"delete_profile" = "Borrar perfil"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "¿Desea borrar este perfil?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "¿Está seguro?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Esta operación no se puede deshacer"; + +/* Call screen text */ +"call_incoming" = "Llamada entrante"; +/* Call screen text */ +"call_ended" = "Llamada finalizada"; +/* Call screen text */ +"call_reaching" = "Llamando..."; + +/* File message text */ +"chat_file_cancelled" = "Cancelada"; +/* File message text */ +"chat_waiting" = "Esperando..."; +/* File message text */ +"chat_paused" = "En pausa"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "¿Borrar este chat y el historial de mensajes?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "¿Eliminar este contacto?\nSu historial de chat será eliminado."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "¿Eliminar la solicitud de contacto?"; +/* Deleting single message in chat */ +"delete_single_message" = "Eliminar mensaje"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Eliminar mensajes"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Eliminar todo"; +/* Delete button */ +"alert_delete" = "Eliminar"; +/* Delete button */ +"alert_cancel" = "Cancelar"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Por favor, introduzca el nombre de usuario y el nombre de perfil."; +/* Error while creating new profile */ +"login_profile_already_exists" = "Ya existe un perfil con ese nombre"; + +/* General error title */ +"error_title" = "Error"; +/* General error button */ +"error_ok_button" = "Ok"; +/* General error button */ +"error_retry_button" = "Reintentar"; +/* Error */ +"error_contact_not_connected" = "El contacto no está conectado"; +/* Error */ +"error_too_many_files" = "Demasiadas transferencias de archivos activas"; +/* Error */ +"error_internal_message" = "Error interno"; +/* Error */ +"error_wrong_password_title" = "Contraseña errónea"; +/* Error */ +"error_wrong_password_message" = "La contraseña contiene símbolos no permitidos."; +/* Error */ +"error_import_not_exist_title" = "No se ha podido importar el archivo de tox"; +/* Error */ +"error_import_not_exist_message" = "El archivo no existe."; +/* Error */ +"error_decrypt_title" = "No se ha podido descifrar el archivo de tox"; +/* Error */ +"error_decrypt_empty_data_message" = "Algunos datos de entrada estaban vacíos."; +/* Error */ +"error_decrypt_bad_format_message" = "El archivo tiene un formato incorrecto o está dañado."; +/* Error */ +"error_decrypt_wrong_password_message" = "La contraseña es incorrecta o el archivo está corrupto."; +/* Error */ +"error_general_unknown_message" = "Error desconocido."; +/* Error */ +"error_general_no_memory_message" = "Memoria insuficiente."; +/* Error */ +"error_general_bind_port_message" = "Error en la conexión al puerto."; +/* Error */ +"error_general_profile_encrypted_message" = "El perfil está cifrado."; +/* Error */ +"error_general_bad_format_message" = "El archivo está mal formateado o está corrupto."; +/* Error */ +"error_proxy_title" = "Error de proxy"; +/* Error */ +"error_proxy_invalid_address_message" = "La dirección de proxy tiene un formato incorrecto."; +/* Error */ +"error_proxy_invalid_port_message" = "El puerto del proxy no es válido."; +/* Error */ +"error_proxy_host_not_resolved_message" = "No se pudo resolver el proxy."; + +/* Error */ +"error_name_too_long" = "El nombre es demasiado largo."; +/* Error */ +"error_status_message_too_long" = "El mensaje de estado es demasiado largo"; + +/* Error */ +"error_contact_request_too_long" = "El mensaje es demasiado largo"; +/* Error */ +"error_contact_request_no_message" = "No se especificó ningún mensaje"; +/* Error */ +"error_contact_request_own_key" = "No se puede añadir a uno mismo a la lista de contactos"; +/* Error */ +"error_contact_request_already_sent" = "La solicitud de contacto fue enviada previamente"; +/* Error */ +"error_contact_request_bad_checksum" = "Fallo en el checksum, por favor compruebe la Tox ID"; +/* Error */ +"error_contact_request_new_nospam" = "Valor de no spam erróneo, por favor compruebe la Tox ID"; + +/* Call error */ +"call_error_already_in_call" = "Ya está en una llamada"; +/* Call error */ +"call_error_contact_is_offline" = "El contacto está desconectado"; +/* Call error */ +"call_error_no_active_call" = "No hay una llamada activa"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "No se puede abrir el tema, formato incorrecto"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "chats sin leer"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Establece o elimina el avatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Chat"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Abre un chat."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Llamada de voz"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Hacer una llamada."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Videollamada"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Realiza una videollamada."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Muestra el menú de copia."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Edita el valor."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Message"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Tu mensaje"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "está escribiendo…"; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Los mensajes no entregados se enviarán cuando tu y tu amigo estéis conectados."; diff --git a/Antidote/es.lproj/import-profile.html b/Antidote/es.lproj/import-profile.html new file mode 100644 index 0000000..c654cd4 --- /dev/null +++ b/Antidote/es.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

Para importar su perfil de Tox:

+ +
    +
  1. Enviar el archivo ".tox" a su dispositivo utilizando cualquier app (Mail, Dropbox, etc.).
  2. +
  3. Usar el menú “Abrir en“ con este archivo.
  4. +
  5. Seleccionar Antidote en la lista de aplicaciones disponibles.
  6. +
  7. Compruebe el nombre de su nuevo perfil y pulse OK.
  8. +
diff --git a/Antidote/fr.lproj/AppStoreLocalizable.strings b/Antidote/fr.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..1e2a7bb Binary files /dev/null and b/Antidote/fr.lproj/AppStoreLocalizable.strings differ diff --git a/Antidote/fr.lproj/InfoPlist.strings b/Antidote/fr.lproj/InfoPlist.strings new file mode 100644 index 0000000..6c333a8 Binary files /dev/null and b/Antidote/fr.lproj/InfoPlist.strings differ diff --git a/Antidote/fr.lproj/Localizable.strings b/Antidote/fr.lproj/Localizable.strings new file mode 100644 index 0000000..df3d70c --- /dev/null +++ b/Antidote/fr.lproj/Localizable.strings @@ -0,0 +1,415 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* + Translation is done with Transifex service. + See https://www.transifex.com/antidote/antidote-ios/ +*/ + +/* Login screen text */ +"login_or_label" = "ou"; +/* Login screen text */ +"create_account" = "Créer un compte"; +/* Login screen text */ +"import_profile" = "Importer un profil"; +/* Password field */ +"password" = "Mot de passe"; +/* Login button */ +"log_in" = "Se connecter"; +/* Login screen text */ +"create_profile" = "Créer un profil"; +/* Login screen text */ +"import_to_antidote" = "Importer vers Antidote"; +/* Login screen text */ +"create_account_username_title" = "Comment les contacts vont-ils vous voir ?"; +/* Login screen text */ +"create_account_username_placeholder" = "Nom d'utilisateur"; +/* Login screen text */ +"create_account_profile_title" = "Comment appeler ce profil ?"; +/* Login screen text */ +"create_account_profile_placeholder" = "Nom du profil"; +/* Login screen text */ +"create_account_profile_hint" = "par exemple, Principal, iPhone"; +/* Login screen text */ +"set_password_title" = "Définir un mot de passe"; +/* Login screen text */ +"set_password_hint" = "Un mot de passe est requis pour protéger vos données. Gardez le en sécurité − une fois perdu il ne pourra pas être restauré."; +/* Login button */ +"create_account_next_button" = "Suivant"; +/* Login button */ +"create_account_go_button" = "Aller"; +/* Default status message */ +"default_user_status_message" = "Je toxe sur Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Entrez votre PIN pour déverrouiller"; +/* PIN screen text */ +"pin_set" = "Définir un code PIN"; +/* PIN screen text */ +"pin_confirm" = "Confirmez le code PIN"; +/* PIN screen error */ +"pin_do_not_match" = "Les codes PIN ne correspondent pas. Réessayez"; +/* PIN screen error */ +"pin_incorrect" = "PIN incorrect"; +/* PIN screen error details */ +"pin_failed_attempts" = "Tentatives échouées : %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Trop de tentatives ratées. Vous avez été déconnecté(e)."; +/* PIN screen screen */ +"pin_enabled" = "Activer le code PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Activer Touch ID"; +/* PIN screen screen */ +"pin_description" = "Empêcher les accès non autorisés à Antidote avec un code PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "Utilisez votre empreinte digitale comme alternative au code PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "Délais de verrouillage"; +/* PIN screen screen */ +"pin_lock_immediately" = "Immédiatement"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 secondes"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 minute"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 minutes"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 minutes"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Chargement..."; + +/* Tab name and screen name */ +"contacts_title" = "Contacts"; +/* Tab name and screen name */ +"chats_title" = "Conversations"; +/* Tab name and screen name */ +"settings_title" = "Réglages"; +/* Tab name and screen name */ +"profile_title" = "Profil"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Il n'y a pas de contacts"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Ajouter un contact\nou\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "Partager votre identité Tox"; +/* Contact request section title */ +"contact_requests_section" = "Invitations"; +/* Contact last seen status */ +"contact_last_seen" = "Vu à : %@"; +/* Screen name */ +"contact_request" = "Invitation"; +/* Contact request button */ +"contact_request_decline" = "Décliner"; +/* Contact request button */ +"contact_request_accept" = "Accepter"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Vous voulez bien supprimer l'invitation ?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Contact supprimé"; + +/* Share Tox ID text */ +"show_qr_code" = "Afficher le code QR"; +/* Share Tox ID text */ +"copy" = "Copier"; + +/* Add contat screen name */ +"add_contact_title" = "Ajouter des contacts"; +/* Add contat button */ +"add_contact_send" = "Envoyer"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Saisir votre identité Tox"; +/* Add contat placeholder text */ +"add_contact_or_label" = "ou"; +/* Add contat button */ +"add_contact_use_qr" = "Utiliser le code QR"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Message"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Salut ! Pourriez-vous m'ajouter à votre liste de contacts ?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Le code QR est mauvais. Il devrait contenir une identité Tox"; + +/* User name text */ +"name" = "Nom"; +/* User nickname text */ +"nickname" = "Pseudonyme"; +/* User status message text */ +"status_message" = "Message de statut"; +/* User status text */ +"status_title" = "Statut"; +/* User Tox ID text */ +"my_tox_id" = "Mon identité Tox"; +/* User public key text */ +"public_key" = "Clé publique"; +/* Share Tox ID text */ +"show_qr" = "Afficher le QR"; +/* Profile menu item / screen name */ +"profile_details" = "Détails du Profil"; +/* Profile button */ +"logout_button" = "Se déconnecter"; + +/* QR code screen button */ +"qr_close_button" = "Quitter"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Il n'y a pas de conversations"; +/* Chats screen message text */ +"chat_outgoing_file" = "Fichier sortant :"; +/* Chats screen message text */ +"chat_incoming_file" = "Fichier entrant :"; +/* Chats screen message text */ +"chat_call_finished" = "Appel terminé"; +/* Chats screen message text */ +"chat_unanwered_call" = "Appel manqué"; +/* Chat button */ +"chat_send_button" = "Envoie"; +/* Chat notification toast */ +"chat_new_messages" = "↓ Nouveaux messages"; +/* Chat call information */ +"chat_call_message" = "Appel, "; +/* Chat call information */ +"chat_missed_call_message" = "Appel manqué"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Plus"; + +/* Status message */ +"status_offline" = "Hors ligne"; +/* Status message */ +"status_online" = "En ligne"; +/* Status message */ +"status_away" = "Absent(e)"; +/* Status message */ +"status_busy" = "Occupé(e)"; + +/* Notification text */ +"notification_new_message" = "Nouveau message"; +/* Notification text */ +"notification_incoming_contact_request" = "Nouvelle invitation"; +/* Notification text */ +"notification_is_calling" = "appelle"; +/* Notification text */ +"notification_incoming_file" = "Fichier entrant"; + +/* Settings menu / screen name */ +"settings_about" = "À propos"; +/* Settings menu / screen name */ +"settings_faq" = "FAQ"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Réglages Avancés"; +/* About screen menu */ +"settings_antidote_version" = "Version Antidote"; +/* About screen menu */ +"settings_antidote_build" = "Build Antidote"; +/* About screen menu */ +"settings_toxcore_version" = "Version Toxcore"; +/* About screen menu */ +"settings_acknowledgements" = "Remerciements"; +/* Settings screen menu */ +"settings_autodownload_images" = "Télécharger automatiquement les fichiers"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Télécharger automatiquement les fichiers entrants."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Aperçu des notifications"; +/* Settings screen menu */ +"settings_notifications_description" = "Lorsque l'application sera en arrière-plan, vous recevrez encore des notifications pendant 10 minutes."; +/* Settings screen menu */ +"settings_udp_enabled" = "Activer UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Restaurer les Réglages par Défaut"; +/* Settings screen autodownload images option */ +"settings_never" = "Jamais"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Utilise le Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_always" = "Toujours"; + +/* Profile settings menu */ +"change_password" = "Changer de mot de passe"; +/* Profile settings menu */ +"delete_password" = "Supprimer le mot de passe"; +/* Profile settings menu */ +"old_password" = "Ancien mot de passe"; +/* Change password text */ +"new_password" = "Nouveau mot de pass"; +/* Change password text */ +"repeat_password" = "Mot de passe encore"; +/* Change password button */ +"change_password_done" = "Fini"; +/* Change password error */ +"password_is_empty_error" = "Le mot de pass ne devrait pas être vide"; +/* Change password error */ +"wrong_old_password" = "Mauvais mot de passe"; +/* Change password error */ +"passwords_do_not_match" = "Les mots de passe ne se ressemblent pas"; + +/* Source of photo to take */ +"photo_from_camera" = "Appareil photo"; +/* Source of photo to take */ +"photo_from_photo_library" = "Photothèque"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Impossible de convertir l'image"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Envoyer à un contact"; + +/* Profile menu item */ +"export_profile" = "Exporter le Profil"; +/* Profile menu item */ +"delete_profile" = "Supprimer le Profil"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Vous voulez bien supprimer ce profil ?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Confirmez-vous ?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Cette opération ne peut pas être annulée"; + +/* Call screen text */ +"call_incoming" = "Appel entrant"; +/* Call screen text */ +"call_ended" = "Appel terminé"; +/* Call screen text */ +"call_reaching" = "En attente…"; + +/* File message text */ +"chat_file_cancelled" = "Annulé"; +/* File message text */ +"chat_waiting" = "En attente…"; +/* File message text */ +"chat_paused" = "Pause"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Vous voulez bien supprimer cette conversation et l'historique des messages ?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Vous voulez bien supprimer ce contact ?\nL'historique de la conversation va être supprimée."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Vous voulez bien supprimer cette invitation ?"; +/* Deleting single message in chat */ +"delete_single_message" = "Supprimer le message"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Supprimer les messages"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Tout supprimer"; +/* Delete button */ +"alert_delete" = "Supprimer"; +/* Delete button */ +"alert_cancel" = "Annuler"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Veuillez saisir un nom d'utilisateur et un nom de profil."; +/* Error while creating new profile */ +"login_profile_already_exists" = "Un profil de ce nom existe déjà"; + +/* General error title */ +"error_title" = "Erreur"; +/* General error button */ +"error_ok_button" = "OK"; +/* General error button */ +"error_retry_button" = "Essayer de nouveau"; +/* Error */ +"error_contact_not_connected" = "Le contact n'est pas connecté"; +/* Error */ +"error_too_many_files" = "Trop de transferts de fichiers actifs"; +/* Error */ +"error_internal_message" = "Erreur interne"; +/* Error */ +"error_wrong_password_title" = "Mauvais mot de passe"; +/* Error */ +"error_wrong_password_message" = "Le mot de passe contient de mauvais symboles."; +/* Error */ +"error_import_not_exist_title" = "Impossible d'importer le fichier tox"; +/* Error */ +"error_import_not_exist_message" = "Le fichier n'existe pas."; +/* Error */ +"error_decrypt_title" = "Impossible de décrypter le fichier tox"; +/* Error */ +"error_decrypt_empty_data_message" = "Certaines données d'entrée étaient vides."; +/* Error */ +"error_decrypt_bad_format_message" = "Le fichier a un mauvais format ou est corrompu."; +/* Error */ +"error_decrypt_wrong_password_message" = "Le mot de passe est mauvais ou le fichier est corrompu."; +/* Error */ +"error_general_unknown_message" = "Une erreur inconnue s'est produite."; +/* Error */ +"error_general_no_memory_message" = "Il n'y a pas assez de mémoire."; +/* Error */ +"error_general_bind_port_message" = "Impossible de se lier à un port."; +/* Error */ +"error_general_profile_encrypted_message" = "Le profil est chiffré."; +/* Error */ +"error_general_bad_format_message" = "Le fichier a un mauvais format ou est corrompu."; +/* Error */ +"error_proxy_title" = "Erreur proxy"; +/* Error */ +"error_proxy_invalid_address_message" = "L'adresse proxy a un format invalide."; +/* Error */ +"error_proxy_invalid_port_message" = "Le port proxy est invalide."; +/* Error */ +"error_proxy_host_not_resolved_message" = "L'hôte proxy n'a pas pû être résolu."; + +/* Error */ +"error_name_too_long" = "Le nom est trop long."; +/* Error */ +"error_status_message_too_long" = "Le message de statut est trop long"; + +/* Error */ +"error_contact_request_too_long" = "Le message est trop long"; +/* Error */ +"error_contact_request_no_message" = "Aucun message spécifié"; +/* Error */ +"error_contact_request_own_key" = "Je ne peux pas m'ajouter à ma liste de contacts"; +/* Error */ +"error_contact_request_already_sent" = "L'invitation a déjà été envoyée"; +/* Error */ +"error_contact_request_bad_checksum" = "Mauvaise somme de contrôle, veuillez vérifier l'identité Tox saisie"; +/* Error */ +"error_contact_request_new_nospam" = "Mauvais nospam, veuillez vous assurer d'avoir saisi la bonne identité Tox"; + +/* Call error */ +"call_error_already_in_call" = "Déjà participant à un appel"; +/* Call error */ +"call_error_contact_is_offline" = "Le contact est déconnecté"; +/* Call error */ +"call_error_no_active_call" = "Il n'y a pas d'appel actif"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Impossible d'ouvrir le thème, mauvais format"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "discussions non lues"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Définit ou supprime l'avatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Discussion"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Ouvre la boîte de dialogue."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Appel audio"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Effectue un appel audio."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Appel vidéo"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Effectue un appel vidéo."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Affiche le menu de copie."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Modifie la valeur."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Message"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Votre message"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "écrit…"; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Les messages non distribués seront envoyés lorsque vous et votre ami(e) serez en ligne."; diff --git a/Antidote/fr.lproj/import-profile.html b/Antidote/fr.lproj/import-profile.html new file mode 100644 index 0000000..bc36255 --- /dev/null +++ b/Antidote/fr.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

Pour importer votre fichier Tox:

+ +
    +
  1. Envoyer le fichier ".tox" vers votre appareil en utilisant n'importe quelle application (Mail, Dropbox, etc.).
  2. +
  3. Utiliser le menu "Ouvrir Dans" pour ce fichier.
  4. +
  5. Sélectionner Antidote de la liste des applications disponibles.
  6. +
  7. Vérifier le nom de votre nouveau profil et appuyer sur OK.
  8. +
diff --git a/Antidote/iPadFriendsButton.swift b/Antidote/iPadFriendsButton.swift new file mode 100644 index 0000000..2b093be --- /dev/null +++ b/Antidote/iPadFriendsButton.swift @@ -0,0 +1,91 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let BadgeHorizontalOffset = 5.0 + static let BadgeMinimumWidth = 22.0 + static let BadgeHeight: CGFloat = 18.0 + static let BadgeRightOffset = -10.0 +} + +class iPadFriendsButton: UIView { + var didTapHandler: (() -> Void)? + + var badgeText: String? { + didSet { + badgeLabel.text = badgeText + badgeContainer.isHidden = (badgeText == nil) + } + } + + fileprivate var badgeContainer: UIView! + fileprivate var badgeLabel: UILabel! + fileprivate var button: UIButton! + + init(theme: Theme) { + super.init(frame: CGRect.zero) + + createViews(theme) + installConstraints() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension iPadFriendsButton { + @objc func buttonPressed() { + didTapHandler?() + } +} + +private extension iPadFriendsButton { + func createViews(_ theme: Theme) { + badgeContainer = UIView() + badgeContainer.backgroundColor = theme.colorForType(.TabBadgeBackground) + badgeContainer.layer.masksToBounds = true + badgeContainer.layer.cornerRadius = Constants.BadgeHeight / 2 + addSubview(badgeContainer) + + badgeLabel = UILabel() + badgeLabel.textColor = theme.colorForType(.TabBadgeText) + badgeLabel.textAlignment = .center + badgeLabel.backgroundColor = .clear + badgeLabel.font = UIFont.antidoteFontWithSize(14.0, weight: .light) + badgeContainer.addSubview(badgeLabel) + + button = UIButton(type: .system) + button.contentHorizontalAlignment = .left + button.contentEdgeInsets.left = 20.0 + button.titleEdgeInsets.left = 20.0 + button.titleLabel?.font = UIFont.systemFont(ofSize: 18.0) + button.setTitle(String(localized: "contacts_title"), for: UIControlState()) + button.setImage(UIImage(named: "tab-bar-friends"), for: UIControlState()) + button.addTarget(self, action: #selector(iPadFriendsButton.buttonPressed), for: .touchUpInside) + addSubview(button) + } + + func installConstraints() { + badgeContainer.snp.makeConstraints { + $0.trailing.equalTo(self).offset(Constants.BadgeRightOffset) + $0.centerY.equalTo(self) + $0.width.greaterThanOrEqualTo(Constants.BadgeMinimumWidth) + $0.height.equalTo(Constants.BadgeHeight) + } + + badgeLabel.snp.makeConstraints { + $0.leading.equalTo(badgeContainer).offset(Constants.BadgeHorizontalOffset) + $0.trailing.equalTo(badgeContainer).offset(-Constants.BadgeHorizontalOffset) + $0.centerY.equalTo(badgeContainer) + } + + button.snp.makeConstraints { + $0.edges.equalTo(self) + } + } +} diff --git a/Antidote/iPadNavigationView.swift b/Antidote/iPadNavigationView.swift new file mode 100644 index 0000000..b3ff6dc --- /dev/null +++ b/Antidote/iPadNavigationView.swift @@ -0,0 +1,73 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import SnapKit + +private struct Constants { + static let Width = 200.0 + static let Height = 34.0 + static let Offset = 12.0 +} + +class iPadNavigationView: UIView { + var avatarView: ImageViewWithStatus! + var label: UILabel! + + var didTapHandler: (() -> Void)? + + fileprivate var button: UIButton! + + init(theme: Theme) { + super.init(frame: CGRect(x: 0.0, y: 0.0, width: Constants.Width, height: Constants.Height)) + + createViews(theme) + installConstraints() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension iPadNavigationView { + @objc func buttonPressed() { + didTapHandler?() + } +} + +private extension iPadNavigationView { + func createViews(_ theme: Theme) { + avatarView = ImageViewWithStatus() + avatarView.userStatusView.theme = theme + addSubview(avatarView) + + label = UILabel() + label.font = UIFont.systemFont(ofSize: 22.0) + addSubview(label) + + button = UIButton() + button.addTarget(self, action: #selector(iPadNavigationView.buttonPressed), for: .touchUpInside) + addSubview(button) + } + + func installConstraints() { + avatarView.snp.makeConstraints { + $0.top.equalTo(self) + $0.leading.equalTo(self) + $0.size.equalTo(Constants.Height) + } + + label.snp.makeConstraints { + $0.leading.equalTo(avatarView.snp.trailing).offset(Constants.Offset) + $0.trailing.equalTo(self) + $0.top.equalTo(self) + $0.bottom.equalTo(self) + } + + button.snp.makeConstraints { + $0.edges.equalTo(self) + } + } +} diff --git a/Antidote/icons8-key-1024.png b/Antidote/icons8-key-1024.png new file mode 100644 index 0000000..8ca885e Binary files /dev/null and b/Antidote/icons8-key-1024.png differ diff --git a/Antidote/isotoxin_Calltone.aac b/Antidote/isotoxin_Calltone.aac new file mode 100644 index 0000000..9756d55 Binary files /dev/null and b/Antidote/isotoxin_Calltone.aac differ diff --git a/Antidote/isotoxin_Hangup.aac b/Antidote/isotoxin_Hangup.aac new file mode 100644 index 0000000..0f913c5 Binary files /dev/null and b/Antidote/isotoxin_Hangup.aac differ diff --git a/Antidote/isotoxin_NewMessage.aac b/Antidote/isotoxin_NewMessage.aac new file mode 100644 index 0000000..90af809 Binary files /dev/null and b/Antidote/isotoxin_NewMessage.aac differ diff --git a/Antidote/isotoxin_Ringtone.aac b/Antidote/isotoxin_Ringtone.aac new file mode 100644 index 0000000..038922e Binary files /dev/null and b/Antidote/isotoxin_Ringtone.aac differ diff --git a/Antidote/isotoxin_RingtoneWhileCall.aac b/Antidote/isotoxin_RingtoneWhileCall.aac new file mode 100644 index 0000000..6b1cd03 Binary files /dev/null and b/Antidote/isotoxin_RingtoneWhileCall.aac differ diff --git a/Antidote/it.lproj/AppStoreLocalizable.strings b/Antidote/it.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..d6fbb04 --- /dev/null +++ b/Antidote/it.lproj/AppStoreLocalizable.strings @@ -0,0 +1,54 @@ +/* + AppStoreLocalizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/02/17. + Copyright © 2017 dvor. All rights reserved. +*/ + +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_1" = "Mary Cokley"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_2" = "Shirley Knox"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_3" = "Jennifer Smith"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_4" = "Marina Dixon"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_5" = "Carol Ortega"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_1" = "Michael Sharpe"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_2" = "Charles Donahue"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_3" = "Lee Murdock"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_4" = "Wayne Henderson"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_5" = "Robert Newton"; + +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_1" = "Is Antidote really that secure?"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_2" = "sure, it is peer-to-peer"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_3" = "And what does that mean? Peer-to-peer? 😄"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_4" = "you text me directly, the are no servers or things like that"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_5" = "+ it's encrypted 🔐😎"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_6" = "Cool!"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_7" = "I'll give it a go then"; + +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_1" = "😂😂😂"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_2" = "dinner tonight?"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_3" = "I think I know what you are talking about"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_4" = "Sure, thanks!"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_5" = "yep"; diff --git a/Antidote/it.lproj/InfoPlist.strings b/Antidote/it.lproj/InfoPlist.strings new file mode 100644 index 0000000..b79a4d0 --- /dev/null +++ b/Antidote/it.lproj/InfoPlist.strings @@ -0,0 +1,16 @@ +/* + InfoPlist.strings + Antidote + + Created by Dmytro Vorobiov on 15/11/16. + Copyright © 2016 dvor. All rights reserved. +*/ + +/* Camera usage alert description */ +"NSCameraUsageDescription" = "You can use video calls, send photos and videos, scan QR codes."; + +/* Microphone usage alert description */ +"NSMicrophoneUsageDescription" = "You can use audio and video calls."; + +/* Photo library usage alert description */ +"NSPhotoLibraryUsageDescription" = "You can send photos and videos."; \ No newline at end of file diff --git a/Antidote/it.lproj/Localizable.strings b/Antidote/it.lproj/Localizable.strings new file mode 100644 index 0000000..3a35810 --- /dev/null +++ b/Antidote/it.lproj/Localizable.strings @@ -0,0 +1,404 @@ + + +/* Login button */ +"log_in" = "Accedi"; +/* Login screen text */ +"login_or_label" = "o"; +/* Login screen text */ +"create_account" = "Crea un account"; +/* Login screen text */ +"import_profile" = "Importa un profilo"; +/* Password field */ +"password" = "Password"; +/* Login screen text */ +"create_profile" = "Crea un profilo"; +/* Login screen text */ +"create_account_username_placeholder" = "Nome utente"; +/* Login screen text */ +"create_account_profile_hint" = "ad esempio, Principale, iPhone"; +/* Login screen text */ +"set_password_title" = "Imposta una password"; +/* Login screen text */ +"set_password_hint" = "La password è necessaria per proteggere i tuoi dati. Tienila al sicuro – una volta persa non può essere ripristinata."; +/* Login button */ +"create_account_next_button" = "Successivo"; +/* Login button */ +"create_account_go_button" = "Vai"; +/* Default status message */ +"default_user_status_message" = "Toxando su Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Inserisci il tuo codice per sbloccare"; +/* PIN screen text */ +"pin_set" = "Imposta PIN"; +/* PIN screen text */ +"pin_confirm" = "Conferma PIN"; +/* PIN screen error */ +"pin_incorrect" = "PIN errato"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Abilita Touch ID"; +/* PIN screen screen */ +"pin_description" = "Impedisci l'accesso non autorizzato ad Antidote con un PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "Usa la tua impronta digitale come alternativa all'inserimento del PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "Tempo prima del blocco"; +/* PIN screen screen */ +"pin_lock_immediately" = "Immediatamente"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 minuto"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 minuti"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Connessione in corso…"; + +/* Tab name and screen name */ +"contacts_title" = "Contatti"; +/* Tab name and screen name */ +"chats_title" = "Discussioni"; +/* Tab name and screen name */ +"settings_title" = "Impostazioni"; +/* Tab name and screen name */ +"profile_title" = "Profilo"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Nessun contatto"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Aggiungi un contatto\no\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "condividi il tuo ID Tox"; +/* Contact request section title */ +"contact_requests_section" = "Richieste di contatto"; +/* Contact request button */ +"contact_request_accept" = "Accetta"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Eliminare la richiesta di contatto?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Contatto eliminato"; + +/* Add contat screen name */ +"add_contact_title" = "Aggiungi un contatto"; +/* Add contat button */ +"add_contact_send" = "Invia"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Inserisci il ID Tox"; +/* Add contat placeholder text */ +"add_contact_or_label" = "o"; +/* Add contat button */ +"add_contact_use_qr" = "Usa il codice QR"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Ciao! Potresti aggiungermi alla tua lista di contatti?"; +/* Contact request button */ +"contact_request_decline" = "Declino"; +/* Profile button */ +"logout_button" = "Esci"; + +/* QR code screen button */ +"qr_close_button" = "Chiudi"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Nessuna discussione"; +/* Chats screen message text */ +"chat_outgoing_file" = "File in uscita:"; +/* Chats screen message text */ +"chat_incoming_file" = "File in arrivo:"; +/* Chats screen message text */ +"chat_call_finished" = "Chiamata finita"; +/* Chat button */ +"chat_send_button" = "Invia"; +/* Chat notification toast */ +"chat_new_messages" = "↓ Nuovi messaggi"; +/* Chat call information */ +"chat_call_message" = "Chiamata, "; +/* Chat call information */ +"chat_missed_call_message" = "Chiamata persa"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Più"; + +/* Status message */ +"status_offline" = "Fuori linea"; +/* Status message */ +"status_online" = "In linea"; +/* Status message */ +"status_away" = "Assente"; +/* Status message */ +"status_busy" = "Occupato/a"; + +/* Notification text */ +"notification_new_message" = "Nuovo messaggio"; +/* Notification text */ +"notification_incoming_contact_request" = "Richiesta di contatto in arrivo"; +/* Notification text */ +"notification_is_calling" = "sta chiamando"; +/* Notification text */ +"notification_incoming_file" = "File in arrivo"; +/* Settings menu / screen name */ +"settings_faq" = "Domande frequenti"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Impostazioni avanzate"; + +/* Settings menu / screen name */ +"settings_about" = "Informazioni"; +/* About screen menu */ +"settings_antidote_version" = "Versione di Antidote"; +/* About screen menu */ +"settings_antidote_build" = "Build di Antidote"; +/* About screen menu */ +"settings_toxcore_version" = "Versione di Toxcore"; +/* About screen menu */ +"settings_acknowledgements" = "Ringraziamenti"; +/* Settings screen menu */ +"settings_autodownload_images" = "Scarica automaticamente le immagini"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Scarica automaticamente le immagini in arrivo."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Anteprima della notifica"; +/* Settings screen menu */ +"settings_notifications_description" = "Quando l'applicazione va nello sfondo, riceverai ancora le notifiche fino a 10 minuti."; +/* Settings screen menu */ +"settings_udp_enabled" = "Abilita UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Ripristina le impostazioni predefinite"; +/* Settings screen autodownload images option */ +"settings_never" = "Mai"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Usando il Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_always" = "Sempre"; + +/* Profile settings menu */ +"change_password" = "Cambia la password"; +/* Profile settings menu */ +"delete_password" = "Elimina la password"; +/* Profile settings menu */ +"old_password" = "Vecchia password"; +/* Change password text */ +"new_password" = "Nuova password"; +/* Change password text */ +"repeat_password" = "Ripeti la password"; +/* Change password button */ +"change_password_done" = "Finito"; +/* Change password error */ +"password_is_empty_error" = "La password non deve essere vuota"; +/* Change password error */ +"wrong_old_password" = "Password sbagliata"; +/* Change password error */ +"passwords_do_not_match" = "Le password non corrispondono"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Impossibile convertire l'immagine"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Invia al contatto"; + +/* Profile menu item */ +"export_profile" = "Esporta il profilo"; +/* Profile menu item */ +"delete_profile" = "Elimina il profilo"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Sei sicuro/a?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Questa operazione non può essere annullata"; + +/* Call screen text */ +"call_incoming" = "Chiamata in arrivo"; +/* File message text */ +"chat_waiting" = "In attesa…"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Eliminare questa discussione e la cronologia dei messaggi?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Eliminare questo contatto?\nLa storia della tua discussione andrà persa."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Eliminare questa richiesta di contatto?"; +/* Deleting single message in chat */ +"delete_single_message" = "Elimina il messaggio"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Elimina i messaggi"; +/* Delete button */ +"alert_cancel" = "Annulla"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Inserisci sia il nome utente che il nome del profilo."; +/* Error while creating new profile */ +"login_profile_already_exists" = "Il profilo con il nome dato esiste già"; + +/* General error title */ +"error_title" = "Errore"; +/* Error */ +"error_internal_message" = "Errore interno"; +/* Error */ +"error_import_not_exist_message" = "Il file non esiste."; +/* Error */ +"error_decrypt_title" = "Impossibile decifrare il file di salvataggio tox"; +/* Error */ +"error_decrypt_empty_data_message" = "Alcuni dati di ingresso erano vuoti."; +/* Error */ +"error_decrypt_bad_format_message" = "Il file ha un cattivo formato o è corrotto."; +/* Error */ +"error_decrypt_wrong_password_message" = "La password è sbagliata o il file è corrotto."; +/* Error */ +"error_general_unknown_message" = "Si è verificato un errore sconosciuto."; +/* Error */ +"error_general_no_memory_message" = "Non c'è abbastanza memoria."; +/* Error */ +"error_general_bind_port_message" = "Non è riuscito a collegarsi a una porta."; +/* Error */ +"error_general_bad_format_message" = "Il file ha un cattivo formato o è corrotto."; +/* Error */ +"error_proxy_title" = "Errore proxy"; +/* Error */ +"error_proxy_invalid_address_message" = "L'indirizzo del proxy ha un formato non valido."; +/* Error */ +"error_contact_request_already_sent" = "La richiesta di contatto è già stata inviata"; +/* Error */ +"error_contact_request_bad_checksum" = "Cattivo checksum, si prega di controllare l'ID Tox inserito"; +/* Error */ +"error_contact_request_new_nospam" = "Valore nospam errato, controlla l'ID Tox inserito"; + +/* Call error */ +"call_error_already_in_call" = "Già in una chiamata"; +/* Call error */ +"call_error_contact_is_offline" = "Il contatto è fuori linea"; +/* Call error */ +"call_error_no_active_call" = "Non c'è nessuna chiamata attiva"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Impossibile aprire il tema, formato sbagliato"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "discussioni non lette"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Imposta o rimuove l'avatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Discussione"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Apre la finestra di dialogo della discussione."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Chiamata audio"; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Videochiamata"; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Mostra il menù di copia."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Modifica il valore."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Messaggio"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Il tuo messaggio"; +/* Login screen text */ +"import_to_antidote" = "Importa in Antidote"; +/* Login screen text */ +"create_account_username_title" = "Come ti vedranno i contatti?"; +/* PIN screen error */ +"pin_do_not_match" = "Il PIN non corrisponde. Provi di nuovo"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Troppi tentativi falliti. Sei stato/a disconnesso/a."; +/* Screen name */ +"contact_request" = "Richiesta di contatto"; +/* Share Tox ID text */ +"copy" = "Copia"; +/* Call screen text */ +"call_reaching" = "In attesa…"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Messaggio"; +/* Login screen text */ +"create_account_profile_title" = "Come chiamare questo profilo?"; +/* PIN screen screen */ +"pin_enabled" = "Abilita il PIN"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 minuti"; + +/* User name text */ +"name" = "Nome"; +/* User nickname text */ +"nickname" = "Soprannome"; + +/* Source of photo to take */ +"photo_from_camera" = "Fotocamera"; +/* File message text */ +"chat_paused" = "In pausa"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Elimina tutto"; +/* Error */ +"error_too_many_files" = "Troppi trasferimenti di file attivi"; +/* Error */ +"error_wrong_password_title" = "Password sbagliata"; +/* Error */ +"error_wrong_password_message" = "La password contiene simboli sbagliati."; +/* Error */ +"error_import_not_exist_title" = "Impossibile importare un file di salvataggio tossico"; +/* Error */ +"error_proxy_invalid_port_message" = "La porta proxy non è valida."; +/* Error */ +"error_proxy_host_not_resolved_message" = "L'host proxy non può essere risolto."; +/* Error */ +"error_contact_request_own_key" = "Non posso aggiungermi alla lista dei contatti"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Fa una videochiamata."; +/* Login screen text */ +"create_account_profile_placeholder" = "Nome del profilo"; +/* PIN screen error details */ +"pin_failed_attempts" = "Tentativi falliti: %@"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 secondi"; +/* Contact last seen status */ +"contact_last_seen" = "Ultimo accesso: %@"; + +/* Share Tox ID text */ +"show_qr_code" = "Mostra il codice QR"; +/* Add contat error */ +"add_contact_wrong_qr" = "Codice QR sbagliato. Dovrebbe contenere l'ID Tox"; +/* User status message text */ +"status_message" = "Messaggio di stato"; +/* User status text */ +"status_title" = "Stato"; +/* User Tox ID text */ +"my_tox_id" = "Il mio ID Tox"; +/* User public key text */ +"public_key" = "Chiave pubblica"; +/* Share Tox ID text */ +"show_qr" = "Mostra il codice QR"; +/* Profile menu item / screen name */ +"profile_details" = "Dettagli del profilo"; +/* Chats screen message text */ +"chat_unanwered_call" = "Chiamata senza risposta"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Source of photo to take */ +"photo_from_photo_library" = "Fototeca"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Elimina questo profilo?"; +/* Call screen text */ +"call_ended" = "Chiamata terminata"; + +/* File message text */ +"chat_file_cancelled" = "Annullato"; +/* Delete button */ +"alert_delete" = "Elimina"; +/* General error button */ +"error_ok_button" = "OK"; +/* General error button */ +"error_retry_button" = "Riprova"; +/* Error */ +"error_contact_not_connected" = "Il contatto non è in linea"; +/* Error */ +"error_general_profile_encrypted_message" = "Il profilo è crittografato."; + +/* Error */ +"error_name_too_long" = "Il nome è troppo lungo."; +/* Error */ +"error_status_message_too_long" = "Il messaggio di stato è troppo lungo"; + +/* Error */ +"error_contact_request_too_long" = "Il messaggio è troppo lungo"; +/* Error */ +"error_contact_request_no_message" = "Nessun messaggio specificato"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Fa una chiamata audio."; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "sta scrivendo…"; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "I messaggi non consegnati saranno inviati quando sia tu che l'amico siete in linea."; diff --git a/Antidote/ko.lproj/AppStoreLocalizable.strings b/Antidote/ko.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..d6fbb04 --- /dev/null +++ b/Antidote/ko.lproj/AppStoreLocalizable.strings @@ -0,0 +1,54 @@ +/* + AppStoreLocalizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/02/17. + Copyright © 2017 dvor. All rights reserved. +*/ + +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_1" = "Mary Cokley"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_2" = "Shirley Knox"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_3" = "Jennifer Smith"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_4" = "Marina Dixon"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_5" = "Carol Ortega"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_1" = "Michael Sharpe"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_2" = "Charles Donahue"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_3" = "Lee Murdock"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_4" = "Wayne Henderson"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_5" = "Robert Newton"; + +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_1" = "Is Antidote really that secure?"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_2" = "sure, it is peer-to-peer"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_3" = "And what does that mean? Peer-to-peer? 😄"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_4" = "you text me directly, the are no servers or things like that"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_5" = "+ it's encrypted 🔐😎"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_6" = "Cool!"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_7" = "I'll give it a go then"; + +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_1" = "😂😂😂"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_2" = "dinner tonight?"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_3" = "I think I know what you are talking about"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_4" = "Sure, thanks!"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_5" = "yep"; diff --git a/Antidote/ko.lproj/InfoPlist.strings b/Antidote/ko.lproj/InfoPlist.strings new file mode 100644 index 0000000..b79a4d0 --- /dev/null +++ b/Antidote/ko.lproj/InfoPlist.strings @@ -0,0 +1,16 @@ +/* + InfoPlist.strings + Antidote + + Created by Dmytro Vorobiov on 15/11/16. + Copyright © 2016 dvor. All rights reserved. +*/ + +/* Camera usage alert description */ +"NSCameraUsageDescription" = "You can use video calls, send photos and videos, scan QR codes."; + +/* Microphone usage alert description */ +"NSMicrophoneUsageDescription" = "You can use audio and video calls."; + +/* Photo library usage alert description */ +"NSPhotoLibraryUsageDescription" = "You can send photos and videos."; \ No newline at end of file diff --git a/Antidote/ko.lproj/Localizable.strings b/Antidote/ko.lproj/Localizable.strings new file mode 100644 index 0000000..8ddeb3c --- /dev/null +++ b/Antidote/ko.lproj/Localizable.strings @@ -0,0 +1,410 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* Login screen text */ +"login_or_label" = "또는"; +/* Login screen text */ +"create_account" = "계정 생성"; +/* Login screen text */ +"import_profile" = "프로파일 가져오기"; +/* Password field */ +"password" = "비밀번호"; +/* Login button */ +"log_in" = "로그인"; +/* Login screen text */ +"create_profile" = "프로파일 생성"; +/* Login screen text */ +"import_to_antidote" = "Antidote로 가져오기"; +/* Login screen text */ +"create_account_username_title" = "상대방이 나를 어떻게 보나요?"; +/* Login screen text */ +"create_account_username_placeholder" = "사용자명"; +/* Login screen text */ +"create_account_profile_title" = "이 프로파일을 연락하는 방법은 무엇인가요?"; +/* Login screen text */ +"create_account_profile_placeholder" = "프로파일 이름"; +/* Login screen text */ +"create_account_profile_hint" = "예: 홈, 아이폰"; +/* Login screen text */ +"set_password_title" = "비밀번호 설정"; +/* Login screen text */ +"set_password_hint" = "데이터를 보호하려면 암호가 필요합니다. 안전하게 보관하세요. 분실하면 복구할 수 없습니다."; +/* Login button */ +"create_account_next_button" = "다음"; +/* Login button */ +"create_account_go_button" = "Go"; +/* Default status message */ +"default_user_status_message" = "Antidote의 Toxing"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "잠금 해제하려면 PIN 입력"; +/* PIN screen text */ +"pin_set" = "PIN 설정"; +/* PIN screen text */ +"pin_confirm" = "PIN 확인"; +/* PIN screen error */ +"pin_do_not_match" = "PIN이 일치하지 않습니다. 다시 시도"; +/* PIN screen error */ +"pin_incorrect" = "잘못된 PIN"; +/* PIN screen error details */ +"pin_failed_attempts" = "실패한 시도: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "너무 많은 시도를 실패했습니다. 로그아웃되었습니다."; +/* PIN screen screen */ +"pin_enabled" = "PIN 입력"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "터치 ID 활성화"; +/* PIN screen screen */ +"pin_description" = "PIN을 사용하여 Antidote에 대한 무단 접속을 방지합니다."; +/* PIN screen screen */ +"pin_touch_id_description" = "PIN을 입력하는 대신 지문을 사용하세요."; +/* PIN screen screen */ +"pin_lock_timeout" = "잠금 시간초과"; +/* PIN screen screen */ +"pin_lock_immediately" = "즉시"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 초"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 분"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 분"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 분"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "연결 중..."; + +/* Tab name and screen name */ +"contacts_title" = "연락처"; +/* Tab name and screen name */ +"chats_title" = "채팅"; +/* Tab name and screen name */ +"settings_title" = "설정"; +/* Tab name and screen name */ +"profile_title" = "프로파일"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "연락처 없음"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "연락처 추가\n또는\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "Tox ID 공유"; +/* Contact request section title */ +"contact_requests_section" = "연락처 요청"; +/* Contact last seen status */ +"contact_last_seen" = "마지막으로 봄: %@"; +/* Screen name */ +"contact_request" = "연락처 요청"; +/* Contact request button */ +"contact_request_decline" = "거절"; +/* Contact request button */ +"contact_request_accept" = "수락"; +/* Contact request confirmation */ +"contact_request_delete_title" = "연락처 요청을 삭제할까요?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "삭제된 연락처"; + +/* Share Tox ID text */ +"show_qr_code" = "QR 코드 표시"; +/* Share Tox ID text */ +"copy" = "복사"; + +/* Add contat screen name */ +"add_contact_title" = "연락처 추가"; +/* Add contat button */ +"add_contact_send" = "보내기"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Tox ID 입력"; +/* Add contat placeholder text */ +"add_contact_or_label" = "또는"; +/* Add contat button */ +"add_contact_use_qr" = "QR 코드 사용"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "메세지"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "안녕하세요! 연락처 목록에 추가해 주겠어요?"; +/* Add contat error */ +"add_contact_wrong_qr" = "잘못된 QR 코드입니다. Tox ID를 포함해야 함"; + +/* User name text */ +"name" = "이름"; +/* User nickname text */ +"nickname" = "별명"; +/* User status message text */ +"status_message" = "상태 메시지"; +/* User status text */ +"status_title" = "상태"; +/* User Tox ID text */ +"my_tox_id" = "내 Tox ID"; +/* User public key text */ +"public_key" = "공개 키"; +/* Share Tox ID text */ +"show_qr" = "QR 표시"; +/* Profile menu item / screen name */ +"profile_details" = "프로파일 세부정보"; +/* Profile button */ +"logout_button" = "로그 아웃"; + +/* QR code screen button */ +"qr_close_button" = "닫기"; + +/* Chat screen placeholder */ +"chat_no_chats" = "채팅 없음"; +/* Chats screen message text */ +"chat_outgoing_file" = "발신 파일:"; +/* Chats screen message text */ +"chat_incoming_file" = "수신 파일:"; +/* Chats screen message text */ +"chat_call_finished" = "통화 완료"; +/* Chats screen message text */ +"chat_unanwered_call" = "응답하지 않은 전화"; +/* Chat button */ +"chat_send_button" = "보내기"; +/* Chat notification toast */ +"chat_new_messages" = "↓ 새로운 메시지"; +/* Chat call information */ +"chat_call_message" = "전화, "; +/* Chat call information */ +"chat_missed_call_message" = "부재중 전화"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "더"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "타이핑 중..."; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "전달되지 않은 메시지는 본인과 친구가 모두 온라인 상태일 때 전송됩니다."; + +/* Status message */ +"status_offline" = "오프라인"; +/* Status message */ +"status_online" = "온라인"; +/* Status message */ +"status_away" = "자리 비움"; +/* Status message */ +"status_busy" = "바쁨"; + +/* Notification text */ +"notification_new_message" = "새로운 메세지"; +/* Notification text */ +"notification_incoming_contact_request" = "수신 연락 요청"; +/* Notification text */ +"notification_is_calling" = "전화 중"; +/* Notification text */ +"notification_incoming_file" = "수신 파일"; + +/* Settings menu / screen name */ +"settings_about" = "정보"; +/* Settings menu / screen name */ +"settings_faq" = "자주 묻는 질문들"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "고급 설정"; +/* About screen menu */ +"settings_antidote_version" = "Antidote 버전"; +/* About screen menu */ +"settings_antidote_build" = "Antidote 빌드"; +/* About screen menu */ +"settings_toxcore_version" = "Toxcore 버전"; +/* About screen menu */ +"settings_acknowledgements" = "감사의 말"; +/* Settings screen menu */ +"settings_autodownload_images" = "파일 자동 다운로드"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "수신 파일을 자동으로 다운로드합니다."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "알림 미리보기"; +/* Settings screen menu */ +"settings_notifications_description" = "응용 프로그램이 배경으로 전환되어도 최대 10분 동안 알림을 받게 됩니다."; +/* Settings screen menu */ +"settings_udp_enabled" = "UDP 활성화"; +/* Settings screen menu */ +"settings_restore_default" = "기본 설정 복원"; +/* Settings screen autodownload images option */ +"settings_never" = "절대 안 함"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Wi-Fi 사용"; +/* Settings screen autodownload images option */ +"settings_always" = "항상"; + +/* Profile settings menu */ +"change_password" = "비밀번호 변경"; +/* Profile settings menu */ +"delete_password" = "비밀번호 삭제"; +/* Profile settings menu */ +"old_password" = "오래된 비밀번호"; +/* Change password text */ +"new_password" = "새로운 비밀번호"; +/* Change password text */ +"repeat_password" = "비밀번호 반복"; +/* Change password button */ +"change_password_done" = "완료"; +/* Change password error */ +"password_is_empty_error" = "비밀번호는 비어 있지 않아야 함"; +/* Change password error */ +"wrong_old_password" = "잘못된 비밀번호"; +/* Change password error */ +"passwords_do_not_match" = "비밀번호가 일치하지 않음"; + +/* Source of photo to take */ +"photo_from_camera" = "카메라"; +/* Source of photo to take */ +"photo_from_photo_library" = "사진 라이브러리"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "이미지를 변환할 수 없음"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "연락처로 보내기"; + +/* Profile menu item */ +"export_profile" = "프로파일 내보내기"; +/* Profile menu item */ +"delete_profile" = "프로파일 삭제"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "이 프로파일을 삭제할까요?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "확실하나요?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "이 작업은 취소할 수 없음"; + +/* Call screen text */ +"call_incoming" = "수신 전화"; +/* Call screen text */ +"call_ended" = "통화 종료"; +/* Call screen text */ +"call_reaching" = "연결 중..."; + +/* File message text */ +"chat_file_cancelled" = "취소됨"; +/* File message text */ +"chat_waiting" = "대기 중..."; +/* File message text */ +"chat_paused" = "일시 중지됨"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "이 채팅 및 메시지 기록을 삭제할까요?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "이 연락처를 삭제할까요?\n채팅 기록이 손실됩니다."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "이 연락처 요청을 삭제할까요?"; +/* Deleting single message in chat */ +"delete_single_message" = "메세지 삭제"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "메세지 삭제"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "모두 삭제"; +/* Delete button */ +"alert_delete" = "삭제"; +/* Delete button */ +"alert_cancel" = "취소"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "사용자 이름과 프로파일 이름을 모두 입력하세요."; +/* Error while creating new profile */ +"login_profile_already_exists" = "해당 이름의 프로파일이 이미 존재"; + +/* General error title */ +"error_title" = "오류"; +/* General error button */ +"error_ok_button" = "확인"; +/* General error button */ +"error_retry_button" = "재시도"; +/* Error */ +"error_contact_not_connected" = "연락처가 온라인 상태가 아님"; +/* Error */ +"error_too_many_files" = "너무 많은 활성 파일 전송"; +/* Error */ +"error_internal_message" = "내부 오류"; +/* Error */ +"error_wrong_password_title" = "잘못된 비밀번호"; +/* Error */ +"error_wrong_password_message" = "암호에 잘못된 기호가 포함되어 있습니다."; +/* Error */ +"error_import_not_exist_title" = "tox 저장 파일을 가져올 수 없음"; +/* Error */ +"error_import_not_exist_message" = "파일이 없습니다."; +/* Error */ +"error_decrypt_title" = "tox 저장 파일을 복호할 수 없음"; +/* Error */ +"error_decrypt_empty_data_message" = "일부 입력 데이터가 비어 있습니다."; +/* Error */ +"error_decrypt_bad_format_message" = "파일 형식이 잘못되었거나 손상되었습니다."; +/* Error */ +"error_decrypt_wrong_password_message" = "암호가 잘못되었거나 파일이 손상되었습니다."; +/* Error */ +"error_general_unknown_message" = "알 수 없는 오류가 발생했습니다."; +/* Error */ +"error_general_no_memory_message" = "메모리가 부족합니다."; +/* Error */ +"error_general_bind_port_message" = "포트에 바인딩할 수 없습니다."; +/* Error */ +"error_general_profile_encrypted_message" = "프로파일이 암호화되었습니다."; +/* Error */ +"error_general_bad_format_message" = "파일 형식이 잘못되었거나 손상되었습니다."; +/* Error */ +"error_proxy_title" = "프록시 오류"; +/* Error */ +"error_proxy_invalid_address_message" = "프록시 주소의 형식이 잘못되었습니다."; +/* Error */ +"error_proxy_invalid_port_message" = "프록시 포트가 잘못되었습니다."; +/* Error */ +"error_proxy_host_not_resolved_message" = "프록시 호스트를 확인할 수 없습니다."; + +/* Error */ +"error_name_too_long" = "이름이 너무 깁니다."; +/* Error */ +"error_status_message_too_long" = "상태 메시지가 너무 김"; + +/* Error */ +"error_contact_request_too_long" = "메시지가 너무 김"; +/* Error */ +"error_contact_request_no_message" = "지정된 메시지 없음"; +/* Error */ +"error_contact_request_own_key" = "연락처 목록에 본인을 추가할 수 없음"; +/* Error */ +"error_contact_request_already_sent" = "연락 요청이 이미 전송됨"; +/* Error */ +"error_contact_request_bad_checksum" = "체크섬이 잘못됨, 입력한 Tox ID를 확인"; +/* Error */ +"error_contact_request_new_nospam" = "nospam 값이 잘못됨, 입력한 Tox ID를 확인"; + +/* Call error */ +"call_error_already_in_call" = "이미 통화 중"; +/* Call error */ +"call_error_contact_is_offline" = "연락처가 오프라인 상태"; +/* Call error */ +"call_error_no_active_call" = "활성 통화가 없음"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "테마를 열 수 없고, 형식이 잘못됨"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "읽지 않은 채팅"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "아바타"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "아바타를 설정하거나 제거합니다."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "채팅"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "채팅 대화 상자를 엽니다."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "음성 통화"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "음성 통화를 합니다."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "영상 통화"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "영상 통화를 합니다."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "복사 메뉴를 표시합니다."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "값을 편집합니다."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "메세지"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "본인 메세지"; diff --git a/Antidote/ko.lproj/import-profile.html b/Antidote/ko.lproj/import-profile.html new file mode 100644 index 0000000..7f2b2f3 --- /dev/null +++ b/Antidote/ko.lproj/import-profile.html @@ -0,0 +1,10 @@ + +

To import your Tox profile:

+ +
    +
  1. Send the ".tox" file to your device using any app (Mail, Dropbox, etc.).
  2. +
  3. Use "Open In" menu for this file.
  4. +
  5. Select Antidote in a list of available apps.
  6. +
  7. Check the name of your new profile and press OK.
  8. +
+
diff --git a/Antidote/lt.lproj/InfoPlist.strings b/Antidote/lt.lproj/InfoPlist.strings new file mode 100644 index 0000000..9c184df Binary files /dev/null and b/Antidote/lt.lproj/InfoPlist.strings differ diff --git a/Antidote/lt.lproj/Localizable.strings b/Antidote/lt.lproj/Localizable.strings new file mode 100644 index 0000000..fafa520 --- /dev/null +++ b/Antidote/lt.lproj/Localizable.strings @@ -0,0 +1,415 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* + Translation is done with Transifex service. + See https://www.transifex.com/antidote/antidote-ios/ +*/ + +/* Login screen text */ +"login_or_label" = "arba"; +/* Login screen text */ +"create_account" = "Sukurti paskyrą"; +/* Login screen text */ +"import_profile" = "Importuoti profilį"; +/* Password field */ +"password" = "Slaptažodis"; +/* Login button */ +"log_in" = "Prisijungti"; +/* Login screen text */ +"create_profile" = "Sukurti profilį"; +/* Login screen text */ +"import_to_antidote" = "Importuoti į Antidote"; +/* Login screen text */ +"create_account_username_title" = "Kokiu vardu jus matys jūsų kontaktai?"; +/* Login screen text */ +"create_account_username_placeholder" = "Naudotojo vardas"; +/* Login screen text */ +"create_account_profile_title" = "Kaip pavadinti šį profilį?"; +/* Login screen text */ +"create_account_profile_placeholder" = "Profilio pavadinimas"; +/* Login screen text */ +"create_account_profile_hint" = "pvz., Namai, iPhone"; +/* Login screen text */ +"set_password_title" = "Nustatyti slaptažodį"; +/* Login screen text */ +"set_password_hint" = "Slaptažodis yra reikalingas apsaugoti jūsų duomenis. Laikykite jį saugioje vietoje, kadangi praradę, negalėsite jo atkurti."; +/* Login button */ +"create_account_next_button" = "Kitas"; +/* Login button */ +"create_account_go_button" = "Pirmyn"; +/* Default status message */ +"default_user_status_message" = "Bendrauju per Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Įrašykite savo PIN, kad atrakintumėte"; +/* PIN screen text */ +"pin_set" = "Nustatykite PIN"; +/* PIN screen text */ +"pin_confirm" = "Patvirtinkite PIN"; +/* PIN screen error */ +"pin_do_not_match" = "PIN kodai nesutapo. Bandykite vėl"; +/* PIN screen error */ +"pin_incorrect" = "Neteisingas PIN"; +/* PIN screen error details */ +"pin_failed_attempts" = "Nepavykusių bandymų: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Per daug nepavykusių bandymų. Jūs buvote atjungti."; +/* PIN screen screen */ +"pin_enabled" = "Įjungti PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Įjungti prisilietimo ID"; +/* PIN screen screen */ +"pin_description" = "Neleisti nesankcionuotą prieigą prie Antidote, naudojant PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "Naudoti jūsų piršto antspaudą, kaip alternatyvą PIN įrašymui."; +/* PIN screen screen */ +"pin_lock_timeout" = "Užrakinimo laukimo laikas"; +/* PIN screen screen */ +"pin_lock_immediately" = "Nedelsiant"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 sekundžių"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 minutė"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 minutės"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 minutės"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Jungiamasi..."; + +/* Tab name and screen name */ +"contacts_title" = "Kontaktai"; +/* Tab name and screen name */ +"chats_title" = "Pokalbiai"; +/* Tab name and screen name */ +"settings_title" = "Nustatymai"; +/* Tab name and screen name */ +"profile_title" = "Profilis"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Nėra kontaktų"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Pridėkite kontaktą\narba\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "dalinkitės savo Tox ID"; +/* Contact request section title */ +"contact_requests_section" = "Kontaktų užklausos"; +/* Contact last seen status */ +"contact_last_seen" = "Buvo prisijungęs: %@"; +/* Screen name */ +"contact_request" = "Kontakto užklausa"; +/* Contact request button */ +"contact_request_decline" = "Atmesti"; +/* Contact request button */ +"contact_request_accept" = "Priimti"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Ištrinti kontakto užklausą?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Ištrintas kontaktas"; + +/* Share Tox ID text */ +"show_qr_code" = "Rodyti QR kodą"; +/* Share Tox ID text */ +"copy" = "Kopijuoti"; + +/* Add contat screen name */ +"add_contact_title" = "Pridėti kontaktą"; +/* Add contat button */ +"add_contact_send" = "Siųsti"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Įrašykite Tox ID"; +/* Add contat placeholder text */ +"add_contact_or_label" = "arba"; +/* Add contat button */ +"add_contact_use_qr" = "Naudokite QR kodą"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Žinutė"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Sveiki! Ar galėtumėte mane, prašau, pridėti į savo kontaktų sąrašą?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Neteisingas QR kodas. Jame turėtų būti Tox ID"; + +/* User name text */ +"name" = "Vardas"; +/* User nickname text */ +"nickname" = "Slapyvardis"; +/* User status message text */ +"status_message" = "Būsenos žinutė"; +/* User status text */ +"status_title" = "Būsena"; +/* User Tox ID text */ +"my_tox_id" = "Mano Tox ID"; +/* User public key text */ +"public_key" = "Viešasis raktas"; +/* Share Tox ID text */ +"show_qr" = "Rodyti QR"; +/* Profile menu item / screen name */ +"profile_details" = "Išsamesnė profilio informacija"; +/* Profile button */ +"logout_button" = "Atsijungti"; + +/* QR code screen button */ +"qr_close_button" = "Užverti"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Pokalbių nėra"; +/* Chats screen message text */ +"chat_outgoing_file" = "Išsiunčiamas failas:"; +/* Chats screen message text */ +"chat_incoming_file" = "Atsiunčiamas failas:"; +/* Chats screen message text */ +"chat_call_finished" = "Skambutis baigtas"; +/* Chats screen message text */ +"chat_unanwered_call" = "Neatsilieptas skambutis"; +/* Chat button */ +"chat_send_button" = "Siųsti"; +/* Chat notification toast */ +"chat_new_messages" = "↓ Naujos žinutės"; +/* Chat call information */ +"chat_call_message" = "Skambutis, "; +/* Chat call information */ +"chat_missed_call_message" = "Praleistas skambutis"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Daugiau"; + +/* Status message */ +"status_offline" = "Atsijungęs"; +/* Status message */ +"status_online" = "Prisijungęs"; +/* Status message */ +"status_away" = "Pasišalinęs"; +/* Status message */ +"status_busy" = "Užsiėmęs"; + +/* Notification text */ +"notification_new_message" = "Nauja žinutė"; +/* Notification text */ +"notification_incoming_contact_request" = "Gaunama kontakto užklausa"; +/* Notification text */ +"notification_is_calling" = "skambina"; +/* Notification text */ +"notification_incoming_file" = "Gaunamas failas"; + +/* Settings menu / screen name */ +"settings_about" = "Apie"; +/* Settings menu / screen name */ +"settings_faq" = "DUK"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Išplėstiniai nustatymai"; +/* About screen menu */ +"settings_antidote_version" = "Antidote versija"; +/* About screen menu */ +"settings_antidote_build" = "Antidote darinys"; +/* About screen menu */ +"settings_toxcore_version" = "Toxcore versija"; +/* About screen menu */ +"settings_acknowledgements" = "Padėkos"; +/* Settings screen menu */ +"settings_autodownload_images" = "Automatinis failų atsiuntimas"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Automatiškai atsisiųsti gaunamus failus."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Pranešimo peržiūra"; +/* Settings screen menu */ +"settings_notifications_description" = "Kai programa pereis į foną, jūs vis dar 10-ies minučių laikotarpyje gausite pranešimus."; +/* Settings screen menu */ +"settings_udp_enabled" = "Įjungti UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Atkurti numatytuosius nustatymus"; +/* Settings screen autodownload images option */ +"settings_never" = "Niekada"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Belaidis ryšys (Wi-Fi)"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Besinaudojant belaidžiu ryšiu (Wi-Fi)"; +/* Settings screen autodownload images option */ +"settings_always" = "Visada"; + +/* Profile settings menu */ +"change_password" = "Keisti slaptažodį"; +/* Profile settings menu */ +"delete_password" = "Ištrinti slaptažodį"; +/* Profile settings menu */ +"old_password" = "Senas slaptažodis"; +/* Change password text */ +"new_password" = "Naujas slaptažodis"; +/* Change password text */ +"repeat_password" = "Pakartokite slaptažodį"; +/* Change password button */ +"change_password_done" = "Atlikta"; +/* Change password error */ +"password_is_empty_error" = "Slaptažodis turėtų būti ne tuščias"; +/* Change password error */ +"wrong_old_password" = "Neteisingas slaptažodis"; +/* Change password error */ +"passwords_do_not_match" = "Slaptažodžiai nesutampa"; + +/* Source of photo to take */ +"photo_from_camera" = "Kamera"; +/* Source of photo to take */ +"photo_from_photo_library" = "Nuotraukų biblioteka"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Nepavyksta konvertuoti paveikslo"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Siųsti kontaktui"; + +/* Profile menu item */ +"export_profile" = "Eksportuoti profilį"; +/* Profile menu item */ +"delete_profile" = "Ištrinti profilį"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Ištrinti šį profilį?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Ar tikrai?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Ši operacija negalės būti panaikinta"; + +/* Call screen text */ +"call_incoming" = "Įeinantis skambutis"; +/* Call screen text */ +"call_ended" = "Skambutis baigtas"; +/* Call screen text */ +"call_reaching" = "Susisiekiama..."; + +/* File message text */ +"chat_file_cancelled" = "Atšaukta"; +/* File message text */ +"chat_waiting" = "Laukiama..."; +/* File message text */ +"chat_paused" = "Pristabdyta"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Ištrinti šį pokalbį ir žinučių žurnalą?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Ištrinti šį kontaktą?\nJūsų pokalbių žurnalas bus prarastas."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Ištrinti šią kontakto užklausą?"; +/* Deleting single message in chat */ +"delete_single_message" = "Ištrinti žinutę"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Ištrinti žinutes"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Ištrinti visas"; +/* Delete button */ +"alert_delete" = "Ištrinti"; +/* Delete button */ +"alert_cancel" = "Atsisakyti"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Prašome įrašyti ir naudotojo vardą, ir profilio pavadinimą."; +/* Error while creating new profile */ +"login_profile_already_exists" = "Profilis tokiu pavadinimu jau yra"; + +/* General error title */ +"error_title" = "Klaida"; +/* General error button */ +"error_ok_button" = "Gerai"; +/* General error button */ +"error_retry_button" = "Bandyti vėl"; +/* Error */ +"error_contact_not_connected" = "Kontaktas nėra prisijungęs"; +/* Error */ +"error_too_many_files" = "Per daug aktyvių failo persiuntimų"; +/* Error */ +"error_internal_message" = "Vidinė klaida"; +/* Error */ +"error_wrong_password_title" = "Neteisingas slaptažodis"; +/* Error */ +"error_wrong_password_message" = "Slaptažodyje yra netinkamų simbolių."; +/* Error */ +"error_import_not_exist_title" = "Nepavyksta importuoti tox įrašyto failo"; +/* Error */ +"error_import_not_exist_message" = "Failo nėra."; +/* Error */ +"error_decrypt_title" = "Nepavyksta iššifruoti tox įrašyto failo"; +/* Error */ +"error_decrypt_empty_data_message" = "Kai kurie įvesties duomenys buvo tušti."; +/* Error */ +"error_decrypt_bad_format_message" = "Failas yra netinkamo formato arba yra pažeistas."; +/* Error */ +"error_decrypt_wrong_password_message" = "Slaptažodis neteisingas arba failas yra pažeistas."; +/* Error */ +"error_general_unknown_message" = "Įvyko nežinoma klaida."; +/* Error */ +"error_general_no_memory_message" = "Neužtenka atminties."; +/* Error */ +"error_general_bind_port_message" = "Nepavyko susieti prievado."; +/* Error */ +"error_general_profile_encrypted_message" = "Profilis yra užšifruotas."; +/* Error */ +"error_general_bad_format_message" = "Failas yra netinkamo formato arba yra pažeistas."; +/* Error */ +"error_proxy_title" = "Įgaliotojo serverio klaida"; +/* Error */ +"error_proxy_invalid_address_message" = "Neteisingas įgaliotojo serverio adreso formatas."; +/* Error */ +"error_proxy_invalid_port_message" = "Neteisingas įgaliotojo serverio prievadas."; +/* Error */ +"error_proxy_host_not_resolved_message" = "Nepavyko nustatyti įgaliotojo serverio."; + +/* Error */ +"error_name_too_long" = "Pavadinimas per ilgas."; +/* Error */ +"error_status_message_too_long" = "Būsenos žinutė yra per ilga"; + +/* Error */ +"error_contact_request_too_long" = "Žinutė per ilga"; +/* Error */ +"error_contact_request_no_message" = "Nėra nurodyta žinutė"; +/* Error */ +"error_contact_request_own_key" = "Negalima savęs pridėti į kontaktų sąrašą"; +/* Error */ +"error_contact_request_already_sent" = "Kontakto užklausa jau buvo išsiųsta"; +/* Error */ +"error_contact_request_bad_checksum" = "Bloga kontrolinė suma, patikrinkite įrašytą Tox ID"; +/* Error */ +"error_contact_request_new_nospam" = "Bloga nospam reikšmė, patikrinkite įrašytą Tox ID"; + +/* Call error */ +"call_error_already_in_call" = "Jau pokalbyje"; +/* Call error */ +"call_error_contact_is_offline" = "Kontaktas atsijungęs"; +/* Call error */ +"call_error_no_active_call" = "Nėra aktyvaus skambučio"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Nepavyksta atverti temos, netinkamas formatas"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "neskaityti pokalbiai"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avataras"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Nustato ar pašalina avatarą."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Pokalbis"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Atveria pokalbio dialogą."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Garso skambutis"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Užmezgia garso skambutį."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Vaizdo skambutis"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Užmezgia vaizdo skambutį."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Rodo kopijavimo meniu."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Redaguoja reikšmę."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Žinutė"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Jūsų žinutė"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "rašo..."; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Nepristatytos žinutės bus išsiųstos kai jūs ir jūsų draugas būsite prisijungę."; diff --git a/Antidote/lt.lproj/import-profile.html b/Antidote/lt.lproj/import-profile.html new file mode 100644 index 0000000..0165032 --- /dev/null +++ b/Antidote/lt.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

Norėdami importuoti savo Tox profilį:

+ +
    +
  1. Siųskite ".tox" failą į savo įrenginį, naudodami bet kurią programą (Paštą, Dropbox ir t.t.).
  2. +
  3. Šiam failui naudokite meniu "Atverti naudojant".
  4. +
  5. Prieinamų programų sąraše pasirinkite Antidote.
  6. +
  7. Patikrinkite savo naujojo profilio pavadinimą ir spauskite Gerai.
  8. +
diff --git a/Antidote/nb.lproj/AppStoreLocalizable.strings b/Antidote/nb.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..d6fbb04 --- /dev/null +++ b/Antidote/nb.lproj/AppStoreLocalizable.strings @@ -0,0 +1,54 @@ +/* + AppStoreLocalizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/02/17. + Copyright © 2017 dvor. All rights reserved. +*/ + +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_1" = "Mary Cokley"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_2" = "Shirley Knox"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_3" = "Jennifer Smith"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_4" = "Marina Dixon"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_5" = "Carol Ortega"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_1" = "Michael Sharpe"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_2" = "Charles Donahue"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_3" = "Lee Murdock"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_4" = "Wayne Henderson"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_5" = "Robert Newton"; + +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_1" = "Is Antidote really that secure?"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_2" = "sure, it is peer-to-peer"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_3" = "And what does that mean? Peer-to-peer? 😄"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_4" = "you text me directly, the are no servers or things like that"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_5" = "+ it's encrypted 🔐😎"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_6" = "Cool!"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_7" = "I'll give it a go then"; + +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_1" = "😂😂😂"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_2" = "dinner tonight?"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_3" = "I think I know what you are talking about"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_4" = "Sure, thanks!"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_5" = "yep"; diff --git a/Antidote/nb.lproj/InfoPlist.strings b/Antidote/nb.lproj/InfoPlist.strings new file mode 100644 index 0000000..b79a4d0 --- /dev/null +++ b/Antidote/nb.lproj/InfoPlist.strings @@ -0,0 +1,16 @@ +/* + InfoPlist.strings + Antidote + + Created by Dmytro Vorobiov on 15/11/16. + Copyright © 2016 dvor. All rights reserved. +*/ + +/* Camera usage alert description */ +"NSCameraUsageDescription" = "You can use video calls, send photos and videos, scan QR codes."; + +/* Microphone usage alert description */ +"NSMicrophoneUsageDescription" = "You can use audio and video calls."; + +/* Photo library usage alert description */ +"NSPhotoLibraryUsageDescription" = "You can send photos and videos."; \ No newline at end of file diff --git a/Antidote/nb.lproj/Localizable.strings b/Antidote/nb.lproj/Localizable.strings new file mode 100644 index 0000000..227bc2b --- /dev/null +++ b/Antidote/nb.lproj/Localizable.strings @@ -0,0 +1,398 @@ + + +/* PIN screen screen */ +"pin_lock_1_minute" = "1 minutt"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 minutter"; +/* Tab name and screen name */ +"chats_title" = "Sludringer"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "del din Tox-ID"; +/* Contact request section title */ +"contact_requests_section" = "Kontaktforespørsler"; +/* Screen name */ +"contact_request" = "Kontaktforespørsel"; +/* Status message */ +"status_busy" = "Opptatt"; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Melding"; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Slett denne kontaktforespørselen?"; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Taleanrop"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Utfører et taleanrop."; +/* Contact request button */ +"contact_request_accept" = "Godta"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Melding"; +/* User public key text */ +"public_key" = "Offentlig nøkkel"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Ingen sludringer"; +/* Contact request button */ +"contact_request_decline" = "Avslå"; + +/* Share Tox ID text */ +"show_qr_code" = "Vis QR-kode"; +/* Login screen text */ +"create_profile" = "Opprett profil"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Skriv inn din PIN for å låse opp"; +/* Add contat button */ +"add_contact_send" = "Send"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Skriv inn Tox-ID"; +/* Add contat placeholder text */ +"add_contact_or_label" = "eller"; +/* Add contat button */ +"add_contact_use_qr" = "Bruk QR-kode"; +/* User nickname text */ +"nickname" = "Kallenavn"; +/* User status message text */ +"status_message" = "Statusmelding"; +/* User Tox ID text */ +"my_tox_id" = "Min Tox-ID."; +/* Chats screen message text */ +"chat_outgoing_file" = "Utgående fil:"; +/* Chats screen message text */ +"chat_incoming_file" = "Innkommende fil:"; +/* Chats screen message text */ +"chat_call_finished" = "Samtale avsluttet"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Er du sikker?"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Slett alle"; + +/* General error title */ +"error_title" = "Feil"; +/* Login screen text */ +"login_or_label" = "eller"; +/* Login screen text */ +"create_account" = "Opprett konto"; +/* Login screen text */ +"import_profile" = "Importer profil"; +/* Password field */ +"password" = "Passord"; +/* Login screen text */ +"create_account_username_placeholder" = "Brukernavn"; +/* Chats screen message text */ +"chat_unanwered_call" = "Ubesvart samtale"; + +/* Notification text */ +"notification_new_message" = "Ny melding"; + +/* Settings menu / screen name */ +"settings_about" = "Om"; +/* Settings menu / screen name */ +"settings_faq" = "O-S-S"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Avanserte innstillinger"; +/* About screen menu */ +"settings_antidote_version" = "Antidote-versjon"; +/* About screen menu */ +"settings_toxcore_version" = "Toxcore-versjon"; +/* Login screen text */ +"create_account_username_title" = "Hvordan kontakter vil se deg."; +/* Login screen text */ +"create_account_profile_title" = "Hvordan skal denne profilen ringes?"; +/* Login screen text */ +"set_password_hint" = "Passord krever for å beskytte dataen din. Ta vare på det siden det ikke kan gjenopprettes."; +/* Default status message */ +"default_user_status_message" = "Toksikalerer på Antidote"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "For mange mislykkede forsøk. Du har blitt utlogget."; +/* PIN screen screen */ +"pin_description" = "Forhindre uautorisert tilgang til Antidote med en PIN."; +/* Contact last seen status */ +"contact_last_seen" = "Sist sett: %@"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Hei. Kan du legge meg til på kontaktlisten din?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Feil QR-kode. Den skal inneholde en Tox-ID."; +/* Chat notification toast */ +"chat_new_messages" = "↓ Nye meldinger"; +/* Chat call information */ +"chat_call_message" = "Anrop, "; +/* Chat call information */ +"chat_missed_call_message" = "Tapt anrop"; +/* Status message */ +"status_online" = "Pålogget"; +/* Status message */ +"status_away" = "Borte"; +/* About screen menu */ +"settings_acknowledgements" = "Bidragsytere"; +/* Profile settings menu */ +"old_password" = "Gammelt passord"; +/* Change password text */ +"new_password" = "Nytt passord"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Kan ikke konvertere bilde"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Last ned innkommende bilder automatisk"; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Merknadsforhåndsvisning"; +/* Settings screen menu */ +"settings_notifications_description" = "Når programmer blir relegert til bakgrunnen vil du fremdeles få merknader i 10 minutter."; +/* Settings screen menu */ +"settings_udp_enabled" = "Skru på UDP"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Ved bruk av Wi-Fi"; +/* Change password error */ +"password_is_empty_error" = "Passordet kan ikke være tomt"; +/* Change password error */ +"passwords_do_not_match" = "Passordene samsvarer ikke"; +/* Source of photo to take */ +"photo_from_photo_library" = "Bildebibliotek"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Denne handlingen kan ikke angres"; +/* Error */ +"error_too_many_files" = "For mange aktive filoverføringer"; +/* Error */ +"error_internal_message" = "Intern feil"; +/* Error */ +"error_wrong_password_title" = "Feil passord"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Skriv inn både brukernavn og profilnavn."; +/* Error */ +"error_contact_not_connected" = "Kontakten er ikke pålogget"; +/* Error while creating new profile */ +"login_profile_already_exists" = "Profil med angitt navn finnes allerede."; +/* Error */ +"error_wrong_password_message" = "Passord inneholder ulovlige symboler."; +/* Error */ +"error_general_unknown_message" = "Ukjent feil oppstod."; +/* Error */ +"error_contact_request_new_nospam" = "Feil NoSpam-verdi. Sjekk innskrevet Tox-ID."; + +/* Call error */ +"call_error_already_in_call" = "Allerede i en samtale"; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Sludring"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Åpner sludringsdialogen."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Videosamtale"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Utfører en videosamtale."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Viser kopieringsmenyen."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Redigerer verdi."; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Din melding"; +/* Settings screen menu */ +"settings_autodownload_images" = "Auto-nedlastede bilder"; + +/* Call screen text */ +"call_incoming" = "Innkommende anrop"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Slett kontaktforespørsel?"; +/* PIN screen error */ +"pin_incorrect" = "Feil PIN"; +/* Tab name and screen name */ +"settings_title" = "Innstillinger"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Slett denne sludringen og meldingshistorikken fra den?"; +/* Settings screen menu */ +"settings_restore_default" = "Gjenopprett forvalgsinnstillinger"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; + +/* Profile settings menu */ +"change_password" = "Endre passord"; +/* Profile settings menu */ +"delete_password" = "Slett passord"; +/* Change password text */ +"repeat_password" = "Gjenta passord"; +/* Change password button */ +"change_password_done" = "Ferdig"; +/* Change password error */ +"wrong_old_password" = "Feil passord"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Send til kontakt"; + +/* Profile menu item */ +"export_profile" = "Eksporter profil"; +/* Profile menu item */ +"delete_profile" = "Slett profil"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Slett denne profilen?"; + +/* File message text */ +"chat_file_cancelled" = "Avbrutt"; +/* File message text */ +"chat_waiting" = "Venter …"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Slett meldinger"; +/* Delete button */ +"alert_delete" = "Slett"; +/* Error */ +"error_import_not_exist_message" = "Filen finnes ikke."; +/* Error */ +"error_general_no_memory_message" = "Ikke nok minne."; +/* Login screen text */ +"create_account_profile_placeholder" = "Profilnavn"; +/* Login screen text */ +"set_password_title" = "Sett passord"; +/* Login button */ +"log_in" = "Logg inn"; +/* Login screen text */ +"import_to_antidote" = "Importer til Antidote"; +/* Login screen text */ +"create_account_profile_hint" = "f.eks. Hjemme, iPhone"; +/* Login button */ +"create_account_next_button" = "Neste"; +/* Login button */ +"create_account_go_button" = "Start"; +/* PIN screen text */ +"pin_confirm" = "Bekreft PIN"; +/* PIN screen error */ +"pin_do_not_match" = "PIN stemte ikke overens. Prøv igjen."; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 minutter"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Legg til kontakt\neller\n"; +/* PIN screen text */ +"pin_set" = "Sett PIN"; +/* PIN screen screen */ +"pin_lock_timeout" = "Låsingstidsavbrudd"; +/* PIN screen screen */ +"pin_lock_immediately" = "Umiddelbart"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Kobler til …"; + +/* Tab name and screen name */ +"contacts_title" = "Kontakter"; +/* Tab name and screen name */ +"profile_title" = "Profil"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Ingen kontakter"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Slettet kontakt"; +/* Share Tox ID text */ +"copy" = "Kopier"; + +/* Add contat screen name */ +"add_contact_title" = "Legg til kontakt"; + +/* User name text */ +"name" = "Navn"; +/* User status text */ +"status_title" = "Status"; +/* Share Tox ID text */ +"show_qr" = "Vis QR"; +/* Profile menu item / screen name */ +"profile_details" = "Profildetaljer"; +/* Profile button */ +"logout_button" = "Logg ut"; + +/* QR code screen button */ +"qr_close_button" = "Lukk"; +/* Chat button */ +"chat_send_button" = "Send"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Mer"; + +/* Status message */ +"status_offline" = "Frakoblet"; +/* About screen menu */ +"settings_antidote_build" = "Antidote-bygg"; +/* Settings screen autodownload images option */ +"settings_never" = "Aldri"; +/* Settings screen autodownload images option */ +"settings_always" = "Alltid"; + +/* Source of photo to take */ +"photo_from_camera" = "Kamera"; +/* Deleting single message in chat */ +"delete_single_message" = "Slett melding"; +/* Delete button */ +"alert_cancel" = "Avbryt"; +/* General error button */ +"error_ok_button" = "OK"; +/* General error button */ +"error_retry_button" = "Gjenta"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 sekunder"; +/* PIN screen screen */ +"pin_enabled" = "Skru på PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Aktiver Touch-ID"; +/* PIN screen error details */ +"pin_failed_attempts" = "Mislykket forsøk: %@"; +/* Error */ +"error_general_bad_format_message" = "Filen har feil format eller er skadet."; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Setter eller fjerner avataren."; +/* Error */ +"error_import_not_exist_title" = "Kan ikke importere Tox-lagringsfil."; +/* Error */ +"error_decrypt_title" = "Kan ikke dekryptere Tox-lagringsfil"; +/* Error */ +"error_decrypt_empty_data_message" = "Noe av inndataen var tom."; +/* Error */ +"error_decrypt_bad_format_message" = "Filen har feilaktig eller skadet format."; +/* Error */ +"error_general_bind_port_message" = "Kunne ikke opprette binding til en port."; +/* Error */ +"error_proxy_invalid_port_message" = "Mellomtjenerporten er ugyldig."; +/* Error */ +"error_proxy_host_not_resolved_message" = "Kunne ikke utlede mellomtjenerporten."; + +/* Error */ +"error_name_too_long" = "Navnet er for langt."; +/* Error */ +"error_contact_request_already_sent" = "Kontaktforespørsel allerede sendt."; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "uleste sludringer"; +/* PIN screen screen */ +"pin_touch_id_description" = "Bruk fingeravtrykket ditt som alternativ til å skive inn PIN."; +/* Notification text */ +"notification_incoming_contact_request" = "Innkommende kontaktforespørsel"; +/* Notification text */ +"notification_is_calling" = "ringer …"; +/* Notification text */ +"notification_incoming_file" = "Innkommende fil"; +/* Call screen text */ +"call_reaching" = "Ringer …"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Slett denne kontakten?\nSludringshistorikken vil gå tapt."; +/* Error */ +"error_general_profile_encrypted_message" = "Profilen er kryptert."; +/* Error */ +"error_proxy_title" = "Mellomtjenerfeil"; +/* File message text */ +"chat_paused" = "På hold"; +/* Error */ +"error_decrypt_wrong_password_message" = "Passordet er galt eller så er filen skadet."; +/* Error */ +"error_contact_request_bad_checksum" = "Feilaktig sjekksum. Sjekk innskrevet Tox-ID."; +/* Error */ +"error_proxy_invalid_address_message" = "Mellomtjeneradressen har ugyldig format."; +/* Error */ +"error_status_message_too_long" = "Statusmeldingen er for lang."; + +/* Error */ +"error_contact_request_too_long" = "Meldingen er for lang."; +/* Error */ +"error_contact_request_no_message" = "Ingen melding angitt."; +/* Error */ +"error_contact_request_own_key" = "Du kan ikke legge til deg selv på kontaktlisten."; +/* Call error */ +"call_error_contact_is_offline" = "Kontakten er frakoblet"; +/* Call error */ +"call_error_no_active_call" = "Det finnes ingen aktiv samtale"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Kan ikke åpne drakt. Feil format."; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; diff --git a/Antidote/nb.lproj/import-profile.html b/Antidote/nb.lproj/import-profile.html new file mode 100644 index 0000000..7f2b2f3 --- /dev/null +++ b/Antidote/nb.lproj/import-profile.html @@ -0,0 +1,10 @@ + +

To import your Tox profile:

+ +
    +
  1. Send the ".tox" file to your device using any app (Mail, Dropbox, etc.).
  2. +
  3. Use "Open In" menu for this file.
  4. +
  5. Select Antidote in a list of available apps.
  6. +
  7. Check the name of your new profile and press OK.
  8. +
+
diff --git a/Antidote/nl.lproj/AppStoreLocalizable.strings b/Antidote/nl.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..3f83883 Binary files /dev/null and b/Antidote/nl.lproj/AppStoreLocalizable.strings differ diff --git a/Antidote/nl.lproj/InfoPlist.strings b/Antidote/nl.lproj/InfoPlist.strings new file mode 100644 index 0000000..6c333a8 Binary files /dev/null and b/Antidote/nl.lproj/InfoPlist.strings differ diff --git a/Antidote/nl.lproj/Localizable.strings b/Antidote/nl.lproj/Localizable.strings new file mode 100644 index 0000000..d01f1dd --- /dev/null +++ b/Antidote/nl.lproj/Localizable.strings @@ -0,0 +1,415 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* + Translation is done with Transifex service. + See https://www.transifex.com/antidote/antidote-ios/ +*/ + +/* Login screen text */ +"login_or_label" = "of"; +/* Login screen text */ +"create_account" = "Maak een Account"; +/* Login screen text */ +"import_profile" = "Importeer Profiel"; +/* Password field */ +"password" = "Wachtwoord"; +/* Login button */ +"log_in" = "Inloggen"; +/* Login screen text */ +"create_profile" = "Maak een profiel"; +/* Login screen text */ +"import_to_antidote" = "Importeer naar Antidote"; +/* Login screen text */ +"create_account_username_title" = "Hoe zullen je contacten je zien?"; +/* Login screen text */ +"create_account_username_placeholder" = "Gebruikersnaam"; +/* Login screen text */ +"create_account_profile_title" = "Beschrijving van dit profiel?"; +/* Login screen text */ +"create_account_profile_placeholder" = "Profielnaam"; +/* Login screen text */ +"create_account_profile_hint" = "bijv. Thuis, iPhone"; +/* Login screen text */ +"set_password_title" = "Wachtwoord instellen"; +/* Login screen text */ +"set_password_hint" = "Een wachtwoord is vereist om je data te beveiligen. Hou het veilig - je data kan niet worden hersteld als je deze kwijtraakt."; +/* Login button */ +"create_account_next_button" = "Volgende"; +/* Login button */ +"create_account_go_button" = "Ga"; +/* Default status message */ +"default_user_status_message" = "Toxing met Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Ontgrendel met je toegangscode"; +/* PIN screen text */ +"pin_set" = "Toegangscode instellen"; +/* PIN screen text */ +"pin_confirm" = "Bevestig toegangscode"; +/* PIN screen error */ +"pin_do_not_match" = "De toegangscodes komen niet overeen. Probeer opnieuw"; +/* PIN screen error */ +"pin_incorrect" = "Verkeerde toegangscode"; +/* PIN screen error details */ +"pin_failed_attempts" = "Failed attempts: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Too many failed attempts. You have been logged out."; +/* PIN screen screen */ +"pin_enabled" = "Gebruik een toegangscode"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Gebruik Touch ID"; +/* PIN screen screen */ +"pin_description" = "Prevent unauthorized access to Antidote with a PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "Use your fingerprint as an alternative to entering a PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "Lock Timeout"; +/* PIN screen screen */ +"pin_lock_immediately" = "Immediately"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 seconden"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 minuut"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 minuten"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 minuten"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Verbinden..."; + +/* Tab name and screen name */ +"contacts_title" = "Contacten"; +/* Tab name and screen name */ +"chats_title" = "Chats"; +/* Tab name and screen name */ +"settings_title" = "Instellingen"; +/* Tab name and screen name */ +"profile_title" = "Profiel"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Geen contacten"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Voeg contact toe\nof\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "deel je Tox ID"; +/* Contact request section title */ +"contact_requests_section" = "Contactverzoek"; +/* Contact last seen status */ +"contact_last_seen" = "Laatst gezien: %@"; +/* Screen name */ +"contact_request" = "Contactverzoek"; +/* Contact request button */ +"contact_request_decline" = "Weiger"; +/* Contact request button */ +"contact_request_accept" = "Accepteer"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Contactverzoek verwijderen?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Deleted contact"; + +/* Share Tox ID text */ +"show_qr_code" = "QR-code weergeven"; +/* Share Tox ID text */ +"copy" = "Kopiëren"; + +/* Add contat screen name */ +"add_contact_title" = "Contact toevoegen"; +/* Add contat button */ +"add_contact_send" = "Verzenden"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Tox ID"; +/* Add contat placeholder text */ +"add_contact_or_label" = "of"; +/* Add contat button */ +"add_contact_use_qr" = "Gebruik QR-code"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Bericht"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Hallo! Wil je mij toevoegen aan je contactpersonen?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Verkeerde QR-code. Het moet een Tox ID bevatten"; + +/* User name text */ +"name" = "Naam"; +/* User nickname text */ +"nickname" = "Schermnaam"; +/* User status message text */ +"status_message" = "Statusbericht"; +/* User status text */ +"status_title" = "Status"; +/* User Tox ID text */ +"my_tox_id" = "Mijn Tox ID"; +/* User public key text */ +"public_key" = "Publieke sleutel"; +/* Share Tox ID text */ +"show_qr" = "Bekijk QR-code"; +/* Profile menu item / screen name */ +"profile_details" = "Profiel details"; +/* Profile button */ +"logout_button" = "Uitloggen"; + +/* QR code screen button */ +"qr_close_button" = "Sluit"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Geen chats"; +/* Chats screen message text */ +"chat_outgoing_file" = "Uitgaand bestand:"; +/* Chats screen message text */ +"chat_incoming_file" = "Inkomend bestand:"; +/* Chats screen message text */ +"chat_call_finished" = "Gesprek beëindigd"; +/* Chats screen message text */ +"chat_unanwered_call" = "Onbeantwoord gesprek"; +/* Chat button */ +"chat_send_button" = "Stuur"; +/* Chat notification toast */ +"chat_new_messages" = "↓ Nieuwe berichten"; +/* Chat call information */ +"chat_call_message" = "Gesprek,"; +/* Chat call information */ +"chat_missed_call_message" = "Gemist gesprek"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Meer"; + +/* Status message */ +"status_offline" = "Offline"; +/* Status message */ +"status_online" = "Online"; +/* Status message */ +"status_away" = "Afwezig"; +/* Status message */ +"status_busy" = "Bezig"; + +/* Notification text */ +"notification_new_message" = "Nieuw bericht"; +/* Notification text */ +"notification_incoming_contact_request" = "Inkomend contactverzoek"; +/* Notification text */ +"notification_is_calling" = "belt"; +/* Notification text */ +"notification_incoming_file" = "Inkomend bestand"; + +/* Settings menu / screen name */ +"settings_about" = "Over"; +/* Settings menu / screen name */ +"settings_faq" = "FAQ"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Geavanceerde instellingen"; +/* About screen menu */ +"settings_antidote_version" = "Antidote Versie"; +/* About screen menu */ +"settings_antidote_build" = "Antitode Build"; +/* About screen menu */ +"settings_toxcore_version" = "Toxcore Versie"; +/* About screen menu */ +"settings_acknowledgements" = "Met dank aan"; +/* Settings screen menu */ +"settings_autodownload_images" = "Download bestanden automatisch"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Automatisch inkomende bestanden downloaden."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Notificatie Voorbeeld"; +/* Settings screen menu */ +"settings_notifications_description" = "When application goes to background, you will still receive notifications up to 10 minutes."; +/* Settings screen menu */ +"settings_udp_enabled" = "Gebruik UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Standaardinstellingen herstellen"; +/* Settings screen autodownload images option */ +"settings_never" = "Nooit"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Via WiFi"; +/* Settings screen autodownload images option */ +"settings_always" = "Altijd"; + +/* Profile settings menu */ +"change_password" = "Verander wachtwoord"; +/* Profile settings menu */ +"delete_password" = "Verwijder wachtwoord"; +/* Profile settings menu */ +"old_password" = "Oud wachtwoord"; +/* Change password text */ +"new_password" = "Nieuw wachtwoord"; +/* Change password text */ +"repeat_password" = "Herhaal wachtwoord"; +/* Change password button */ +"change_password_done" = "Klaar"; +/* Change password error */ +"password_is_empty_error" = "Password should be non-empty"; +/* Change password error */ +"wrong_old_password" = "Verkeerd wachtwoord"; +/* Change password error */ +"passwords_do_not_match" = "Wachtwoorden komen niet overeen"; + +/* Source of photo to take */ +"photo_from_camera" = "Camera"; +/* Source of photo to take */ +"photo_from_photo_library" = "Fotobibliotheek"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Kan afbeelding niet converteren"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Verzend naar contact"; + +/* Profile menu item */ +"export_profile" = "Exporteer profiel"; +/* Profile menu item */ +"delete_profile" = "Verwijder profiel"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Dit profiel verwijderen?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Weet je het zeker?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Deze actie kan niet worden hersteld"; + +/* Call screen text */ +"call_incoming" = "Inkomend gesprek"; +/* Call screen text */ +"call_ended" = "Gesprek beëindigd"; +/* Call screen text */ +"call_reaching" = "Reaching..."; + +/* File message text */ +"chat_file_cancelled" = "Geannuleerd"; +/* File message text */ +"chat_waiting" = "Wachten..."; +/* File message text */ +"chat_paused" = "Gepauzeerd"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Verwijder deze chat en gesprek geschiedenis?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Verwijder dit contact?\nJe gesprek geschiedenis zal gewist worden."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Verwijder dit contactverzoek?"; +/* Deleting single message in chat */ +"delete_single_message" = "Wis bericht"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Wis berichten"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Wis alle berichten"; +/* Delete button */ +"alert_delete" = "Verwijder"; +/* Delete button */ +"alert_cancel" = "Annuleren"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Please enter both username and profile name."; +/* Error while creating new profile */ +"login_profile_already_exists" = "Een profiel met deze naam bestaat al"; + +/* General error title */ +"error_title" = "Fout"; +/* General error button */ +"error_ok_button" = "OK"; +/* General error button */ +"error_retry_button" = "Opnieuw"; +/* Error */ +"error_contact_not_connected" = "Contactpersoon is niet online"; +/* Error */ +"error_too_many_files" = "Te veel actieve bestandsoverdrachten"; +/* Error */ +"error_internal_message" = "Interne fout"; +/* Error */ +"error_wrong_password_title" = "Verkeerd wachtwoord"; +/* Error */ +"error_wrong_password_message" = "Wachtwoord bevat ongeldige tekens"; +/* Error */ +"error_import_not_exist_title" = "Kan het opgeslagen tox bestand niet importeren"; +/* Error */ +"error_import_not_exist_message" = "Bestand bestaat niet."; +/* Error */ +"error_decrypt_title" = "Kan het tox bestand niet decoderen"; +/* Error */ +"error_decrypt_empty_data_message" = "Enkele gegevens ontbreken."; +/* Error */ +"error_decrypt_bad_format_message" = "Bestand heeft een verkeerd formaat of is corrupt."; +/* Error */ +"error_decrypt_wrong_password_message" = "Wachtwoord is onjuist of het bestand is corrupt."; +/* Error */ +"error_general_unknown_message" = "Onbekende fout opgetreden."; +/* Error */ +"error_general_no_memory_message" = "Te weinig vrij geheugen."; +/* Error */ +"error_general_bind_port_message" = "Niet gelukt om met een poort te verbinden."; +/* Error */ +"error_general_profile_encrypted_message" = "Profiel is versleuteld."; +/* Error */ +"error_general_bad_format_message" = "Bestand heeft een verkeerd formaat of is corrupt."; +/* Error */ +"error_proxy_title" = "Proxy error"; +/* Error */ +"error_proxy_invalid_address_message" = "Proxy address has invalid format."; +/* Error */ +"error_proxy_invalid_port_message" = "Proxy port is invalid."; +/* Error */ +"error_proxy_host_not_resolved_message" = "Proxy host could not be resolved."; + +/* Error */ +"error_name_too_long" = "Naam is te lang"; +/* Error */ +"error_status_message_too_long" = "Statusbericht is te lang"; + +/* Error */ +"error_contact_request_too_long" = "Bericht is te lang"; +/* Error */ +"error_contact_request_no_message" = "Geen bericht ingevoerd"; +/* Error */ +"error_contact_request_own_key" = "Cannot add myself to contact list"; +/* Error */ +"error_contact_request_already_sent" = "Contactverzoek is al verzonden"; +/* Error */ +"error_contact_request_bad_checksum" = "Verkeerd controlegetal, controleer het ingevoerde Tox ID"; +/* Error */ +"error_contact_request_new_nospam" = "Verkeerd nospam-value, controleer het ingevoerde Tox ID"; + +/* Call error */ +"call_error_already_in_call" = "Reeds in een gesprek."; +/* Call error */ +"call_error_contact_is_offline" = "Contactpersoon is offline"; +/* Call error */ +"call_error_no_active_call" = "Er is geen actief gesprek"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Kan het thema niet openen, verkeerd formaat"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "unread chats"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Sets or removes avatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Chat"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Opens chat dialog."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Audio call"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Makes audio call."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Video call"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Makes video call."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Shows copy menu."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Edits value."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Message"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Your message"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "aan het typen..."; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Niet-bezorgde berichten worden verstuurd zodra jij en je vriend online zijn."; diff --git a/Antidote/nl.lproj/import-profile.html b/Antidote/nl.lproj/import-profile.html new file mode 100644 index 0000000..5d335d3 --- /dev/null +++ b/Antidote/nl.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

To import your Tox profile:

+ +
    +
  1. Send the ".tox" file to your device using any app (Mail, Dropbox, etc.).
  2. +
  3. Use "Open In" menu for this file.
  4. +
  5. Select Antidote in a list of available apps.
  6. +
  7. Check the name of your new profile and press OK.
  8. +
diff --git a/Antidote/old_antidote_logo_with_text.png b/Antidote/old_antidote_logo_with_text.png new file mode 100644 index 0000000..722cf84 Binary files /dev/null and b/Antidote/old_antidote_logo_with_text.png differ diff --git a/Antidote/pl.lproj/InfoPlist.strings b/Antidote/pl.lproj/InfoPlist.strings new file mode 100644 index 0000000..6c333a8 Binary files /dev/null and b/Antidote/pl.lproj/InfoPlist.strings differ diff --git a/Antidote/pl.lproj/Localizable.strings b/Antidote/pl.lproj/Localizable.strings new file mode 100644 index 0000000..c527e59 --- /dev/null +++ b/Antidote/pl.lproj/Localizable.strings @@ -0,0 +1,415 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* + Translation is done with Transifex service. + See https://www.transifex.com/antidote/antidote-ios/ +*/ + +/* Login screen text */ +"login_or_label" = "lub"; +/* Login screen text */ +"create_account" = "Utwórz konto"; +/* Login screen text */ +"import_profile" = "Importuj profil"; +/* Password field */ +"password" = "Hasło"; +/* Login button */ +"log_in" = "Logowanie"; +/* Login screen text */ +"create_profile" = "Utwórz profil"; +/* Login screen text */ +"import_to_antidote" = "Importuj do Antidote"; +/* Login screen text */ +"create_account_username_title" = "Jak kontakty będą Cie widzieć?"; +/* Login screen text */ +"create_account_username_placeholder" = "Użytkownik"; +/* Login screen text */ +"create_account_profile_title" = "Jak wywołać ten profil?"; +/* Login screen text */ +"create_account_profile_placeholder" = "Nazwa profilu"; +/* Login screen text */ +"create_account_profile_hint" = "np. Dom, Iphone"; +/* Login screen text */ +"set_password_title" = "Ustaw hasło"; +/* Login screen text */ +"set_password_hint" = "Hasło jest wymagane dla ochrony twoich danych. Przechowuj go w bezpiecznym dla Ciebie miejscu. Utracone hasło nie może być odzyskane."; +/* Login button */ +"create_account_next_button" = "Dalej"; +/* Login button */ +"create_account_go_button" = "Idź"; +/* Default status message */ +"default_user_status_message" = "Tox dla Anidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Wprowadź swój kod PIN do odblokowania"; +/* PIN screen text */ +"pin_set" = "Ustaw PIN"; +/* PIN screen text */ +"pin_confirm" = "Potwierdź PIN"; +/* PIN screen error */ +"pin_do_not_match" = "PINy są różne. Spróbuj ponownie"; +/* PIN screen error */ +"pin_incorrect" = "Nieprawidłowy PIN"; +/* PIN screen error details */ +"pin_failed_attempts" = "Nieudane próby: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Zbyt wiele nieudanych prób. Nastąpiło wylogowanie."; +/* PIN screen screen */ +"pin_enabled" = "Włącz PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Udostępnij ID"; +/* PIN screen screen */ +"pin_description" = "Zapobiegnij nieuprawnionemu dostępowi do programu aktywując kod PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "Jako alternatywy do PIN-u odcisku palca."; +/* PIN screen screen */ +"pin_lock_timeout" = "Blokada czasowa"; +/* PIN screen screen */ +"pin_lock_immediately" = "Natychmiast"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 sekund"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 minuta"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 minuty"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 minut"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Łącze ..."; + +/* Tab name and screen name */ +"contacts_title" = "Kontakty"; +/* Tab name and screen name */ +"chats_title" = "Czaty"; +/* Tab name and screen name */ +"settings_title" = "Ustawienia"; +/* Tab name and screen name */ +"profile_title" = "Profil"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Brak kontaktów"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Dodaj kontakt\nlub\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "udostępnij swój Tox ID"; +/* Contact request section title */ +"contact_requests_section" = "Zapytania kontaktowe"; +/* Contact last seen status */ +"contact_last_seen" = "Ostatnio widziany: %@"; +/* Screen name */ +"contact_request" = "Prośba kontaktu"; +/* Contact request button */ +"contact_request_decline" = "Odmowa"; +/* Contact request button */ +"contact_request_accept" = "Akceptacja"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Skasować zadanie, prośbę kontaktu?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Usunięto kontakt"; + +/* Share Tox ID text */ +"show_qr_code" = "Pokaż QR-Kod"; +/* Share Tox ID text */ +"copy" = "Kopiuj"; + +/* Add contat screen name */ +"add_contact_title" = "Dodaj kontakt"; +/* Add contat button */ +"add_contact_send" = "Wyślij"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Wprowadź Tox-ID"; +/* Add contat placeholder text */ +"add_contact_or_label" = "lub"; +/* Add contat button */ +"add_contact_use_qr" = "Użyj kodu QR"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Wiadomość"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Cześć, witaj! Czy możesz dodać mnie do listy twoich kontaktów?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Niepoprawny kod QR. Powinien on zawierać Tox ID"; + +/* User name text */ +"name" = "Nazwisko"; +/* User nickname text */ +"nickname" = "Ksywka"; +/* User status message text */ +"status_message" = "Status wiadomości"; +/* User status text */ +"status_title" = "Status"; +/* User Tox ID text */ +"my_tox_id" = "Mój Tox ID"; +/* User public key text */ +"public_key" = "Klucz publiczny"; +/* Share Tox ID text */ +"show_qr" = "Pokaż kod QR"; +/* Profile menu item / screen name */ +"profile_details" = "Dane profilu"; +/* Profile button */ +"logout_button" = "Wyloguj się"; + +/* QR code screen button */ +"qr_close_button" = "Zamknij"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Brak czatów"; +/* Chats screen message text */ +"chat_outgoing_file" = "Wysyłany plik:"; +/* Chats screen message text */ +"chat_incoming_file" = "Odbierany plik:"; +/* Chats screen message text */ +"chat_call_finished" = "Rozmowa zakończona"; +/* Chats screen message text */ +"chat_unanwered_call" = "Rozmowa nieodebrana"; +/* Chat button */ +"chat_send_button" = "Wyślij"; +/* Chat notification toast */ +"chat_new_messages" = "↓ Nowa wiadomość"; +/* Chat call information */ +"chat_call_message" = "Połączenie, "; +/* Chat call information */ +"chat_missed_call_message" = "Rozmowa nieodebrana"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Więcej"; + +/* Status message */ +"status_offline" = "Niedostępny"; +/* Status message */ +"status_online" = "Dostępny"; +/* Status message */ +"status_away" = "Zaraz wracam"; +/* Status message */ +"status_busy" = "Zajęty"; + +/* Notification text */ +"notification_new_message" = "Nowa wiadomość"; +/* Notification text */ +"notification_incoming_contact_request" = "Nadchodząca prośba kontaktu"; +/* Notification text */ +"notification_is_calling" = "telefonuje"; +/* Notification text */ +"notification_incoming_file" = "Nadchodzący plik"; + +/* Settings menu / screen name */ +"settings_about" = "O nas"; +/* Settings menu / screen name */ +"settings_faq" = "Najczęstsze pytania"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Ustawienia zaawansowane"; +/* About screen menu */ +"settings_antidote_version" = "Wersja programu Antidote"; +/* About screen menu */ +"settings_antidote_build" = "Wersja kompilacji Antidote"; +/* About screen menu */ +"settings_toxcore_version" = "Wersja Toxcore"; +/* About screen menu */ +"settings_acknowledgements" = "Podziękowania"; +/* Settings screen menu */ +"settings_autodownload_images" = "Automatycznie pobieranie plików"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Automatycznie pobieraj przychodzące pliki."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Przegląd powiadomień"; +/* Settings screen menu */ +"settings_notifications_description" = "Jeżeli aplikacja będzie działa w tle, to masz możliwość odbioru powiadomień jeszcze przez okres 10-u minut."; +/* Settings screen menu */ +"settings_udp_enabled" = "Uruchom transmisje poprzez UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Przywróć domyślne ustawienia"; +/* Settings screen autodownload images option */ +"settings_never" = "Nigdy"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Użyj do połączeń internetu bezprzewodowego Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_always" = "Zawsze"; + +/* Profile settings menu */ +"change_password" = "Zmień hasło"; +/* Profile settings menu */ +"delete_password" = "Usuń Hasło"; +/* Profile settings menu */ +"old_password" = "Stare Hasło"; +/* Change password text */ +"new_password" = "Nowe Hasło"; +/* Change password text */ +"repeat_password" = "Powtórz hasło"; +/* Change password button */ +"change_password_done" = "Zakończ"; +/* Change password error */ +"password_is_empty_error" = "Ta wartość nie może być pusta"; +/* Change password error */ +"wrong_old_password" = "Niepoprawne hasło"; +/* Change password error */ +"passwords_do_not_match" = "Hasło nie jest identyczne"; + +/* Source of photo to take */ +"photo_from_camera" = "Aparat"; +/* Source of photo to take */ +"photo_from_photo_library" = "Biblioteka zdjęć"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Brak możliwości przekształcenia obrazu"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Wyślij do osoby"; + +/* Profile menu item */ +"export_profile" = "Eksportuj profil"; +/* Profile menu item */ +"delete_profile" = "Skasuj profil"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Czy skasować profil?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Czy na pewno?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Przeprowadzenie niniejszej operacji nie może być cofnięte"; + +/* Call screen text */ +"call_incoming" = "Rozmowa przychodząca"; +/* Call screen text */ +"call_ended" = "Rozmowa zakończona"; +/* Call screen text */ +"call_reaching" = "Ustanawiam połączenie..."; + +/* File message text */ +"chat_file_cancelled" = "Anulowano"; +/* File message text */ +"chat_waiting" = "Oczekuje..."; +/* File message text */ +"chat_paused" = "Wstrzymany"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Skasować tego czata i jego wiadomości?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Skasować ten kontakt?\nHistoria korespondencji będzie skasowana."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Skasować to żądanie kontaktu?"; +/* Deleting single message in chat */ +"delete_single_message" = "Skasuj wiadomość"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Skasuj wiadomości"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Skasuj wszystko"; +/* Delete button */ +"alert_delete" = "Usuń"; +/* Delete button */ +"alert_cancel" = "Anuluj"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Wprowadź nazwę użytkownika i nazwę profilu."; +/* Error while creating new profile */ +"login_profile_already_exists" = "Wprowadzona nazwa profilu jest już niestety zajęta"; + +/* General error title */ +"error_title" = "Błąd"; +/* General error button */ +"error_ok_button" = "OK"; +/* General error button */ +"error_retry_button" = "Powtórz"; +/* Error */ +"error_contact_not_connected" = "Osoba jest niedostępna"; +/* Error */ +"error_too_many_files" = "Zbyt duża liczba przesyłanych plików"; +/* Error */ +"error_internal_message" = "Błąd wewnętrzny aplikacji"; +/* Error */ +"error_wrong_password_title" = "Niepoprawne hasło"; +/* Error */ +"error_wrong_password_message" = "Hasło zawiera niedopuszczalne symbole."; +/* Error */ +"error_import_not_exist_title" = "Brak możliwości importu pliku tox"; +/* Error */ +"error_import_not_exist_message" = "Plik nie istnieje."; +/* Error */ +"error_decrypt_title" = "Brak możliwości rozkodowania pliku tox"; +/* Error */ +"error_decrypt_empty_data_message" = "Wprowadzone dane są niepełne."; +/* Error */ +"error_decrypt_bad_format_message" = "Format pliku jest niedozwolony bądź plik jest uszkodzony."; +/* Error */ +"error_decrypt_wrong_password_message" = "Hasło jest niepoprawne lub plik uszkodzony."; +/* Error */ +"error_general_unknown_message" = "Nieznany błąd."; +/* Error */ +"error_general_no_memory_message" = "Brak wystarczającej ilości pamięci."; +/* Error */ +"error_general_bind_port_message" = "Brak możliwości otworzenia portu komunikacji."; +/* Error */ +"error_general_profile_encrypted_message" = "Profil jest zaszyfrowany."; +/* Error */ +"error_general_bad_format_message" = "Plik ma niepoprawny format lub jest uszkodzony."; +/* Error */ +"error_proxy_title" = "Błąd serwera proxy"; +/* Error */ +"error_proxy_invalid_address_message" = "Adres serwera proxy ma niepoprawny format."; +/* Error */ +"error_proxy_invalid_port_message" = "Port proxy jest niepoprawny."; +/* Error */ +"error_proxy_host_not_resolved_message" = "Translacja DNS-IP Proxy hosta jest niemożliwa."; + +/* Error */ +"error_name_too_long" = "Nazwa jest zbyt długa."; +/* Error */ +"error_status_message_too_long" = "Status wiadomości jest zbyt długi"; + +/* Error */ +"error_contact_request_too_long" = "Wiadomość jest zbyt długa"; +/* Error */ +"error_contact_request_no_message" = "Brak określonej wiadomości"; +/* Error */ +"error_contact_request_own_key" = "Dodanie samego siebie do listy kontaktów jest niemożliwe"; +/* Error */ +"error_contact_request_already_sent" = "Zapytanie zostało już wysłane"; +/* Error */ +"error_contact_request_bad_checksum" = "Niepoprawna suma kontrolna, proszę sprawdzić wprowadzony Tox ID"; +/* Error */ +"error_contact_request_new_nospam" = "Niepoprawna wartość nospam, proszę sprawdzić Tox ID"; + +/* Call error */ +"call_error_already_in_call" = "Przeprowadza rozmowę"; +/* Call error */ +"call_error_contact_is_offline" = "Osoba jest aktualnie niedostępna"; +/* Call error */ +"call_error_no_active_call" = "Brak aktywnych połączeń"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Niniejszy temat graficzny do programu czyli tzw. skórka ma niepoprawny format"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "nieodczytane czaty"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Ustawia lub usuwa awatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Chat"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Otwiera okno dialogowe czatu."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Połączenie głosowe"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Rozpoczyna połączenie głosowe."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Połączenie wideo"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Rozpoczyna połączenie wideo."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Pokazuje menu kopiowania."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Edytuje wartość."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Message"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Twoja wiadomość"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "pisze..."; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Niedostarczone wiadomości będą wysyłane, gdy zarówno Ty, jak i znajomy będziecie online."; diff --git a/Antidote/pl.lproj/import-profile.html b/Antidote/pl.lproj/import-profile.html new file mode 100644 index 0000000..90bfb7b --- /dev/null +++ b/Antidote/pl.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

Importuj profil Tox

+ +
    +
  1. Wyślij plik ".tox" do urządzenia używając aplikacji (Mail, Dropbox, itp.).
  2. +
  3. Użyj opcji "Otwórz w" dla tego pliku.
  4. +
  5. Wybierz Antidote z listy dostępnych aplikacji.
  6. +
  7. Wybierz nazwę twojego nowego profilu i wciśnij OK.
  8. +
diff --git a/Antidote/pt-BR.lproj/AppStoreLocalizable.strings b/Antidote/pt-BR.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..d6fbb04 --- /dev/null +++ b/Antidote/pt-BR.lproj/AppStoreLocalizable.strings @@ -0,0 +1,54 @@ +/* + AppStoreLocalizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/02/17. + Copyright © 2017 dvor. All rights reserved. +*/ + +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_1" = "Mary Cokley"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_2" = "Shirley Knox"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_3" = "Jennifer Smith"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_4" = "Marina Dixon"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_female_5" = "Carol Ortega"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_1" = "Michael Sharpe"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_2" = "Charles Donahue"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_3" = "Lee Murdock"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_4" = "Wayne Henderson"; +/* Name of friend for the App Store screenshot. Should be fully random, no real people here. Can be generated with service like http://www.fakenamegenerator.com/ */ +"app_store_screenshot_friend_male_5" = "Robert Newton"; + +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_1" = "Is Antidote really that secure?"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_2" = "sure, it is peer-to-peer"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_3" = "And what does that mean? Peer-to-peer? 😄"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_4" = "you text me directly, the are no servers or things like that"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_5" = "+ it's encrypted 🔐😎"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_6" = "Cool!"; +/* Demo conversation for the App Store screenshot */ +"app_store_screenshot_conversation_7" = "I'll give it a go then"; + +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_1" = "😂😂😂"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_2" = "dinner tonight?"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_3" = "I think I know what you are talking about"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_4" = "Sure, thanks!"; +/* Demo chat message for the App Store screenshot */ +"app_store_screenshot_chat_message_5" = "yep"; diff --git a/Antidote/pt-BR.lproj/InfoPlist.strings b/Antidote/pt-BR.lproj/InfoPlist.strings new file mode 100644 index 0000000..b79a4d0 --- /dev/null +++ b/Antidote/pt-BR.lproj/InfoPlist.strings @@ -0,0 +1,16 @@ +/* + InfoPlist.strings + Antidote + + Created by Dmytro Vorobiov on 15/11/16. + Copyright © 2016 dvor. All rights reserved. +*/ + +/* Camera usage alert description */ +"NSCameraUsageDescription" = "You can use video calls, send photos and videos, scan QR codes."; + +/* Microphone usage alert description */ +"NSMicrophoneUsageDescription" = "You can use audio and video calls."; + +/* Photo library usage alert description */ +"NSPhotoLibraryUsageDescription" = "You can send photos and videos."; \ No newline at end of file diff --git a/Antidote/pt-BR.lproj/Localizable.strings b/Antidote/pt-BR.lproj/Localizable.strings new file mode 100644 index 0000000..40a9be4 --- /dev/null +++ b/Antidote/pt-BR.lproj/Localizable.strings @@ -0,0 +1,404 @@ + + +/* Login screen text */ +"login_or_label" = "ou"; +/* Login screen text */ +"create_account_profile_placeholder" = "Nome de perfil"; +/* Login screen text */ +"set_password_hint" = "Uma senha é necessária para proteger seus dados. Mantenha-a segura - uma vez perdida, ela não pode ser restaurada."; +/* PIN screen screen */ +"pin_enabled" = "Ativar PIN"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Muitas tentativas falhas. Você foi desconectado."; +/* Contact request button */ +"contact_request_decline" = "Recusar"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Mensagem"; +/* Login button */ +"log_in" = "Entrar"; +/* Login screen text */ +"create_profile" = "Criar perfil"; +/* Login screen text */ +"create_account_username_title" = "Como os contatos irão te ver?"; +/* Login screen text */ +"create_account_username_placeholder" = "Nome de usuário"; +/* Login screen text */ +"create_account_profile_title" = "Como chamar este perfil?"; +/* Login screen text */ +"set_password_title" = "Definir Senha"; +/* Login button */ +"create_account_next_button" = "Próximo"; +/* Default status message */ +"default_user_status_message" = "Toxing no Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Entre seu PIN para desbloquear"; +/* PIN screen text */ +"pin_set" = "Definir PIN"; +/* PIN screen text */ +"pin_confirm" = "Confirmar PIN"; +/* PIN screen error */ +"pin_do_not_match" = "PINs não conferem. Tente novamente"; +/* PIN screen error */ +"pin_incorrect" = "PIN incorreto"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Ativar Touch ID"; +/* PIN screen screen */ +"pin_description" = "Prevenir acesso não-autorizado ao Antidote com um PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "Tempo de bloqueio"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 minutos"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 minutos"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Conectando..."; + +/* Tab name and screen name */ +"contacts_title" = "Contatos"; +/* Tab name and screen name */ +"chats_title" = "Conversas"; +/* Tab name and screen name */ +"settings_title" = "Configurações"; +/* Tab name and screen name */ +"profile_title" = "Perfil"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Sem contatos"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Adicionar um contato\nou\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "compartilhar seu ID do Tox"; +/* Contact request section title */ +"contact_requests_section" = "Solicitações de contato"; +/* Contact request button */ +"contact_request_accept" = "Aceitar"; + +/* Share Tox ID text */ +"show_qr_code" = "Exibir código QR"; +/* Share Tox ID text */ +"copy" = "Copiar"; + +/* Add contat screen name */ +"add_contact_title" = "Adicionar contato"; +/* Add contat placeholder text */ +"add_contact_or_label" = "ou"; +/* Add contat button */ +"add_contact_use_qr" = "Usar código QR"; +/* Add contat error */ +"add_contact_wrong_qr" = "Código QR inválido. Ele deve conter um ID do Tox"; +/* User Tox ID text */ +"my_tox_id" = "Meu ID do Tox"; +/* User public key text */ +"public_key" = "Chave Pública"; +/* Profile menu item / screen name */ +"profile_details" = "Detalhes do perfil"; +/* Profile button */ +"logout_button" = "Sair"; + +/* QR code screen button */ +"qr_close_button" = "Fechar"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Sem conversas"; +/* Chats screen message text */ +"chat_outgoing_file" = "Arquivo de saída:"; +/* Chats screen message text */ +"chat_incoming_file" = "Arquivo de entrada:"; +/* Login screen text */ +"create_account" = "Criar Conta"; +/* Login screen text */ +"import_profile" = "Importar Perfil"; +/* PIN screen error details */ +"pin_failed_attempts" = "Tentativas erradas: %@"; +/* Password field */ +"password" = "Senha"; +/* Login screen text */ +"import_to_antidote" = "Importar para o Antidote"; +/* Login screen text */ +"create_account_profile_hint" = "ex. Home, iPhone"; +/* Login button */ +"create_account_go_button" = "Ir"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 segundos"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 minuto"; +/* Contact last seen status */ +"contact_last_seen" = "Visto por último: %@"; +/* PIN screen screen */ +"pin_touch_id_description" = "Usar sua impressão digital como uma alternativa ao PIN."; +/* PIN screen screen */ +"pin_lock_immediately" = "Imediatamente"; +/* Screen name */ +"contact_request" = "Solicitações de contato"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Deletar solicitação de contato?"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Entrar ID do Tox"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Contato deletado"; +/* Add contat button */ +"add_contact_send" = "Enviar"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Olá! Poderia por favor me adicionar à sua lista de contatos?"; +/* User nickname text */ +"nickname" = "Apelido"; +/* User status message text */ +"status_message" = "Mensagem de status"; +/* User status text */ +"status_title" = "Status"; + +/* User name text */ +"name" = "Nome"; +/* Chats screen message text */ +"chat_call_finished" = "Chamada finalizada"; +/* Share Tox ID text */ +"show_qr" = "Exibir QR"; +/* Chats screen message text */ +"chat_unanwered_call" = "Chamada não atendida"; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Mensagens não entregues serão enviadas quando você e seu contato estiverem online."; + +/* Status message */ +"status_offline" = "Desconectado"; +/* Status message */ +"status_online" = "Online"; +/* Status message */ +"status_away" = "Ausente"; +/* Status message */ +"status_busy" = "Ocupado"; + +/* Notification text */ +"notification_new_message" = "Nova mensagem"; +/* Error */ +"error_internal_message" = "Erro interno"; +/* Error */ +"error_general_bind_port_message" = "Impossível vincular a uma porta."; +/* Error */ +"error_proxy_invalid_port_message" = "Porta de proxy inválida."; +/* Error */ +"error_proxy_host_not_resolved_message" = "Host de proxy não pode ser resolvido."; + +/* Error */ +"error_name_too_long" = "O nome é longo demais."; +/* Error */ +"error_contact_request_already_sent" = "Solicitação de contato já enviada"; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Prévia da notificação"; +/* Source of photo to take */ +"photo_from_photo_library" = "Biblioteca de fotos"; +/* Chat button */ +"chat_send_button" = "Enviar"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Mais"; +/* Settings menu / screen name */ +"settings_faq" = "FAQ"; +/* About screen menu */ +"settings_acknowledgements" = "Agradecimentos"; +/* Settings screen menu */ +"settings_notifications_description" = "Quando a aplicação fica em plano de fundo, você ainda recebe notificações em até 10 minutos."; +/* Settings screen menu */ +"settings_udp_enabled" = "Ativar UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Restaurar configurações padrão"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "Usando Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_always" = "Sempre"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Enviar ao contato"; +/* Chat call information */ +"chat_call_message" = "Chamada, "; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "está digitando..."; +/* Chat notification toast */ +"chat_new_messages" = "↓ Novas mensagens"; +/* Chat call information */ +"chat_missed_call_message" = "Chamada perdida"; +/* Notification text */ +"notification_incoming_file" = "Recebendo arquivo"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Configurações avançadas"; +/* About screen menu */ +"settings_antidote_version" = "Versão do Antidote"; +/* About screen menu */ +"settings_antidote_build" = "Compilação do Antidote"; +/* Notification text */ +"notification_incoming_contact_request" = "Recebendo solicitação de contato"; +/* Notification text */ +"notification_is_calling" = "está em chamada"; +/* Settings screen menu */ +"settings_autodownload_images" = "Baixar arquivos automaticamente"; + +/* Profile settings menu */ +"change_password" = "Trocar senha"; +/* Change password error */ +"wrong_old_password" = "Senha incorreta"; + +/* Source of photo to take */ +"photo_from_camera" = "Câmera"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Impossível converter imagem"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Esta operação não pode ser desfeita"; + +/* Settings menu / screen name */ +"settings_about" = "Sobre"; +/* About screen menu */ +"settings_toxcore_version" = "Versão do Toxcore"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Baixar automaticamente os arquivos recebidos."; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Você tem certeza?"; +/* Change password error */ +"password_is_empty_error" = "A senha não deve ser vazia"; +/* Settings screen autodownload images option */ +"settings_never" = "Nunca"; +/* Profile settings menu */ +"delete_password" = "Deletar senha"; +/* Change password text */ +"new_password" = "Nova senha"; +/* Change password text */ +"repeat_password" = "Repita a senha"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Profile settings menu */ +"old_password" = "Senha antiga"; +/* Change password button */ +"change_password_done" = "Concluído"; +/* Change password error */ +"passwords_do_not_match" = "As senhas não coincidem"; + +/* Profile menu item */ +"export_profile" = "Exportar perfil"; +/* Profile menu item */ +"delete_profile" = "Deletar perfil"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Deletar este perfil?"; + +/* File message text */ +"chat_file_cancelled" = "Cancelado"; +/* File message text */ +"chat_waiting" = "Aguardando..."; +/* File message text */ +"chat_paused" = "Pausado"; +/* Call screen text */ +"call_reaching" = "Contatando..."; + +/* Call screen text */ +"call_incoming" = "Recebendo chamada"; +/* Call screen text */ +"call_ended" = "Chamada finalizada"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Deletar este contato?\nO histórico de mensagens será perdido."; +/* Error */ +"error_too_many_files" = "Há transferências de arquivos ativas demais"; +/* Error */ +"error_wrong_password_title" = "Senha incorreta"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Deletar esta conversa e o histórico de mensagens?"; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Deletar esta solicitação de contato?"; +/* Deleting single message in chat */ +"delete_single_message" = "Deletar mensagem"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Deletar mensagens"; +/* Delete button */ +"alert_cancel" = "Cancelar"; +/* General error button */ +"error_ok_button" = "OK"; +/* Error */ +"error_wrong_password_message" = "A senha contém símbolos incorretos."; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Deletar tudo"; +/* Delete button */ +"alert_delete" = "Deletar"; +/* Error while creating new profile */ +"login_profile_already_exists" = "Um perfil com este nome já existe"; + +/* General error title */ +"error_title" = "Erro"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Por favor insira o nome de usuário e o nome de perfil."; +/* General error button */ +"error_retry_button" = "Tentar novamente"; +/* Error */ +"error_contact_not_connected" = "O contato não está online"; +/* Error */ +"error_decrypt_empty_data_message" = "Alguns dados de entrada estavam vazios."; +/* Error */ +"error_decrypt_wrong_password_message" = "A senha está incorreta ou o arquivo está corrompido."; +/* Error */ +"error_general_unknown_message" = "Ocorreu um erro desconhecido."; +/* Error */ +"error_general_no_memory_message" = "Não há memória o suficiente."; +/* Error */ +"error_general_profile_encrypted_message" = "O perfil está criptografado."; +/* Error */ +"error_import_not_exist_title" = "Arquivo de salvamento do Tox não pôde ser importado"; +/* Error */ +"error_import_not_exist_message" = "O arquivo não existe."; +/* Error */ +"error_decrypt_bad_format_message" = "O arquivo tem o formato errado ou está corrompido."; + +/* Error */ +"error_contact_request_too_long" = "A mensagem é longa demais"; +/* Error */ +"error_contact_request_no_message" = "Nenhuma mensagem especificada"; +/* Error */ +"error_decrypt_title" = "O arquivo de salvamento do Tox não pode ser descriptografado"; +/* Error */ +"error_general_bad_format_message" = "O arquivo tem o formato errado ou está corrompido."; +/* Error */ +"error_proxy_title" = "Erro de proxy"; +/* Error */ +"error_proxy_invalid_address_message" = "Endereço de proxy tem um formato inválido."; +/* Error */ +"error_contact_request_bad_checksum" = "Checksum incorreto, por favor cheque o ID do Tox inserido"; +/* Error */ +"error_status_message_too_long" = "A mensagem de status é longa demais"; +/* Error */ +"error_contact_request_own_key" = "Não é possível adicionar si mesmo à lista de contatos"; +/* Error */ +"error_contact_request_new_nospam" = "Valor incorreto de nospam, por favor cheque o ID do Tox inserido"; +/* Call error */ +"call_error_contact_is_offline" = "O contato está offline"; +/* Call error */ +"call_error_no_active_call" = "Não há nenhuma chamada ativa"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Define ou remove o avatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Conversa"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Abre a caixa de diálogo do chat."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Edita o valor."; + +/* Call error */ +"call_error_already_in_call" = "Já em chamada"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "O tema não pôde ser aberto, o formato está incorreto"; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Exibe o menu de cópia."; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "conversas não lidas"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Chamada de vídeo"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Faz uma chamada de vídeo."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Mensagem"; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Chamada de áudio"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Faz uma chamada de áudio."; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Sua mensagem"; diff --git a/Antidote/pt-BR.lproj/import-profile.html b/Antidote/pt-BR.lproj/import-profile.html new file mode 100644 index 0000000..7f2b2f3 --- /dev/null +++ b/Antidote/pt-BR.lproj/import-profile.html @@ -0,0 +1,10 @@ + +

To import your Tox profile:

+ +
    +
  1. Send the ".tox" file to your device using any app (Mail, Dropbox, etc.).
  2. +
  3. Use "Open In" menu for this file.
  4. +
  5. Select Antidote in a list of available apps.
  6. +
  7. Check the name of your new profile and press OK.
  8. +
+
diff --git a/Antidote/pt.lproj/AppStoreLocalizable.strings b/Antidote/pt.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..3f83883 Binary files /dev/null and b/Antidote/pt.lproj/AppStoreLocalizable.strings differ diff --git a/Antidote/pt.lproj/InfoPlist.strings b/Antidote/pt.lproj/InfoPlist.strings new file mode 100644 index 0000000..6c333a8 Binary files /dev/null and b/Antidote/pt.lproj/InfoPlist.strings differ diff --git a/Antidote/pt.lproj/Localizable.strings b/Antidote/pt.lproj/Localizable.strings new file mode 100644 index 0000000..895844b --- /dev/null +++ b/Antidote/pt.lproj/Localizable.strings @@ -0,0 +1,415 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* + Translation is done with Transifex service. + See https://www.transifex.com/antidote/antidote-ios/ +*/ + +/* Login screen text */ +"login_or_label" = "ou"; +/* Login screen text */ +"create_account" = "Criar Conta"; +/* Login screen text */ +"import_profile" = "Importar Perfil"; +/* Password field */ +"password" = "Palavra-Passe"; +/* Login button */ +"log_in" = "Entrar"; +/* Login screen text */ +"create_profile" = "Criar Perfil"; +/* Login screen text */ +"import_to_antidote" = "Importar para Antidote"; +/* Login screen text */ +"create_account_username_title" = "Como os contactos irão vê-lo?"; +/* Login screen text */ +"create_account_username_placeholder" = "Nome de Utilizador"; +/* Login screen text */ +"create_account_profile_title" = "Que nome deve ter este perfil"; +/* Login screen text */ +"create_account_profile_placeholder" = "Nome do perfil"; +/* Login screen text */ +"create_account_profile_hint" = "exemplo: Casa, iPhone"; +/* Login screen text */ +"set_password_title" = "Set Password"; +/* Login screen text */ +"set_password_hint" = "Password is required to protect your data. Keep it safe - once lost it cannot be restored."; +/* Login button */ +"create_account_next_button" = "Next"; +/* Login button */ +"create_account_go_button" = "Ir"; +/* Default status message */ +"default_user_status_message" = "Toxing com Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Enter your PIN to unlock"; +/* PIN screen text */ +"pin_set" = "Set PIN"; +/* PIN screen text */ +"pin_confirm" = "Confirm PIN"; +/* PIN screen error */ +"pin_do_not_match" = "PINs did not match. Try again"; +/* PIN screen error */ +"pin_incorrect" = "Incorrect PIN"; +/* PIN screen error details */ +"pin_failed_attempts" = "Failed attempts: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Too many failed attempts. You have been logged out."; +/* PIN screen screen */ +"pin_enabled" = "Enable PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Enable Touch ID"; +/* PIN screen screen */ +"pin_description" = "Prevent unauthorized access to Antidote with a PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "Use your fingerprint as an alternative to entering a PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "Lock Timeout"; +/* PIN screen screen */ +"pin_lock_immediately" = "Immediately"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 Seconds"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 Minute"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 Minutes"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 Minutes"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Ligando"; + +/* Tab name and screen name */ +"contacts_title" = "Contactos"; +/* Tab name and screen name */ +"chats_title" = "Conversas"; +/* Tab name and screen name */ +"settings_title" = "Configurações"; +/* Tab name and screen name */ +"profile_title" = "Perfil"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Nenhum contacto"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Adiciona contacto\nou\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "partilha o teu Tox ID"; +/* Contact request section title */ +"contact_requests_section" = "Pedidos"; +/* Contact last seen status */ +"contact_last_seen" = "Online ontem a: %@"; +/* Screen name */ +"contact_request" = "Pedido de Contacto"; +/* Contact request button */ +"contact_request_decline" = "Rejeitar"; +/* Contact request button */ +"contact_request_accept" = "Aceitar"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Rejeita o pedido"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Deleted contact"; + +/* Share Tox ID text */ +"show_qr_code" = "Mostra o código QR"; +/* Share Tox ID text */ +"copy" = "Copiar"; + +/* Add contat screen name */ +"add_contact_title" = "Adicionar Contacto"; +/* Add contat button */ +"add_contact_send" = "Enviar"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Introduza Tox ID"; +/* Add contat placeholder text */ +"add_contact_or_label" = "ou"; +/* Add contat button */ +"add_contact_use_qr" = "Usa Código QR"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Mensagem"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Olá! Podias por favor adicionar-me a tua lista de contactos?"; +/* Add contat error */ +"add_contact_wrong_qr" = "Código QR Errado. Deve conter o Tox ID"; + +/* User name text */ +"name" = "Nome"; +/* User nickname text */ +"nickname" = "Alcunha"; +/* User status message text */ +"status_message" = "Mensagem de Estado"; +/* User status text */ +"status_title" = "Estado"; +/* User Tox ID text */ +"my_tox_id" = "Tox ID"; +/* User public key text */ +"public_key" = "Chave Pública"; +/* Share Tox ID text */ +"show_qr" = "Mostra QR"; +/* Profile menu item / screen name */ +"profile_details" = "Detalhes Perfil"; +/* Profile button */ +"logout_button" = "Desligar"; + +/* QR code screen button */ +"qr_close_button" = "Fechar"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Sem conversas"; +/* Chats screen message text */ +"chat_outgoing_file" = "Enviar ficheiro"; +/* Chats screen message text */ +"chat_incoming_file" = "Receber ficheiro"; +/* Chats screen message text */ +"chat_call_finished" = "Chamada terminada"; +/* Chats screen message text */ +"chat_unanwered_call" = "Chamada não atendida"; +/* Chat button */ +"chat_send_button" = "Enviar"; +/* Chat notification toast */ +"chat_new_messages" = "Novas mensagens"; +/* Chat call information */ +"chat_call_message" = "Chamada, "; +/* Chat call information */ +"chat_missed_call_message" = "Chamada perdida"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "More"; + +/* Status message */ +"status_offline" = "Indisponível"; +/* Status message */ +"status_online" = "Disponível"; +/* Status message */ +"status_away" = "Ausente"; +/* Status message */ +"status_busy" = "Ocupado"; + +/* Notification text */ +"notification_new_message" = "Mensagem nova"; +/* Notification text */ +"notification_incoming_contact_request" = "Pedido de contacto recebido"; +/* Notification text */ +"notification_is_calling" = "a ligar"; +/* Notification text */ +"notification_incoming_file" = "Receber ficheiro?"; + +/* Settings menu / screen name */ +"settings_about" = "Acerca"; +/* Settings menu / screen name */ +"settings_faq" = "FAQ"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Configurações avançadas"; +/* About screen menu */ +"settings_antidote_version" = "Versão Antidote"; +/* About screen menu */ +"settings_antidote_build" = "Antidote Build"; +/* About screen menu */ +"settings_toxcore_version" = "Versão Toxcore"; +/* About screen menu */ +"settings_acknowledgements" = "Acknowledgements"; +/* Settings screen menu */ +"settings_autodownload_images" = "Descarregar imagens automaticamente"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Descarregar automaticamente os ficheiros recebidos."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Pre-visualização de notificações"; +/* Settings screen menu */ +"settings_notifications_description" = "Quando a aplicação fica em segundo plano, as notificações ficarão activas durante 10 minutos"; +/* Settings screen menu */ +"settings_udp_enabled" = "Enable UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Restaurar definições iniciais"; +/* Settings screen autodownload images option */ +"settings_never" = "Nunca"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "A usar Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_always" = "Sempre"; + +/* Profile settings menu */ +"change_password" = "Alterar Palavra-Passe"; +/* Profile settings menu */ +"delete_password" = "Apagar Palavra-Passe"; +/* Profile settings menu */ +"old_password" = "Palavra-Passe Antiga"; +/* Change password text */ +"new_password" = "Nova Palavra-Passe"; +/* Change password text */ +"repeat_password" = "Repete Palavra-Passe"; +/* Change password button */ +"change_password_done" = "Terminado"; +/* Change password error */ +"password_is_empty_error" = "Palavra-Passe não pode estar vazia"; +/* Change password error */ +"wrong_old_password" = "Palavra-Passe errada"; +/* Change password error */ +"passwords_do_not_match" = "Palavras-Passe não coincidem"; + +/* Source of photo to take */ +"photo_from_camera" = "Câmara"; +/* Source of photo to take */ +"photo_from_photo_library" = "Fotografias"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Não é possivel converter imagem"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Envia a contacto"; + +/* Profile menu item */ +"export_profile" = "Exportar Perfil"; +/* Profile menu item */ +"delete_profile" = "Apagar Perfil"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Apagar este Perfil?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Confirmar?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Não é possível refazer esta operação"; + +/* Call screen text */ +"call_incoming" = "A receber chamada"; +/* Call screen text */ +"call_ended" = "Chamada terminada"; +/* Call screen text */ +"call_reaching" = "A ligar/ A conectar"; + +/* File message text */ +"chat_file_cancelled" = "Cancelada"; +/* File message text */ +"chat_waiting" = "Em espera"; +/* File message text */ +"chat_paused" = "Em pausa"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Apagar esta conversa e o histórico de mensagens?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Apagar este contacto?\nO histórico de mensagens será apagado."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Rejeitar pedido?"; +/* Deleting single message in chat */ +"delete_single_message" = "Delete Message"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Delete Messages"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Delete All"; +/* Delete button */ +"alert_delete" = "Apagar"; +/* Delete button */ +"alert_cancel" = "Cancelar"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Insira o nome de utilizador e o nome do perfil"; +/* Error while creating new profile */ +"login_profile_already_exists" = "Nome escolhido já existe"; + +/* General error title */ +"error_title" = "Erro"; +/* General error button */ +"error_ok_button" = "OK"; +/* General error button */ +"error_retry_button" = "Tentar de novo"; +/* Error */ +"error_contact_not_connected" = "Contacto não está disponivel"; +/* Error */ +"error_too_many_files" = "Demasiadas transferências activas"; +/* Error */ +"error_internal_message" = "Erro interno"; +/* Error */ +"error_wrong_password_title" = "Palavra-Passe errada"; +/* Error */ +"error_wrong_password_message" = "Palavra-Passe contêm simbolos errados"; +/* Error */ +"error_import_not_exist_title" = "Não é possivel importar tox salvar/guardar ficheiro"; +/* Error */ +"error_import_not_exist_message" = "Ficheiro inexistente"; +/* Error */ +"error_decrypt_title" = "Não é possivel decriptar tox salvar/guardar ficheiro"; +/* Error */ +"error_decrypt_empty_data_message" = "Input data vazio"; +/* Error */ +"error_decrypt_bad_format_message" = "Ficheiro corrompido ou no formato errado"; +/* Error */ +"error_decrypt_wrong_password_message" = "Palavra-passe errada ou o ficheiro esta corrompido"; +/* Error */ +"error_general_unknown_message" = "Erro desconhecido"; +/* Error */ +"error_general_no_memory_message" = "Memória insuficiente"; +/* Error */ +"error_general_bind_port_message" = "Incapaz de ligar/conectar a uma porta"; +/* Error */ +"error_general_profile_encrypted_message" = "Perfil está encriptado"; +/* Error */ +"error_general_bad_format_message" = "Ficheiro têm um formato errado ou está corrompido"; +/* Error */ +"error_proxy_title" = "Erro de proxy"; +/* Error */ +"error_proxy_invalid_address_message" = "Proxy têm uma porta incorrecta"; +/* Error */ +"error_proxy_invalid_port_message" = "Porta proxy é inválida"; +/* Error */ +"error_proxy_host_not_resolved_message" = "Não é possivel ligar a proxy host"; + +/* Error */ +"error_name_too_long" = "Nome muito longo"; +/* Error */ +"error_status_message_too_long" = "Mensagem de status é demasiado longa"; + +/* Error */ +"error_contact_request_too_long" = "Mensagem demasiado extensa"; +/* Error */ +"error_contact_request_no_message" = "Nenhuma mensagem especificada"; +/* Error */ +"error_contact_request_own_key" = "Não pode adicionar-se a lista de contactos"; +/* Error */ +"error_contact_request_already_sent" = "Pedido de contacto já foi enviado"; +/* Error */ +"error_contact_request_bad_checksum" = "Má checksum, por favor confirme o Tox ID introduzido"; +/* Error */ +"error_contact_request_new_nospam" = "Má nospam value, por favor confirme o Tox ID introduzido"; + +/* Call error */ +"call_error_already_in_call" = "Numa chamada"; +/* Call error */ +"call_error_contact_is_offline" = "Contacto está indisponível"; +/* Call error */ +"call_error_no_active_call" = "Não existe nenhuma chamada activa"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Não é possivel abrir theme, formato incorrecto"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "unread chats"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Sets or removes avatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Chat"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Opens chat dialog."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Audio call"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Makes audio call."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Video call"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Makes video call."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Shows copy menu."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Edits value."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Message"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Your message"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "está a digitar..."; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Mensagens não entregues serão enviadas quando você e o amigo estiverem online."; diff --git a/Antidote/pt.lproj/import-profile.html b/Antidote/pt.lproj/import-profile.html new file mode 100644 index 0000000..e203a8f --- /dev/null +++ b/Antidote/pt.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

Para importar o seu perfil TOX:

+ +
    +
  1. Envie o ficheiro ".tox" para o seu aparelho usando (Mail, Dropbox, etc)
  2. +
  3. Utilize "Open In" menu para este ficheiro
  4. +
  5. Escolha Antidote na lista de opções
  6. +
  7. Confirme o nome do teu novo perfil e pressiona OK.
  8. +
diff --git a/Antidote/ru.lproj/AppStoreLocalizable.strings b/Antidote/ru.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..3f83883 Binary files /dev/null and b/Antidote/ru.lproj/AppStoreLocalizable.strings differ diff --git a/Antidote/ru.lproj/InfoPlist.strings b/Antidote/ru.lproj/InfoPlist.strings new file mode 100644 index 0000000..a601fc3 Binary files /dev/null and b/Antidote/ru.lproj/InfoPlist.strings differ diff --git a/Antidote/ru.lproj/Localizable.strings b/Antidote/ru.lproj/Localizable.strings new file mode 100644 index 0000000..7318ce2 --- /dev/null +++ b/Antidote/ru.lproj/Localizable.strings @@ -0,0 +1,415 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* + Translation is done with Transifex service. + See https://www.transifex.com/antidote/antidote-ios/ +*/ + +/* Login screen text */ +"login_or_label" = "или"; +/* Login screen text */ +"create_account" = "Создать аккаунт"; +/* Login screen text */ +"import_profile" = "Импорт профиля"; +/* Password field */ +"password" = "Пароль"; +/* Login button */ +"log_in" = "Войти"; +/* Login screen text */ +"create_profile" = "Создать профиль"; +/* Login screen text */ +"import_to_antidote" = "Импорт профиля"; +/* Login screen text */ +"create_account_username_title" = "Как показывать контакты?"; +/* Login screen text */ +"create_account_username_placeholder" = "Имя"; +/* Login screen text */ +"create_account_profile_title" = "Название профиля?"; +/* Login screen text */ +"create_account_profile_placeholder" = "Профиль"; +/* Login screen text */ +"create_account_profile_hint" = "напр. Home, iPhone"; +/* Login screen text */ +"set_password_title" = "Пароль"; +/* Login screen text */ +"set_password_hint" = "Пароль необходим, чтобы защитить ваши данные. Храните его в безопастности, потому что пароль невозможно восстановить. "; +/* Login button */ +"create_account_next_button" = "Далее"; +/* Login button */ +"create_account_go_button" = "Создать"; +/* Default status message */ +"default_user_status_message" = "Toxing on Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Введите PIN-код для разблокировки"; +/* PIN screen text */ +"pin_set" = "Установите PIN-код"; +/* PIN screen text */ +"pin_confirm" = "Подтвердите PIN-код"; +/* PIN screen error */ +"pin_do_not_match" = "PIN-коды не совпадают. Попробуйте снова"; +/* PIN screen error */ +"pin_incorrect" = "Неправильный PIN"; +/* PIN screen error details */ +"pin_failed_attempts" = "Failed attempts: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Too many failed attempts. You have been logged out."; +/* PIN screen screen */ +"pin_enabled" = "PIN-код включен"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Touch ID включен"; +/* PIN screen screen */ +"pin_description" = "Предотвратить неавторизованный доступ к Antidote с помощью PIN-кода."; +/* PIN screen screen */ +"pin_touch_id_description" = "Используйте ваш отпечаток пальца как альтернативу вводу PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = " Тайм-аут блокировки"; +/* PIN screen screen */ +"pin_lock_immediately" = "Сразу"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 секунд"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 минута"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 минуты"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 минут"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "Соединение..."; + +/* Tab name and screen name */ +"contacts_title" = "Контакты"; +/* Tab name and screen name */ +"chats_title" = "Чаты"; +/* Tab name and screen name */ +"settings_title" = "Настройки"; +/* Tab name and screen name */ +"profile_title" = "Профиль"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "Нет контактов"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "Добавьте друзей\nили\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "дайте им свой Tox ID"; +/* Contact request section title */ +"contact_requests_section" = "Запросы"; +/* Contact last seen status */ +"contact_last_seen" = "Был в сети: %@"; +/* Screen name */ +"contact_request" = "Запрос на добавление"; +/* Contact request button */ +"contact_request_decline" = "Отклонить"; +/* Contact request button */ +"contact_request_accept" = "Принять"; +/* Contact request confirmation */ +"contact_request_delete_title" = "Удалить запрос?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Deleted contact"; + +/* Share Tox ID text */ +"show_qr_code" = "Показать QR-код"; +/* Share Tox ID text */ +"copy" = "Копировать"; + +/* Add contat screen name */ +"add_contact_title" = "Добавить контакт"; +/* Add contat button */ +"add_contact_send" = "Отправить"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "Введите Tox ID"; +/* Add contat placeholder text */ +"add_contact_or_label" = "или"; +/* Add contat button */ +"add_contact_use_qr" = "Сканировать QR-код"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "Сообщение"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "Привет! Пожалуйста, добавьте меня в список своих контактов?"; +/* Add contat error */ +"add_contact_wrong_qr" = "QR-код должен содержать Tox ID"; + +/* User name text */ +"name" = "Имя"; +/* User nickname text */ +"nickname" = "Псевдоним"; +/* User status message text */ +"status_message" = "Статус"; +/* User status text */ +"status_title" = "Статус подключения"; +/* User Tox ID text */ +"my_tox_id" = "Мой Tox ID"; +/* User public key text */ +"public_key" = "Публичный ключ"; +/* Share Tox ID text */ +"show_qr" = "QR-код"; +/* Profile menu item / screen name */ +"profile_details" = "Детали"; +/* Profile button */ +"logout_button" = "Выйти"; + +/* QR code screen button */ +"qr_close_button" = "Закрыть"; + +/* Chat screen placeholder */ +"chat_no_chats" = "Нет чатов"; +/* Chats screen message text */ +"chat_outgoing_file" = "Исходящий файл:"; +/* Chats screen message text */ +"chat_incoming_file" = "Входящий файл:"; +/* Chats screen message text */ +"chat_call_finished" = "Звонок завершен"; +/* Chats screen message text */ +"chat_unanwered_call" = "Нет ответа"; +/* Chat button */ +"chat_send_button" = "Отпр."; +/* Chat notification toast */ +"chat_new_messages" = "↓ Новые сообщения"; +/* Chat call information */ +"chat_call_message" = "Звонок,"; +/* Chat call information */ +"chat_missed_call_message" = "Пропущенный вызов"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "Ещё"; + +/* Status message */ +"status_offline" = "Не в сети"; +/* Status message */ +"status_online" = "В сети"; +/* Status message */ +"status_away" = "Отсутствую"; +/* Status message */ +"status_busy" = "Не беспокоить"; + +/* Notification text */ +"notification_new_message" = "Новое сообщение"; +/* Notification text */ +"notification_incoming_contact_request" = "Входящий запрос"; +/* Notification text */ +"notification_is_calling" = "звонит"; +/* Notification text */ +"notification_incoming_file" = "Входящий файл"; + +/* Settings menu / screen name */ +"settings_about" = "О приложении"; +/* Settings menu / screen name */ +"settings_faq" = "FAQ"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "Расширенные настройки"; +/* About screen menu */ +"settings_antidote_version" = "Версия"; +/* About screen menu */ +"settings_antidote_build" = "Сборка"; +/* About screen menu */ +"settings_toxcore_version" = "Версия Toxcore"; +/* About screen menu */ +"settings_acknowledgements" = "Лицензии"; +/* Settings screen menu */ +"settings_autodownload_images" = "Загрузка вложений"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "Автоматически загружать входящие вложения."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "Превью уведомлений"; +/* Settings screen menu */ +"settings_notifications_description" = "Вы будете получать уведомления в течении 10 минут после сворачивания Antidote."; +/* Settings screen menu */ +"settings_udp_enabled" = "Использовать UDP"; +/* Settings screen menu */ +"settings_restore_default" = "Сбросить настройки"; +/* Settings screen autodownload images option */ +"settings_never" = "Никогда"; +/* Settings screen autodownload images option */ +"settings_wifi" = "Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "При наличии Wi-Fi"; +/* Settings screen autodownload images option */ +"settings_always" = "Всегда"; + +/* Profile settings menu */ +"change_password" = "Изменить пароль"; +/* Profile settings menu */ +"delete_password" = "Удалить пароль"; +/* Profile settings menu */ +"old_password" = "Старый пароль"; +/* Change password text */ +"new_password" = "Новый пароль"; +/* Change password text */ +"repeat_password" = "Подтвердите пароль"; +/* Change password button */ +"change_password_done" = "Готово"; +/* Change password error */ +"password_is_empty_error" = "Пароль не может быть пустым"; +/* Change password error */ +"wrong_old_password" = "Неверный пароль"; +/* Change password error */ +"passwords_do_not_match" = "Пароли не совпадают"; + +/* Source of photo to take */ +"photo_from_camera" = "Камера"; +/* Source of photo to take */ +"photo_from_photo_library" = "Медиатека"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "Ошибка конвертации изображения"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "Отправить контакту"; + +/* Profile menu item */ +"export_profile" = "Экспорт профиля"; +/* Profile menu item */ +"delete_profile" = "Удалить профиль"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "Удалить этот профиль?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "Вы уверены?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "Это действие необратимо"; + +/* Call screen text */ +"call_incoming" = "Входящий вызов"; +/* Call screen text */ +"call_ended" = "Конец вызова"; +/* Call screen text */ +"call_reaching" = "Вызов..."; + +/* File message text */ +"chat_file_cancelled" = "Отменено"; +/* File message text */ +"chat_waiting" = "Ожидание..."; +/* File message text */ +"chat_paused" = "Пауза"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "Удалить чат и историю сообщений?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "Удалить контакт?\nИстория вашего чата будет потеряна."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "Удалить запрос?"; +/* Deleting single message in chat */ +"delete_single_message" = "Удалить сообщение"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Удалить сообщения"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Удалить всё"; +/* Delete button */ +"alert_delete" = "Удалить"; +/* Delete button */ +"alert_cancel" = "Отмена"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "Имя и профиль должны быть заданы."; +/* Error while creating new profile */ +"login_profile_already_exists" = "Такой профиль уже существует"; + +/* General error title */ +"error_title" = "Ошибка"; +/* General error button */ +"error_ok_button" = "ОК"; +/* General error button */ +"error_retry_button" = "Повторить"; +/* Error */ +"error_contact_not_connected" = "Контакт не в сети"; +/* Error */ +"error_too_many_files" = "Слишком много активных загрузок"; +/* Error */ +"error_internal_message" = "Внутренняя ошибка"; +/* Error */ +"error_wrong_password_title" = "Неверный пароль"; +/* Error */ +"error_wrong_password_message" = "Пароль содержит недопустимые символы."; +/* Error */ +"error_import_not_exist_title" = "Невозможно импортировать файл"; +/* Error */ +"error_import_not_exist_message" = "Файл не существует"; +/* Error */ +"error_decrypt_title" = "Невозможно расшифровать файл"; +/* Error */ +"error_decrypt_empty_data_message" = "Недостаточно данных."; +/* Error */ +"error_decrypt_bad_format_message" = "Файл поврежден или имеет неверный формат."; +/* Error */ +"error_decrypt_wrong_password_message" = "Неверный пароль"; +/* Error */ +"error_general_unknown_message" = "Неизвестная ошибка"; +/* Error */ +"error_general_no_memory_message" = "Недостаточно памяти."; +/* Error */ +"error_general_bind_port_message" = "Порт занят."; +/* Error */ +"error_general_profile_encrypted_message" = "Профиль зашифрован."; +/* Error */ +"error_general_bad_format_message" = "Файл поврежден или имеет неверный формат."; +/* Error */ +"error_proxy_title" = "Ошибка прокси"; +/* Error */ +"error_proxy_invalid_address_message" = "Адрес имеет неверный формат."; +/* Error */ +"error_proxy_invalid_port_message" = "Неверный порт."; +/* Error */ +"error_proxy_host_not_resolved_message" = "Невозможно разрешить адрес."; + +/* Error */ +"error_name_too_long" = "Имя слишком длинное."; +/* Error */ +"error_status_message_too_long" = "Статус слишком длинный"; + +/* Error */ +"error_contact_request_too_long" = "Сообщение слишком длинная"; +/* Error */ +"error_contact_request_no_message" = "Отсутствует сообщение"; +/* Error */ +"error_contact_request_own_key" = "Нельзя добавить себя в Контакты"; +/* Error */ +"error_contact_request_already_sent" = "Запрос уже отправлен"; +/* Error */ +"error_contact_request_bad_checksum" = "Неверная контрольная сумма, перепроверьте Tox ID"; +/* Error */ +"error_contact_request_new_nospam" = "Неверный nospam, перепроверьте Tox ID"; + +/* Call error */ +"call_error_already_in_call" = "Абонент занят"; +/* Call error */ +"call_error_contact_is_offline" = "Абонент не в сети"; +/* Call error */ +"call_error_no_active_call" = "Нет активных звонков"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "Не удалось открыть, тема имеет неверный формат"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "непрочитанные чаты"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "аватар"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Установить или удалить аватар."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Чат"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Открывает чат."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Аудио звонок"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Совершает аудио звонок."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Видео звонок"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Совершает видео звонок."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Показать меню \"копировать\"."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Редактирует значение."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Сообщение"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Ваше сообщение"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "пишет..."; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "Сообщения будут отправлены, когда вы и ваш друг оба будете онлайн."; diff --git a/Antidote/ru.lproj/import-profile.html b/Antidote/ru.lproj/import-profile.html new file mode 100644 index 0000000..906124e --- /dev/null +++ b/Antidote/ru.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

Чтобы импортировать профиль:

+ +
    +
  1. Отправьте файл .tox на Ваше устройство (используя почту, Dropbox и др.).
  2. +
  3. Нажмите "Открыть в".
  4. +
  5. Выберите Antidote в списке доступных приложений.
  6. +
  7. Проверьте имя нового профиля и нажмите ОК.
  8. +
diff --git a/Antidote/sv.lproj/Localizable.strings b/Antidote/sv.lproj/Localizable.strings new file mode 100644 index 0000000..3fd89d1 --- /dev/null +++ b/Antidote/sv.lproj/Localizable.strings @@ -0,0 +1,34 @@ + + +/* Login screen text */ +"import_profile" = "Importera profil"; +/* Login screen text */ +"set_password_title" = "Välj lösenordet"; +/* Login screen text */ +"login_or_label" = "eller"; +/* Login button */ +"log_in" = "Logga in"; +/* Login screen text */ +"create_profile" = "Skapa konto"; +/* Login screen text */ +"import_to_antidote" = "Importera till Antidote"; +/* Login screen text */ +"create_account_profile_title" = "Hur kallar man denna profil?"; +/* Login screen text */ +"create_account_profile_placeholder" = "Profilnamn"; +/* Login screen text */ +"create_account_profile_hint" = "T.ex. Hem, Iphone"; +/* Login screen text */ +"create_account" = "Skapa konto"; +/* Password field */ +"password" = "Lösenordet"; +/* Login screen text */ +"create_account_username_placeholder" = "Användarnamn"; +/* Login screen text */ +"create_account_username_title" = "Hur vill du se dina kontakter?"; +/* Login screen text */ +"set_password_hint" = "Lösenordet behövs för att skydda din data. Förvara det säkert - om du har förlorat det går det inte att återskapa det."; +/* Login button */ +"create_account_next_button" = "Nästa"; +/* Login button */ +"create_account_go_button" = "Gå"; diff --git a/Antidote/tr.lproj/Localizable.strings b/Antidote/tr.lproj/Localizable.strings new file mode 100644 index 0000000..c188b9c --- /dev/null +++ b/Antidote/tr.lproj/Localizable.strings @@ -0,0 +1,14 @@ + + +/* Login button */ +"log_in" = "giriş yap"; +/* Password field */ +"password" = "Şifre"; +/* Login screen text */ +"create_profile" = "Hesap oluştur"; +/* Login screen text */ +"create_account" = "Hesap oluştur"; +/* Login screen text */ +"import_profile" = "Dosyayı dışarı çıkar"; +/* Login screen text */ +"login_or_label" = "ya da"; diff --git a/Antidote/zgh.lproj/Localizable.strings b/Antidote/zgh.lproj/Localizable.strings new file mode 100644 index 0000000..5a84c69 --- /dev/null +++ b/Antidote/zgh.lproj/Localizable.strings @@ -0,0 +1,404 @@ + + +/* Call screen text */ +"call_reaching" = "ⵉⵇⵇⴰⵔ..."; +/* Login screen text */ +"login_or_label" = "ⵏⵉⵖ"; +/* Login screen text */ +"create_account" = "ⵙⴽⵔ ⴽⵔⴰ ⵏ ⵓⵎⵉⴹⴰⵏ"; +/* Login screen text */ +"import_profile" = "ⵙⵙⴽⵛⵎ ⴽⵔⴰ ⵏ ⵓⵎⵉⴹⴰⵏ"; +/* Password field */ +"password" = "ⵉⵏⵉⴳⵍ ⵏ ⵡⴰⴷⴰⴼ"; +/* Login button */ +"log_in" = "ⴽⵛⵎ"; +/* Login screen text */ +"create_profile" = "ⵙⴽⵔ ⴽⵔⴰ ⵏ ⵓⵎⵉⴹⴰⵏ"; +/* Login screen text */ +"import_to_antidote" = "ⴷⴷⵓ ⵖⵔ antidote"; +/* Login screen text */ +"create_account_username_title" = "ⵎⴰⵎⴽ ⴰ ⵔⴰⴷ ⴽ ⵥⵕⵏ ⵉⵎⴰⵡⴰⴹⵏ ⵏⵏⴽ?"; +/* Login screen text */ +"create_account_username_placeholder" = "ⵉⵙⵎ ⵏ ⵓⵏⵙⵙⵎⵔⵙ"; +/* Login screen text */ +"create_account_profile_title" = "ⵎⴰⵎⴽ ⴰ ⵔⴰⴷ ⵜⵖⵔⴷ ⵉ ⵓⵎⵉⴹⴰⵏ ⴰⴷ?"; +/* Login screen text */ +"create_account_profile_placeholder" = "ⵉⵙⵎ ⵏ ⵓⵎⵉⴹⴰⵏ"; +/* Login screen text */ +"create_account_profile_hint" = "ⵙ ⵓⵎⴷⵢⴰ ⵜⴰⴷⴷⴰⵔⵜ, ⴰⵢⴼⵓⵏ"; +/* Login screen text */ +"set_password_title" = "ⵙⵔⵙ ⵉⵏⵉⴳⵍ ⵏ ⵡⴰⴷⴰⴼ"; +/* Login screen text */ +"set_password_hint" = "ⵉⵡⴰⵜⵜⵙ ⵉⵏⵉⴳⵍ ⵏ ⵡⴰⴷⴰⴼ ⴰⴼⵔⴰⴳ ⵏ ⵜⵎⵓⵛⴰ ⵏⵏⴽ ⵜⵉⵏⵉⵎⴰⵏⵉⵏ. ⵃⴹⵓ ⵜⵏⵜ - ⵎⵛ ⴰⴽ ⴷⴷⴰⵏⵜ ⵓⵔ ⵜⵣⵎⵎⴰⵔⴷ ⴰⴷ ⵜⵔⴰⵔⴷ ⴰⵎⵉⴹⴰⵏ."; +/* Login button */ +"create_account_next_button" = "ⵢⴰⴹⵏ"; +/* Login button */ +"create_account_go_button" = "ⵙⵙⵏⵜ"; +/* Default status message */ +"default_user_status_message" = "ⵜⵜⵎⵢⴰⵡⴰⴹⵖ ⵙ Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "ⵙⵙⵉⴷⴼ PIN ⵏⵏⴽ ⵉ ⵓⵕⵥⴰⵎ"; +/* PIN screen text */ +"pin_set" = "ⵙⵔⵙ PIN"; +/* PIN screen text */ +"pin_confirm" = "ⵙⴷⴷⵉⴷ PIN"; +/* PIN screen error */ +"pin_do_not_match" = "ⵓⵔ ⵎⵙⴰⵙⴰⵏ ⵡⵓⵟⵟⵓⵏⵏ, ⴰⵍⵙ ⴷⴰⵖ"; +/* PIN screen error */ +"pin_incorrect" = "PIN ⴷ ⴰⵔⵎⵉⴷⵉ"; +/* PIN screen error details */ +"pin_failed_attempts" = "ⵉⵔⵉⵎⵏ ⵉⵏⴳⵓⴼⵏ: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "ⴽⵉⴳⴰⵏ ⵏ ⵉⵔⵉⵎⵏ ⵉⵏⴳⵓⴼⵏ. ⵉⵜⵜⵡⴰⵣⵎⵎⴻⵎ ⵡⵓⴼⵓⵖ ⵏⵏⴽ."; +/* PIN screen screen */ +"pin_enabled" = "ⴰⵙⵎⵀⵍ ⵏ PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "ⴰⵙⵎⵀⵍ ⵏ ⵜⵎⴰⴳⵉⵜ ⵙ ⵓⵙⵍⴰⵢ"; +/* PIN screen screen */ +"pin_description" = "ⵏⵀⵕ ⴰⵡⴰⴹ ⵏⵏⴰ ⵓⵔ ⵉⵜⵜⵓⵙⵙⵓⴳⵔⵏ ⵖⵔ Antidote ⵙ ⵓⵙⵎⵔⵙ ⵏ PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "ⵙⵎⵔⵙ ⵜⵉⵡⴳⴳⴹⵜ ⴰⵎ ⵜⵉⵎⴽⴽⵉⵙⵉⵜ ⵏ ⵓⵙⵉⴷⴼ ⵏ PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "ⴰⵣⵎⵣ ⵏ ⵓⵔⴳⴰⵍ"; +/* PIN screen screen */ +"pin_lock_immediately" = "ⴷⵖⵉ"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 ⵏ ⵜⵙⵉⵏⵜ"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 ⵏ ⵜⵓⵙⴷⵉⴷⵜ"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 ⵏ ⵜⵓⵙⴷⵉⴷⵉⵏ"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 ⵏ ⵜⵓⵙⴷⵉⴷⵉⵏ"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "ⴳ ⵉⴼⵉⵍⵉ..."; + +/* Tab name and screen name */ +"contacts_title" = "ⵉⵎⴰⵡⴰⴹⵏ"; +/* Tab name and screen name */ +"chats_title" = "ⵉⵎⵙⴰⵡⴰⵍⵏ"; +/* Tab name and screen name */ +"settings_title" = "ⵜⵉⵙⵖⴰⵍ"; +/* Tab name and screen name */ +"profile_title" = "ⴰⵎⵉⴹⴰⵏ"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "ⵓⵔ ⵍⵍⵉⵏ ⵉⵎⴰⵡⴰⴹⵏ"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "ⵔⵏⵓ ⴽⵔⴰ ⵏ ⵓⵎⴰⵡⴰⴹ\nⵏⵉⵖ\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "ⴷⵔⵓ ⴰⵎⵉⴹⴰⵏ ⵏⵏⴽ ⵏ ⵜⵓⴽⵙ"; +/* Contact request section title */ +"contact_requests_section" = "ⵜⴰⵍⴳⴰⵎⵜ ⵏ ⵜⵎⵔⵏⵉⵡⵜ"; +/* Contact last seen status */ +"contact_last_seen" = "ⵜⵉⴳⵉⵔⴰ ⵏⵏⵙ: %@"; +/* Screen name */ +"contact_request" = "ⴰⵙⵉⴳⵔ"; +/* Contact request button */ +"contact_request_decline" = "ⵜⴰⴳⵯⵉⵜ"; +/* Contact request button */ +"contact_request_accept" = "ⵜⴰⵢⵢⵉⵀⵜ"; +/* Contact request confirmation */ +"contact_request_delete_title" = "ⴽⴽⵙ ⴰⵙⵉⴳⵔ?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "ⵉⵜⵜⵡⴰⴽⴽⵙ ⵓⵎⴰⵡⴰⴹ ⴰⴷ"; + +/* Share Tox ID text */ +"show_qr_code" = "ⴼⵙⵔ ⵉⵏⵉⴳⵍ ⵏ QR"; +/* Share Tox ID text */ +"copy" = "ⵙⵙⵏⵖⵍ"; + +/* Add contat screen name */ +"add_contact_title" = "ⵔⵏⵓ ⴽⵔⴰ ⵏ ⵓⵎⴰⵡⴰⴹ"; +/* Add contat button */ +"add_contact_send" = "ⴰⵣⵏ"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "ⵙⵙⵉⴷⴼ Tox ID"; +/* Add contat placeholder text */ +"add_contact_or_label" = "ⵏⵉⵖ"; +/* Add contat button */ +"add_contact_use_qr" = "ⵙⵎⵔⵙ ⵉⵏⵉⴳⵍ ⵏ QR"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "ⵜⴰⴱⵔⴰⵜ"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "ⴰⵣⵓⵍ! ⵉⵙ ⵜⵣⵎⵔⴷ ⴰⴷ ⵢⵉ ⵜⵔⵏⵓⴷ ⴳ ⵜⵍⴳⴰⵎⵜ ⵏ ⵡⴰⵡⴰⴹ ⵏⵏⴽ?"; +/* Add contat error */ +"add_contact_wrong_qr" = "ⵉⵏⵉⴳ ⵏ QR ⴷ ⴰⵔⵎⵉⴷⵉ. ⵉⵅⵚⵚⴰ ⴰⴷ ⴷⵉⴳⵙ ⵢⵉⵍⵉ Tox ID"; + +/* User name text */ +"name" = "ⵉⵙⵎ"; +/* User nickname text */ +"nickname" = "ⵉⵙⵎ ⵏ ⵜⵡⴰⵛⵓⵏⵜ"; +/* User status message text */ +"status_message" = "ⵜⴰⴱⵔⴰⵜ ⵏ ⵡⴰⴷⴷⴰⴷ"; +/* User status text */ +"status_title" = "ⴰⴷⴷⴰⴷ"; +/* User Tox ID text */ +"my_tox_id" = "Tox ID ⵉⵏⵓ"; +/* User public key text */ +"public_key" = "ⵜⴰⵙⴰⵔⵓⵜ ⵜⴰⴳⴷⵓⴷⴰⵏⵜ"; +/* Share Tox ID text */ +"show_qr" = "ⵎⵍ ⵉⵏⵉⴳⵍ ⵏ QR"; +/* Profile menu item / screen name */ +"profile_details" = "ⵉⴼⵔⵓⵔⵉⵜⵏ ⵏ ⵓⵎⵉⴹⴰⵏ"; +/* Profile button */ +"logout_button" = "ⴼⴼⵖ"; + +/* QR code screen button */ +"qr_close_button" = "ⵇⵇⵏ"; + +/* Chat screen placeholder */ +"chat_no_chats" = "ⵓⵔ ⵉⵍⵍⵉ ⴽⵔⴰ ⵏ ⵓⵎⵙⴰⵡⴰⵍ"; +/* Chats screen message text */ +"chat_outgoing_file" = "ⴰⴼⴰⵢⵍⵓ ⵉⴼⴼⵖⵏ:"; +/* Chats screen message text */ +"chat_incoming_file" = "ⴰⴼⴰⵢⵍⵓ ⵓⴽⵛⵉⵎ:"; +/* Chats screen message text */ +"chat_call_finished" = "ⵜⵙⵎⴷ ⵜⵖⵓⵔⵉ"; +/* Chats screen message text */ +"chat_unanwered_call" = "ⵓⵔ ⵉⵍⵍⵉ ⵓⵙⴰⴷⵎⵔ ⵅⴼ ⵜⵖⵓⵔⵉ ⴰⴷ"; +/* Chat button */ +"chat_send_button" = "ⴰⵣⵏ"; +/* Chat notification toast */ +"chat_new_messages" = "ⵜⵉⴱⵔⴰⵜⵉⵏ ⵜⵉⵎⴰⵢⵏⵓⵜⵉⵏ"; +/* Chat call information */ +"chat_call_message" = "ⵖⵔ، "; +/* Chat call information */ +"chat_missed_call_message" = "ⵓⵔ ⵉⵍⵍⵉ ⵓⵙⴰⴷⵎⵔ ⵅⴼ ⵜⵖⵓⵔⵉ ⴰⴷ"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "ⵓⴳⴰⵔ"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "ⵉⵜⵜⴰⵔⵉ..."; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "ⵔⴰⴷ ⵜⵜⵡⴰⵣⵏⵏⵜ ⵜⴱⵔⴰⵜⵉⵏ ⵏⵏⴰ ⵓⵔ ⵉⵜⵜⵡⴰⵎⵥⵏ ⴰⴷⴷⴰⵢ ⵜⵉⵍⵉⴷ ⴰⴽⴷ ⵓⵎⴷⴷⴰⴽⴽⵯⵍ ⵏⵏⴽ ⴳ ⵉⴼⵉⵍⵉ."; + +/* Status message */ +"status_offline" = "ⵓⵔ ⵉⵍⵍⵉ ⴳ ⵉⴼⵉⵍⵉ"; +/* Status message */ +"status_online" = "ⴳ ⵉⴼⵉⵍⵉ"; +/* Status message */ +"status_away" = "ⴳ ⴱⵕⵕⴰ"; +/* Status message */ +"status_busy" = "ⴳ ⵜⵡⵓⵔⵉ"; + +/* Notification text */ +"notification_new_message" = "ⵜⴰⴱⵔⴰⵜ ⵜⴰⵎⴰⵢⵏⵓⵜ"; +/* Notification text */ +"notification_incoming_contact_request" = "ⴰⵙⵓⵜⵔ ⵏ ⵜⵖⵓⵔⵉ"; +/* Notification text */ +"notification_is_calling" = "ⵉⵇⵇⴰⵔ"; +/* Notification text */ +"notification_incoming_file" = "ⵢⵓⴷⴼ ⴷ ⵓⴼⴰⵢⵍⵓ"; + +/* Settings menu / screen name */ +"settings_about" = "ⵅⴼ"; +/* Settings menu / screen name */ +"settings_faq" = "ⵜⵉⵖⵍⴰⴼ"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "ⵜⵉⵙⵖⴰⵍ ⵉⵣⵡⴰⵔⵏ"; +/* About screen menu */ +"settings_antidote_version" = "ⵜⵓⵏⵖⵉⵍⵜ ⵏ Antidote"; +/* About screen menu */ +"settings_antidote_build" = "ⵜⵓⵚⴰ ⵏ Antidote"; +/* About screen menu */ +"settings_toxcore_version" = "ⵜⵓⵏⵖⵉⵍⵜ ⵏ Toxcore"; +/* About screen menu */ +"settings_acknowledgements" = "ⴰⵙⵏⵉⵎⵎⵔ"; +/* Settings screen menu */ +"settings_autodownload_images" = "ⵉⴼⴰⵢⵍⵓⵜⵏ ⵏ ⵡⴰⴳⴰⵎ ⴰⵡⵓⵔⵎⴰⵏ"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "ⴰⴳⴰⵎ ⴰⵡⵓⵔⵎⴰⵏ ⵏ ⵉⴼⴰⵢⵍⵓⵜⵏ ⵓⴽⵛⵉⵎⵏ."; +/* Settings screen menu */ +"settings_notifications_message_preview" = "ⴰⴼⵙⴰⵔ ⵏ ⵉⵏⵖⵎⵉⵙⵏ"; +/* Settings screen menu */ +"settings_notifications_description" = "ⴰⴷⴷⴰⵢ ⵜⴽⵛⵎ ⵜⵙⵏⵙⵉ ⴳ ⵓⵡⵔⵏ, ⵔⴰⴷ ⵢⵉⵍⵉ ⵓⴼⵙⴰⵔ ⵏ ⵉⵏⵖⵎⵉⵙⵏ ⴰⵔ 10 ⵏ ⵜⵓⵙⴷⵉⴷⵉⵏ."; +/* Settings screen menu */ +"settings_udp_enabled" = "ⴰⵙⵎⵀⵍ ⵏ UDP"; +/* Settings screen menu */ +"settings_restore_default" = "ⵉⵔⵉⵔ ⵏ ⵜⵙⵖⴰⵍ ⵜⵉⵎⵔⴷⴰ"; +/* Settings screen autodownload images option */ +"settings_never" = "ⵓⵀⵓ"; +/* Settings screen autodownload images option */ +"settings_wifi" = "ⴰⵙⵎⵔⵙ ⵏ ⵡⵉⴼⵉ"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "ⵡⵉⴼⵉ"; +/* Settings screen autodownload images option */ +"settings_always" = "ⴰⴱⴷⴰ"; + +/* Profile settings menu */ +"change_password" = "ⵙⵏⴼⵍ ⵉⵏⵉⴳⵍ ⵏ ⵡⴰⴷⴰⴼ"; +/* Profile settings menu */ +"delete_password" = "ⴽⴽⵙ ⵉⵏⵉⴳⵍ ⵏ ⵡⴰⴷⴰⴼ"; +/* Profile settings menu */ +"old_password" = "ⵉⵏⵉⴳⵍ ⴰⵇⴱⵓⵔ ⵏ ⵡⴰⴷⴰⴼ"; +/* Change password text */ +"new_password" = "ⵉⵏⵉⴳⵍ ⴰⵎⴰⵢⵏⵓ ⵏ ⵡⴰⴷⴰⴼ"; +/* Change password text */ +"repeat_password" = "ⴰⵍⵙ ⵉⵏⵉⴳⵍ ⵏ ⵡⴰⴷⴰⴼ"; +/* Change password button */ +"change_password_done" = "ⵙⵎⴷ"; +/* Change password error */ +"password_is_empty_error" = "ⵉⵅⵚⵚⴰ ⵓⵔ ⵉⵜⵜⵉⵍⵉ ⵉⵏⵉⴳⵍ ⵏ ⵡⴰⴷⴰⴼ ⵉⵅⵡⴰ"; +/* Change password error */ +"wrong_old_password" = "ⴰⵔⵎⵉⴷⵉ ⴳ ⵉⵏⵉⴳⵍ ⵏ ⵡⴰⴷⴰⴼ"; +/* Change password error */ +"passwords_do_not_match" = "ⵓⵔ ⵉⵎⵙⴰⵙⴰ ⵉⵏⵉⴳⵍ ⵏ ⵡⴰⴷⴰⴼ"; + +/* Source of photo to take */ +"photo_from_camera" = "ⴰⵙⵓⵍⴼ"; +/* Source of photo to take */ +"photo_from_photo_library" = "ⵜⴰⵙⵓⵍⴰⴼⵜ"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "ⵓⵔ ⵜⵣⵎⵉⵔ ⵜⵡⵍⴰⴼⵜ ⴰⴷ ⵜⴻⵜⵜⵓⵙⵏⴼⵍ"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "ⴰⵣⵏ ⵉ ⴽⵔⴰ ⵏ ⵓⵎⴰⵡⴰⴹ"; + +/* Profile menu item */ +"export_profile" = "ⵃⴹⵓ ⴰⵎⵉⴹⴰⵏ ⴰⵏⵉⵎⴰⵏ"; +/* Profile menu item */ +"delete_profile" = "ⴽⴽⵙ ⴰⵎⵉⴹⴰⵏ ⴰⵏⵉⵎⴰⵏ"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "ⵉⵙ ⵜⵔⵉⴷ ⴰⴷ ⵜⴽⴽⵙⴷ ⴰⵎⵉⴹⴰⵏ ⴰⵏⵉⵎⴰⵏ?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "ⵉⵙ ⵜⵃⵇⵇⵉⴷ?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "ⵜⴰⵎⵀⵍⵜ ⴰⴷ ⵓⵔ ⴷⵉⴳⵙ ⴰⴱⵕⵕⴰⵎ"; + +/* Call screen text */ +"call_incoming" = "ⵜⴰⵖⵓⵔⵉ ⵜⵓⴽⵛⵉⵎⵜ"; +/* Call screen text */ +"call_ended" = "ⵜⵙⵎⴷ ⵜⵖⵓⵔⵉ"; + +/* File message text */ +"chat_file_cancelled" = "ⵉⵜⵜⵓⵙⵙⵔ"; +/* File message text */ +"chat_waiting" = "ⴰⴳⴰⵏⵉ..."; +/* File message text */ +"chat_paused" = "ⴱⴷⴷ ⵉⵎⵉⴽ"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "ⴽⴽⵙ ⴰⵎⵙⴰⵡⴰⵍ ⴰⴷ ⴷ ⵜⴱⵔⴰⵜⵉⵏ ⵏⵏⴰ ⴷⵉⴳⵙ ⵉⵜⵜⵓⵃⴹⵓⵏ?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "ⴽⴽⵙ ⴰⵎⴰⵡⴰⴹ ⴰⴷ?\nⵔⴰⴷ ⵉⵜⵜⵓⴽⴽⵙ ⵡⴰⵔⵔⴰ ⵏ ⵜⵊⵎⵎⴰⵄⵜ."; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "ⵉⵙ ⵜⵔⵉⴷ ⴰⴷ ⵜⴽⴽⵙⴷ ⴰⵙⵉⴳⵔ ⵏ ⵓⵎⵉⴹⴰⵏ ⴰⴷ?"; +/* Deleting single message in chat */ +"delete_single_message" = "ⴽⴽⵙ ⵜⴰⴱⵔⴰⵜ"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "ⴽⴽⵙ ⵜⵉⴱⵔⴰⵜⵉⵏ"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "ⴽⴽⵙ ⴰⴽⴽⵯ"; +/* Delete button */ +"alert_delete" = "ⴽⴽⵙ"; +/* Delete button */ +"alert_cancel" = "ⵙⵙⵔ"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "ⵙⵙⵉⴷⴼ ⵉⵙⵎ ⵏ ⵓⵏⵙⵙⵎⵔⵙ ⴷ ⵢⵉⵙⵎ ⵏ ⵓⵎⵉⴹⴰⵏ ⴰⵏⵉⵎⴰⵏ."; +/* Error while creating new profile */ +"login_profile_already_exists" = "ⵉⵍⵍⴰ ⵢⵉⵙⵎ ⴰⴷ ⵏ ⵓⵎⵉⴹⴰⵏ ⴰⵏⵉⵎⴰⵏ"; + +/* General error title */ +"error_title" = "ⵜⴰⵣⴳⵍⵜ"; +/* General error button */ +"error_ok_button" = "ⵡⴰⵅⵅⴰ"; +/* General error button */ +"error_retry_button" = "ⴰⵍⵙ ⴷⴰⵖ"; +/* Error */ +"error_contact_not_connected" = "ⵓⵔ ⵉⵍⵍⵉ ⵓⵎⴰⵡⴰⴹ ⴳ ⵉⴼⵉⵍⵉ"; +/* Error */ +"error_too_many_files" = "ⴽⵉⴳⴰⵏ ⵏ ⵜⵎⵀⴰⵍ ⵏ ⵓⵙⵎⵓⵜⵜⵉ ⵏ ⵉⴼⴰⵢⵍⵓⵜⵏ ⵉⵜⵜⵓⵙⵎⵀⵍⵏ"; +/* Error */ +"error_internal_message" = "ⵜⴰⵣⴳⵍⵜ ⵜⴰⴳⵯⵏⵙⴰⵏⵜ"; +/* Error */ +"error_wrong_password_title" = "ⵉⵏⵉⴳⵍ ⵏ ⵡⴰⴷⴰⴼ ⴷ ⴰⵔⵎⵉⴷⵉ"; +/* Error */ +"error_wrong_password_message" = "ⵉⵏⵉⴳⵍ ⵏ ⵡⴰⴷⴰⴼ ⴷⵉⴳⵙ ⵜⵉⵎⵉⵜⴰⵔ ⵜⵉⵔⵎⵉⴷⵉⵜⵉⵏ."; +/* Error */ +"error_import_not_exist_title" = "ⵓⵔ ⵉⵣⵔⵉ ⴰⴷ ⵉⵜⵜⵓⵙⵏⴼⵍ ⵓⴼⴰⵢⵍⵓ ⵏ tox"; +/* Error */ +"error_import_not_exist_message" = "ⵓⵔ ⵉⵍⵍⵉ ⵓⴼⴰⵢⵍⵓ."; +/* Error */ +"error_decrypt_title" = "ⵓⵔ ⵉⵣⵔⵉ ⵓⵔⴰⵏⵜⴰⵍ ⵏ ⵓⴼⴰⵢⵍⵓ ⵏ tox ⵉⵜⵜⵓⵃⴹⵓⵏ"; +/* Error */ +"error_decrypt_empty_data_message" = "ⴽⵔⴰ ⵏ ⵜⵎⵓⵛⴰ ⵏ ⵓⴽⵊⵊⵓⵎ ⵅⵡⴰⵏⵜ."; +/* Error */ +"error_decrypt_bad_format_message" = "ⴰⴼⴰⵢⵍⵓ ⴷⵉⴳⵙ ⴰⵣⴷⴷⵓⵢ ⴷ ⴰⵎⵛⵓⵎ ⵏⵉⵖ ⴰⵎⴰⴽⵓⵍ."; +/* Error */ +"error_decrypt_wrong_password_message" = "ⵉⵏⵉⴳⵍ ⵏ ⵡⴰⴷⴰⴼ ⴷ ⴰⵔⵎⵉⴷⵉ ⵏⵉⵖ ⴰⴼⴰⵢⵍⵓ ⴷ ⴰⵎⴰⴽⵓⵍ."; +/* Error */ +"error_general_unknown_message" = "ⵜⴰⵣⴳⵍⵜ ⵜⴰⵔ ⵉⵙⵎ."; +/* Error */ +"error_general_no_memory_message" = "ⵜⵉⵎⴽⵜⵉⵜ ⴷ ⵜⴰⵎⴽⵜⵓⵔⵜ."; +/* Error */ +"error_general_bind_port_message" = "ⵓⵔ ⵉⵣⵎⵉⵔ ⴰⴷ ⵉⵣⴷⵉ ⴷ ⵓⴼⵜⴰⵙ."; +/* Error */ +"error_general_profile_encrypted_message" = "ⵉⵜⵜⵡⴰⴳⴳ ⵓⵙⵏⵜⵍ ⵉ ⵓⵎⵉⴹⴰⵏ ⴰⵏⵉⵎⴰⵏ."; +/* Error */ +"error_general_bad_format_message" = "ⴰⴼⴰⵢⵍⵓ ⴷⵉⴳⵙ ⴰⵣⴷⴷⵓⵢ ⴷ ⴰⵎⵛⵓⵎ ⵏⵉⵖ ⴰⵎⴰⴽⵓⵍ."; +/* Error */ +"error_proxy_title" = "ⵜⴰⵣⴳⵍⵜ ⴳ Proxy"; +/* Error */ +"error_proxy_invalid_address_message" = "ⴰⵏⵙⴰ ⵏ Proxy ⵖⵔⵙ ⵜⴰⵍⵖⴰ ⵜⴰⵔⵎⵉⴷⵉⵜ."; +/* Error */ +"error_proxy_invalid_port_message" = "ⴰⴼⵜⴰⵙ ⵏ Proxy ⴷ ⴰⵔⵎⵉⴷⵉ."; +/* Error */ +"error_proxy_host_not_resolved_message" = "ⵓⵔ ⵉⵣⵔⵉ ⵓⵕⵥⵥⵓⵎ ⵏ Proxy."; + +/* Error */ +"error_name_too_long" = "ⵉⵖⵣⵣⵉⴼ ⵢⵉⵙⵎ ⴽⵉⴳⴰⵏ."; +/* Error */ +"error_status_message_too_long" = "ⵜⵖⵣⵣⵉⴼ ⴽⵉⴳⴰⵏ ⵜⴱⵔⴰⵜ ⵏ ⵡⴰⴷⴷⴰⴷ"; + +/* Error */ +"error_contact_request_too_long" = "ⵜⵖⵣⵣⵉⴼ ⵜⴱⵔⴰⵜ ⴽⵉⴳⴰⵏ"; +/* Error */ +"error_contact_request_no_message" = "ⵓⵔ ⵍⵍⵉⵏⵜ ⴽⵔⴰ ⵏ ⵜⴱⵔⴰⵜⵉⵏ"; +/* Error */ +"error_contact_request_own_key" = "ⵓⵔ ⵜⵣⵔⵉ ⵜⵎⵔⵏⵉⵡⵜ ⵏ ⵢⵉⵅⴼ ⵉⵏⵓ ⴳ ⵜⵍⴳⴰⵎⵜ ⵏ ⵉⵎⴰⵡⴰⴹⵏ"; +/* Error */ +"error_contact_request_already_sent" = "ⵉⵜⵜⵡⴰⵣⵏ ⵏⵏⵉⵜ ⵓⵙⵉⴳⵔ"; +/* Error */ +"error_contact_request_bad_checksum" = "ⵜⴰⵎⵓⵜⵜⵔⵜ ⵏ ⵉⵔⵉⵎⵏ ⴷ ⵜⴰⵎⵓⵛⵎⵜ, ⵙⵙⵉⴷⴷⴻⴷ Tox ID ⵢⵓⴷⴼⵏ"; +/* Error */ +"error_contact_request_new_nospam" = "ⴰⵜⵉⴳ ⵏ nospam ⴷ ⴰⵎⵛⵓⵎ, ⵙⵙⵉⴷⴷⴻⴷ Tox ID"; + +/* Call error */ +"call_error_already_in_call" = "ⵉⵙⵙⴰⵡⴰⵍ"; +/* Call error */ +"call_error_contact_is_offline" = "ⵉⵇⵇⵏ ⵓⵎⵉⴹⴰⵏ"; +/* Call error */ +"call_error_no_active_call" = "ⵓⵔ ⵜⵍⵍⵉ ⴽⵔⴰ ⵏ ⵜⵖⵓⵔⵉ ⵢⴰⴹⵏⵉⵏ"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "ⵓⵔ ⵉⵣⵔⵉ ⵓⴽⵛⴰⵎ ⵙ ⵜⴼⵔⵙⵜ, ⵜⴰⵍⵖⴰ ⴷ ⵜⴰⵔⵎⵉⴷⵉⵜ"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "ⵉⵎⵙⴰⵡⴰⵍⵏ ⵓⵔ ⵉⵜⵜⵡⴰⵖⵔⵉⵏ"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "ⵜⴰⵡⵍⴰⴼⵜ"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "ⵙⴽⵔ ⵏⵉⵖ ⴽⴽⵙ ⵜⴰⵡⵍⴰⴼⵜ."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "ⴰⵎⵙⴰⵡⴰⵍ"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "ⴷⴰ ⵜⵕⵥⵥⵎ ⵜⵏⴽⵍⵓⵜ ⵏ ⵓⵎⵙⴰⵡⴰⵍ."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "ⴰⵎⵢⴰⵡⴰⴹ ⴰⴳⵕⴹⴰⵏ"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "ⵙⴽⵔ ⴰⵎⵢⴰⵡⴰⴹ ⴰⴳⵕⴹⴰⵏ."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "ⴰⵎⵢⴰⵡⴰⴹ ⴰⵎⵡⴰⵍⴰⵏ"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "ⵙⴽⵔ ⴰⵎⵢⴰⵡⴰⴹ ⴰⵎⵡⴰⵍⴰⵏ."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "ⵜⴰⵙⵎⵎⴰⵍⵜⵏ ⵜⵍⴳⴰⵎⵜ ⵏ ⵜⵓⵏⵖⵉⵍⵜ."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "ⵜⴰⴱⵔⴰⵜ"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "ⵜⴰⴱⵔⴰⵜ ⵏⵏⴽ"; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "ⵉⵙⴳⴳⴷⵏ ⵏ ⵡⴰⵜⵉⴳ."; diff --git a/Antidote/zh.lproj/AppStoreLocalizable.strings b/Antidote/zh.lproj/AppStoreLocalizable.strings new file mode 100644 index 0000000..3f83883 Binary files /dev/null and b/Antidote/zh.lproj/AppStoreLocalizable.strings differ diff --git a/Antidote/zh.lproj/InfoPlist.strings b/Antidote/zh.lproj/InfoPlist.strings new file mode 100644 index 0000000..6c333a8 Binary files /dev/null and b/Antidote/zh.lproj/InfoPlist.strings differ diff --git a/Antidote/zh.lproj/Localizable.strings b/Antidote/zh.lproj/Localizable.strings new file mode 100644 index 0000000..4d6fc33 --- /dev/null +++ b/Antidote/zh.lproj/Localizable.strings @@ -0,0 +1,415 @@ +/* + Localizable.strings + Antidote + + Created by Dmytro Vorobiov on 08/10/15. + Copyright © 2015 dvor. All rights reserved. +*/ + +/* + Translation is done with Transifex service. + See https://www.transifex.com/antidote/antidote-ios/ +*/ + +/* Login screen text */ +"login_or_label" = "或"; +/* Login screen text */ +"create_account" = "创建账户"; +/* Login screen text */ +"import_profile" = "导入用户档案"; +/* Password field */ +"password" = "密码"; +/* Login button */ +"log_in" = "登录"; +/* Login screen text */ +"create_profile" = "建立用户档案"; +/* Login screen text */ +"import_to_antidote" = "导入到 Antidote"; +/* Login screen text */ +"create_account_username_title" = "你的联系人将怎样看到你?"; +/* Login screen text */ +"create_account_username_placeholder" = "用户名"; +/* Login screen text */ +"create_account_profile_title" = "如何为用户档案命名?"; +/* Login screen text */ +"create_account_profile_placeholder" = "档案名"; +/* Login screen text */ +"create_account_profile_hint" = "例如:家庭,iPhone"; +/* Login screen text */ +"set_password_title" = "Set Password"; +/* Login screen text */ +"set_password_hint" = "Password is required to protect your data. Keep it safe - once lost it cannot be restored."; +/* Login button */ +"create_account_next_button" = "Next"; +/* Login button */ +"create_account_go_button" = "前进"; +/* Default status message */ +"default_user_status_message" = "正在使用 Antidote"; + +/* PIN screen text */ +"pin_enter_to_unlock" = "Enter your PIN to unlock"; +/* PIN screen text */ +"pin_set" = "Set PIN"; +/* PIN screen text */ +"pin_confirm" = "Confirm PIN"; +/* PIN screen error */ +"pin_do_not_match" = "PINs did not match. Try again"; +/* PIN screen error */ +"pin_incorrect" = "Incorrect PIN"; +/* PIN screen error details */ +"pin_failed_attempts" = "Failed attempts: %@"; +/* PIN screen message shown on log out */ +"pin_logout_message" = "Too many failed attempts. You have been logged out."; +/* PIN screen screen */ +"pin_enabled" = "Enable PIN"; +/* PIN screen screen */ +"pin_touch_id_enabled" = "Enable Touch ID"; +/* PIN screen screen */ +"pin_description" = "Prevent unauthorized access to Antidote with a PIN."; +/* PIN screen screen */ +"pin_touch_id_description" = "Use your fingerprint as an alternative to entering a PIN."; +/* PIN screen screen */ +"pin_lock_timeout" = "Lock Timeout"; +/* PIN screen screen */ +"pin_lock_immediately" = "Immediately"; +/* PIN screen screen */ +"pin_lock_30_seconds" = "30 Seconds"; +/* PIN screen screen */ +"pin_lock_1_minute" = "1 Minute"; +/* PIN screen screen */ +"pin_lock_2_minutes" = "2 Minutes"; +/* PIN screen screen */ +"pin_lock_5_minutes" = "5 Minutes"; + +/* Label shown when connecting to Tox network */ +"connecting_label" = "连接中 ..."; + +/* Tab name and screen name */ +"contacts_title" = "联系人"; +/* Tab name and screen name */ +"chats_title" = "聊天"; +/* Tab name and screen name */ +"settings_title" = "设置"; +/* Tab name and screen name */ +"profile_title" = "档案"; + +/* No contacts placeholder text */ +"contact_no_contacts" = "没有联系人"; +/* No contacts placeholder text */ +"contact_no_contacts_add_contact" = "添加联系人\n或\n"; +/* No contacts placeholder text */ +"contact_no_contacts_share_tox_id" = "分享你的 Tox ID"; +/* Contact request section title */ +"contact_requests_section" = "联系人请求"; +/* Contact last seen status */ +"contact_last_seen" = "最后上线:%@"; +/* Screen name */ +"contact_request" = "联系人请求"; +/* Contact request button */ +"contact_request_decline" = "拒绝"; +/* Contact request button */ +"contact_request_accept" = "接受"; +/* Contact request confirmation */ +"contact_request_delete_title" = "删除联系人请求?"; +/* Text shown when contact was deleted */ +"contact_deleted" = "Deleted contact"; + +/* Share Tox ID text */ +"show_qr_code" = "显示二维码"; +/* Share Tox ID text */ +"copy" = "复制"; + +/* Add contat screen name */ +"add_contact_title" = "添加联系人"; +/* Add contat button */ +"add_contact_send" = "发送"; +/* Add contat placeholder text */ +"add_contact_tox_id_placeholder" = "输入 Tox ID"; +/* Add contat placeholder text */ +"add_contact_or_label" = "或"; +/* Add contat button */ +"add_contact_use_qr" = "使用二维码"; +/* Add contat placeholder text */ +"add_contact_default_message_title" = "信息"; +/* Add contat default text sended to contact */ +"add_contact_default_message_text" = "你好,请问可以成为你的联系人吗?"; +/* Add contat error */ +"add_contact_wrong_qr" = "无法从此二维码中找到 Tox ID"; + +/* User name text */ +"name" = "姓名"; +/* User nickname text */ +"nickname" = "昵称"; +/* User status message text */ +"status_message" = "状态消息"; +/* User status text */ +"status_title" = "状态"; +/* User Tox ID text */ +"my_tox_id" = "我的 Tox ID"; +/* User public key text */ +"public_key" = "公钥"; +/* Share Tox ID text */ +"show_qr" = "显示二维码"; +/* Profile menu item / screen name */ +"profile_details" = "档案详情"; +/* Profile button */ +"logout_button" = "安全退出"; + +/* QR code screen button */ +"qr_close_button" = "关闭"; + +/* Chat screen placeholder */ +"chat_no_chats" = "没有聊天"; +/* Chats screen message text */ +"chat_outgoing_file" = "发送的文件:"; +/* Chats screen message text */ +"chat_incoming_file" = "接收的文件:"; +/* Chats screen message text */ +"chat_call_finished" = "完成的语音通话"; +/* Chats screen message text */ +"chat_unanwered_call" = "未接的语音通话"; +/* Chat button */ +"chat_send_button" = "发送"; +/* Chat notification toast */ +"chat_new_messages" = "↓ 新的消息"; +/* Chat call information */ +"chat_call_message" = "呼叫, "; +/* Chat call information */ +"chat_missed_call_message" = "错过的语音通话"; +/* Item present in the menu on long press on message */ +"chat_more_menu_item" = "更多"; + +/* Status message */ +"status_offline" = "离线"; +/* Status message */ +"status_online" = "在线"; +/* Status message */ +"status_away" = "离开"; +/* Status message */ +"status_busy" = "忙碌"; + +/* Notification text */ +"notification_new_message" = "新消息"; +/* Notification text */ +"notification_incoming_contact_request" = "收到的联系人请求"; +/* Notification text */ +"notification_is_calling" = "正在呼叫"; +/* Notification text */ +"notification_incoming_file" = "接收的文件"; + +/* Settings menu / screen name */ +"settings_about" = "关于"; +/* Settings menu / screen name */ +"settings_faq" = "FAQ"; +/* Settings menu / screen name */ +"settings_advanced_settings" = "高级设置"; +/* About screen menu */ +"settings_antidote_version" = "Antidote 版本"; +/* About screen menu */ +"settings_antidote_build" = "Antidote 构建"; +/* About screen menu */ +"settings_toxcore_version" = "Toxcore 版本"; +/* About screen menu */ +"settings_acknowledgements" = "Acknowledgements"; +/* Settings screen menu */ +"settings_autodownload_images" = "自动下载文件"; +/* Settings screen menu */ +"settings_autodownload_images_description" = "自动下载收到的文件。"; +/* Settings screen menu */ +"settings_notifications_message_preview" = "通知预览"; +/* Settings screen menu */ +"settings_notifications_description" = "当应用程序进入后台之后,您仍会接收到 10 分钟以内的通知。"; +/* Settings screen menu */ +"settings_udp_enabled" = "Enable UDP"; +/* Settings screen menu */ +"settings_restore_default" = "恢复默认设置"; +/* Settings screen autodownload images option */ +"settings_never" = "从不"; +/* Settings screen autodownload images option */ +"settings_wifi" = "无线网络"; +/* Settings screen autodownload images option */ +"settings_using_wifi" = "使用无线网络"; +/* Settings screen autodownload images option */ +"settings_always" = "永远"; + +/* Profile settings menu */ +"change_password" = "修改密码"; +/* Profile settings menu */ +"delete_password" = "删除密码"; +/* Profile settings menu */ +"old_password" = "原密码"; +/* Change password text */ +"new_password" = "新密码"; +/* Change password text */ +"repeat_password" = "重复新密码"; +/* Change password button */ +"change_password_done" = "完成"; +/* Change password error */ +"password_is_empty_error" = "密码不能为空"; +/* Change password error */ +"wrong_old_password" = "密码错误"; +/* Change password error */ +"passwords_do_not_match" = "两次输入的密码不匹配"; + +/* Source of photo to take */ +"photo_from_camera" = "摄像头"; +/* Source of photo to take */ +"photo_from_photo_library" = "图片库"; +/* Error while converting avatar image */ +"change_avatar_error_convert_image" = "无法转换图像"; + +/* Alert text when sending file to contacts */ +"file_send_to_contact" = "发送给联系人"; + +/* Profile menu item */ +"export_profile" = "导出用户档案"; +/* Profile menu item */ +"delete_profile" = "删除用户档案"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_1" = "删除这个档案?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_title_2" = "你确定?"; +/* Delete profile confirmation */ +"delete_profile_confirmation_message" = "这个操作无法被撤销"; + +/* Call screen text */ +"call_incoming" = "呼入的语音通话"; +/* Call screen text */ +"call_ended" = "通话结束"; +/* Call screen text */ +"call_reaching" = "连接中 ..."; + +/* File message text */ +"chat_file_cancelled" = "已取消"; +/* File message text */ +"chat_waiting" = "正在等待 ..."; +/* File message text */ +"chat_paused" = "已暂停"; + +/* Deleting chat confirmation */ +"delete_chat_title" = "删除这个聊天及所有消息记录?"; +/* Deleting contat confirmation */ +"delete_contact_title" = "删除这个联系人?\n你们之间的聊天记录将同时被删除。"; +/* Deleting contat request confirmation */ +"delete_contact_request_title" = "删除这个联系人请求?"; +/* Deleting single message in chat */ +"delete_single_message" = "Delete Message"; +/* Deleting multiple messages in chat. */ +"delete_multiple_messages" = "Delete Messages"; +/* Deleting all messages in chat. */ +"delete_all_messages" = "Delete All"; +/* Delete button */ +"alert_delete" = "删除"; +/* Delete button */ +"alert_cancel" = "取消"; + +/* Error while creating new profile */ +"login_enter_username_and_profile" = "请输入用户名与用户档案名。"; +/* Error while creating new profile */ +"login_profile_already_exists" = "档案名已存在"; + +/* General error title */ +"error_title" = "错误"; +/* General error button */ +"error_ok_button" = "确认"; +/* General error button */ +"error_retry_button" = "重试"; +/* Error */ +"error_contact_not_connected" = "联系人不在线"; +/* Error */ +"error_too_many_files" = "同时传输的文件数过多"; +/* Error */ +"error_internal_message" = "内部错误"; +/* Error */ +"error_wrong_password_title" = "密码错误"; +/* Error */ +"error_wrong_password_message" = "密码包含不支持的符号。"; +/* Error */ +"error_import_not_exist_title" = "无法导入 Tox 保存的文件"; +/* Error */ +"error_import_not_exist_message" = "文件不存在。"; +/* Error */ +"error_decrypt_title" = "无法解密 Tox 保存的文件"; +/* Error */ +"error_decrypt_empty_data_message" = "部分输入内容为空。"; +/* Error */ +"error_decrypt_bad_format_message" = "文件已损坏。"; +/* Error */ +"error_decrypt_wrong_password_message" = "密码错误或文件已损坏。"; +/* Error */ +"error_general_unknown_message" = "发生未知错误。"; +/* Error */ +"error_general_no_memory_message" = "内存不足。"; +/* Error */ +"error_general_bind_port_message" = "无法绑定端口。"; +/* Error */ +"error_general_profile_encrypted_message" = "档案已加密。"; +/* Error */ +"error_general_bad_format_message" = "文件已损坏。"; +/* Error */ +"error_proxy_title" = "代理服务器错误"; +/* Error */ +"error_proxy_invalid_address_message" = "代理服务器地址格式有误。"; +/* Error */ +"error_proxy_invalid_port_message" = "代理服务器端口无效。"; +/* Error */ +"error_proxy_host_not_resolved_message" = "无法连接到代理服务器。"; + +/* Error */ +"error_name_too_long" = "名字过长。"; +/* Error */ +"error_status_message_too_long" = "状态信息过长"; + +/* Error */ +"error_contact_request_too_long" = "消息过长"; +/* Error */ +"error_contact_request_no_message" = "未选中消息"; +/* Error */ +"error_contact_request_own_key" = "你不能添加自己为联系人"; +/* Error */ +"error_contact_request_already_sent" = "联系人请求已发送"; +/* Error */ +"error_contact_request_bad_checksum" = "无效的校验值,请检查你输入的 Tox ID"; +/* Error */ +"error_contact_request_new_nospam" = "该 Tox ID 已更改反垃圾设置,请核实"; + +/* Call error */ +"call_error_already_in_call" = "正在通话中"; +/* Call error */ +"call_error_contact_is_offline" = "联系人已离线"; +/* Call error */ +"call_error_no_active_call" = "没有进行中的语音通话"; + +/* Error while using color theme */ +"theme_error_cannot_open" = "无法开启主题,格式错误"; + +/* Tab chats badge ending */ +"accessibility_chats_ending" = "unread chats"; +/* Avatar button label in settings */ +"accessibility_avatar_button_label" = "Avatar"; +/* Avatar button hint in settings */ +"accessibility_avatar_button_hint" = "Sets or removes avatar."; +/* Chat button label in contact screen */ +"accessibility_chat_button_label" = "Chat"; +/* Chat button hint in contact screen */ +"accessibility_chat_button_hint" = "Opens chat dialog."; +/* Call button label in contact screen */ +"accessibility_call_button_label" = "Audio call"; +/* Call button hint in contact screen */ +"accessibility_call_button_hint" = "Makes audio call."; +/* Video button label in contact screen */ +"accessibility_video_button_label" = "Video call"; +/* Video button hint in contact screen */ +"accessibility_video_button_hint" = "Makes video call."; +/* Tap to copy hint */ +"accessibility_show_copy_hint" = "Shows copy menu."; +/* Tap to edit value hint */ +"accessibility_edit_value_hint" = "Edits value."; +/* Incoming message label */ +"accessibility_incoming_message_label" = "Message"; +/* Outgoing message label */ +"accessibility_outgoing_message_label" = "Your message"; +/* Text shown in chat when there are undelivered faux offline messages */ +"chat_pending_faux_offline_messages" = "当您和朋友都在线时,将发送未送达的消息。"; +/* Message shown on chat list when contact is typing */ +"chat_is_typing_text" = "正在打字..."; diff --git a/Antidote/zh.lproj/import-profile.html b/Antidote/zh.lproj/import-profile.html new file mode 100644 index 0000000..6a4a129 --- /dev/null +++ b/Antidote/zh.lproj/import-profile.html @@ -0,0 +1,9 @@ + +

导入你的 Tox 档案:

+ +
    +
  1. 使用任何应用 ( 如邮件或 Dropbox ) 发送“.tox”文件到你的设备
  2. +
  3. 然后打开这个文件
  4. +
  5. 在可用的应用列表中选择 Antidote
  6. +
  7. 选中你的新档案名并点击确定
  8. +
diff --git a/AntidoteTests/AntidoteTests-Bridging-Header.h b/AntidoteTests/AntidoteTests-Bridging-Header.h new file mode 100644 index 0000000..94dfa1b --- /dev/null +++ b/AntidoteTests/AntidoteTests-Bridging-Header.h @@ -0,0 +1,45 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#undef LOG_INFO +#undef LOG_DEBUG +#import "DDLog.h" +#import "DDASLLogger.h" +#import "DDTTYLogger.h" + +#import +#import +#import +#import + +#import "ExceptionHandling.h" + +#import "FBSnapshotTestCase.h" diff --git a/AntidoteTests/CellSnapshotTest.swift b/AntidoteTests/CellSnapshotTest.swift new file mode 100644 index 0000000..686b0a8 --- /dev/null +++ b/AntidoteTests/CellSnapshotTest.swift @@ -0,0 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class CellSnapshotTest: SnapshotBaseTest { + func updateCellLayout(_ cell: UITableViewCell) { + let size = cell.systemLayoutSizeFitting(CGSize(width: 320, height: 1000)) + cell.frame = CGRect(x: 0, y: 0, width: 320, height: size.height) + + cell.setNeedsLayout() + cell.layoutIfNeeded() + } +} diff --git a/AntidoteTests/ChatIncomingCallCellSnapshotTest.swift b/AntidoteTests/ChatIncomingCallCellSnapshotTest.swift new file mode 100644 index 0000000..c20b468 --- /dev/null +++ b/AntidoteTests/ChatIncomingCallCellSnapshotTest.swift @@ -0,0 +1,37 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatIncomingCallCellSnapshotTest: CellSnapshotTest { + override func setUp() { + super.setUp() + + recordMode = false + } + + func testAnsweredCall() { + let model = ChatIncomingCallCellModel() + model.callDuration = 137 + model.answered = true + + let cell = ChatIncomingCallCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testNonAnsweredCall() { + let model = ChatIncomingCallCellModel() + model.callDuration = 137 + model.answered = false + + let cell = ChatIncomingCallCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } +} diff --git a/AntidoteTests/ChatIncomingFileCellSnapshotTest.swift b/AntidoteTests/ChatIncomingFileCellSnapshotTest.swift new file mode 100644 index 0000000..c73ea8f --- /dev/null +++ b/AntidoteTests/ChatIncomingFileCellSnapshotTest.swift @@ -0,0 +1,109 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import MobileCoreServices + +class ChatIncomingFileCellSnapshotTest: CellSnapshotTest { + override func setUp() { + super.setUp() + + recordMode = false + } + + func testWaitingState() { + let model = ChatIncomingFileCellModel() + model.state = .waitingConfirmation + model.fileName = "file.txt" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePlainText as String + + let cell = ChatIncomingFileCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testLoading() { + let model = ChatIncomingFileCellModel() + model.state = .loading + model.fileName = "file.txt" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePlainText as String + + let progressObject = MockedChatProgressProtocol() + + let cell = ChatIncomingFileCell() + cell.setupWithTheme(theme, model: model) + cell.progressObject = progressObject + + progressObject.updateProgress?(0.43) + + updateCellLayout(cell) + verifyView(cell) + } + + func testPaused() { + let model = ChatIncomingFileCellModel() + model.state = .paused + model.fileName = "file.txt" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePlainText as String + + let progressObject = MockedChatProgressProtocol() + + let cell = ChatIncomingFileCell() + cell.setupWithTheme(theme, model: model) + cell.progressObject = progressObject + + progressObject.updateProgress?(0.43) + + updateCellLayout(cell) + verifyView(cell) + } + + func testCancelled() { + let model = ChatIncomingFileCellModel() + model.state = .cancelled + model.fileName = "file.txt" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePlainText as String + + let cell = ChatIncomingFileCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testDone() { + let model = ChatIncomingFileCellModel() + model.state = .done + model.fileName = "file.txt" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePlainText as String + + let cell = ChatIncomingFileCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testDoneWithImage() { + let model = ChatIncomingFileCellModel() + model.state = .done + model.fileName = "icon.png" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePNG as String + + let cell = ChatIncomingFileCell() + cell.setupWithTheme(theme, model: model) + cell.setButtonImage(image) + + updateCellLayout(cell) + verifyView(cell) + } +} diff --git a/AntidoteTests/ChatIncomingTextCellSnapshotTest.swift b/AntidoteTests/ChatIncomingTextCellSnapshotTest.swift new file mode 100644 index 0000000..5f7d62f --- /dev/null +++ b/AntidoteTests/ChatIncomingTextCellSnapshotTest.swift @@ -0,0 +1,67 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatIncomingTextCellSnapshotTest: CellSnapshotTest { + override func setUp() { + super.setUp() + + recordMode = false + } + + func testSmallMessage() { + let model = ChatBaseTextCellModel() + model.message = "Hi" + + let cell = ChatIncomingTextCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testMediumMessage() { + let model = ChatBaseTextCellModel() + model.message = "Some nice medium message" + + let cell = ChatIncomingTextCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testHugeMessage() { + let model = ChatBaseTextCellModel() + model.message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. " + + let cell = ChatIncomingTextCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + // Broken on Travic CI. + // func testWithLink() { + // let model = ChatBaseTextCellModel() + // model.message = "Lorem ipsum dolor sit amet, https://tox.chat consectetur adipiscing elit, +1234567890" + + // let cell = ChatIncomingTextCell() + // cell.setupWithTheme(theme, model: model) + + // updateCellLayout(cell) + + // let expectation = self.expectation(description: "link rendering expectation") + + // let delayTime = DispatchTime.now() + Double(Int64(0.5 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) + // DispatchQueue.main.asyncAfter(deadline: delayTime) { [weak self] in + // self?.verifyView(cell) + // expectation.fulfill() + // } + + // waitForExpectations(timeout: 1.0, handler: nil) + // } +} diff --git a/AntidoteTests/ChatListCellSnapshotTest.swift b/AntidoteTests/ChatListCellSnapshotTest.swift new file mode 100644 index 0000000..067344a --- /dev/null +++ b/AntidoteTests/ChatListCellSnapshotTest.swift @@ -0,0 +1,62 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatListCellSnapshotTest: CellSnapshotTest { + override func setUp() { + super.setUp() + + recordMode = false + } + + func testDefault() { + let model = ChatListCellModel() + model.avatar = image + model.nickname = "dvor" + model.message = "Hi! This is some random message." + model.dateText = "Yesterday" + model.status = .offline + model.isUnread = false + + let cell = ChatListCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testLongMessage() { + let model = ChatListCellModel() + model.avatar = image + model.nickname = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + model.message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. " + model.dateText = "Yesterday" + model.status = .online + model.isUnread = false + + let cell = ChatListCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testUnread() { + let model = ChatListCellModel() + model.avatar = image + model.nickname = "dvor" + model.message = "Hi! This is some random message." + model.dateText = "10:37" + model.status = .away + model.isUnread = true + + let cell = ChatListCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } +} + diff --git a/AntidoteTests/ChatMovableDateCellSnapshotTest.swift b/AntidoteTests/ChatMovableDateCellSnapshotTest.swift new file mode 100644 index 0000000..625ca1e --- /dev/null +++ b/AntidoteTests/ChatMovableDateCellSnapshotTest.swift @@ -0,0 +1,36 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatMovableDateCellSnapshotTest: CellSnapshotTest { + override func setUp() { + super.setUp() + + recordMode = false + } + + func testDefault() { + let model = ChatMovableDateCellModel() + model.dateString = "03:13" + + let cell = ChatMovableDateCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testPanned() { + let model = ChatMovableDateCellModel() + model.dateString = "03:13" + + let cell = ChatMovableDateCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + cell.movableOffset = -200.0 + verifyView(cell) + } +} diff --git a/AntidoteTests/ChatOutgoingCallCellSnapshotTest.swift b/AntidoteTests/ChatOutgoingCallCellSnapshotTest.swift new file mode 100644 index 0000000..76be5e3 --- /dev/null +++ b/AntidoteTests/ChatOutgoingCallCellSnapshotTest.swift @@ -0,0 +1,37 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatOutgoingCallCellSnapshotTest: CellSnapshotTest { + override func setUp() { + super.setUp() + + recordMode = false + } + + func testAnsweredCall() { + let model = ChatOutgoingCallCellModel() + model.callDuration = 137 + model.answered = true + + let cell = ChatOutgoingCallCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testNonAnsweredCall() { + let model = ChatOutgoingCallCellModel() + model.callDuration = 137 + model.answered = false + + let cell = ChatOutgoingCallCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } +} diff --git a/AntidoteTests/ChatOutgoingFileCellSnapshotTest.swift b/AntidoteTests/ChatOutgoingFileCellSnapshotTest.swift new file mode 100644 index 0000000..1b508ae --- /dev/null +++ b/AntidoteTests/ChatOutgoingFileCellSnapshotTest.swift @@ -0,0 +1,179 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import MobileCoreServices + +class ChatOutgoingFileCellSnapshotTest: CellSnapshotTest { + override func setUp() { + super.setUp() + + recordMode = false + } + + func testWaitingState() { + let model = ChatOutgoingFileCellModel() + model.state = .waitingConfirmation + model.fileName = "file.txt" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePlainText as String + + let cell = ChatOutgoingFileCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testLoading() { + let model = ChatOutgoingFileCellModel() + model.state = .loading + model.fileName = "file.txt" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePlainText as String + + let progressObject = MockedChatProgressProtocol() + + let cell = ChatOutgoingFileCell() + cell.setupWithTheme(theme, model: model) + cell.progressObject = progressObject + + progressObject.updateProgress?(0.43) + + updateCellLayout(cell) + verifyView(cell) + } + + func testPaused() { + let model = ChatOutgoingFileCellModel() + model.state = .paused + model.fileName = "file.txt" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePlainText as String + + let progressObject = MockedChatProgressProtocol() + + let cell = ChatOutgoingFileCell() + cell.setupWithTheme(theme, model: model) + cell.progressObject = progressObject + + progressObject.updateProgress?(0.43) + + updateCellLayout(cell) + verifyView(cell) + } + + func testCancelled() { + let model = ChatOutgoingFileCellModel() + model.state = .cancelled + model.fileName = "file.txt" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePlainText as String + + let cell = ChatOutgoingFileCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testDone() { + let model = ChatOutgoingFileCellModel() + model.state = .done + model.fileName = "file.txt" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePlainText as String + + let cell = ChatOutgoingFileCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testWaitingStateWithImage() { + let model = ChatOutgoingFileCellModel() + model.state = .waitingConfirmation + model.fileName = "icon.png" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePNG as String + + let cell = ChatOutgoingFileCell() + cell.setupWithTheme(theme, model: model) + cell.setButtonImage(image) + + updateCellLayout(cell) + verifyView(cell) + } + + func testLoadingWithImage() { + let model = ChatOutgoingFileCellModel() + model.state = .loading + model.fileName = "icon.png" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePNG as String + + let progressObject = MockedChatProgressProtocol() + + let cell = ChatOutgoingFileCell() + cell.setupWithTheme(theme, model: model) + cell.progressObject = progressObject + cell.setButtonImage(image) + + progressObject.updateProgress?(0.43) + + updateCellLayout(cell) + verifyView(cell) + } + + func testPausedWithImage() { + let model = ChatOutgoingFileCellModel() + model.state = .paused + model.fileName = "icon.png" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePNG as String + + let progressObject = MockedChatProgressProtocol() + + let cell = ChatOutgoingFileCell() + cell.setupWithTheme(theme, model: model) + cell.progressObject = progressObject + cell.setButtonImage(image) + + progressObject.updateProgress?(0.43) + + updateCellLayout(cell) + verifyView(cell) + } + + func testCancelledWithImage() { + let model = ChatOutgoingFileCellModel() + model.state = .cancelled + model.fileName = "icon.png" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePNG as String + + let cell = ChatOutgoingFileCell() + cell.setupWithTheme(theme, model: model) + cell.setButtonImage(image) + + updateCellLayout(cell) + verifyView(cell) + } + + func testDoneWithImage() { + let model = ChatOutgoingFileCellModel() + model.state = .done + model.fileName = "icon.png" + model.fileSize = "3.14 KB" + model.fileUTI = kUTTypePNG as String + + let cell = ChatOutgoingFileCell() + cell.setupWithTheme(theme, model: model) + cell.setButtonImage(image) + + updateCellLayout(cell) + verifyView(cell) + } +} diff --git a/AntidoteTests/ChatOutgoingTextCellSnapshotTest.swift b/AntidoteTests/ChatOutgoingTextCellSnapshotTest.swift new file mode 100644 index 0000000..5a86af7 --- /dev/null +++ b/AntidoteTests/ChatOutgoingTextCellSnapshotTest.swift @@ -0,0 +1,83 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class ChatOutgoingTextCellSnapshotTest: CellSnapshotTest { + override func setUp() { + super.setUp() + + recordMode = false + } + + func testSmallMessage() { + let model = ChatOutgoingTextCellModel() + model.message = "Hi" + model.delivered = true + + let cell = ChatOutgoingTextCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testMediumMessage() { + let model = ChatOutgoingTextCellModel() + model.message = "Some nice medium message" + model.delivered = true + + let cell = ChatOutgoingTextCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testMediumMessageUndelivered() { + let model = ChatOutgoingTextCellModel() + model.message = "Some nice medium message" + model.delivered = false + + let cell = ChatOutgoingTextCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + func testHugeMessage() { + let model = ChatOutgoingTextCellModel() + model.message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. " + model.delivered = true + + let cell = ChatOutgoingTextCell() + cell.setupWithTheme(theme, model: model) + + updateCellLayout(cell) + verifyView(cell) + } + + // Broken on Travic CI. + // func testWithLink() { + // let model = ChatOutgoingTextCellModel() + // model.message = "Lorem ipsum dolor sit amet, https://tox.chat consectetur adipiscing elit, +1234567890" + // model.delivered = true + + // let cell = ChatOutgoingTextCell() + // cell.setupWithTheme(theme, model: model) + + // updateCellLayout(cell) + + // let expectation = self.expectation(description: "link rendering expectation") + + // let delayTime = DispatchTime.now() + Double(Int64(0.5 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) + // DispatchQueue.main.asyncAfter(deadline: delayTime) { [weak self] in + // self?.verifyView(cell) + // expectation.fulfill() + // } + + // waitForExpectations(timeout: 1.0, handler: nil) + // } +} diff --git a/AntidoteTests/FriendListDataSourceTest.swift b/AntidoteTests/FriendListDataSourceTest.swift new file mode 100644 index 0000000..7fc4434 --- /dev/null +++ b/AntidoteTests/FriendListDataSourceTest.swift @@ -0,0 +1,159 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import XCTest + +class FriendListDataSourceTest: XCTestCase { + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + // Antidote/objcTox should be refactored to protocol oriented architecture, then testing will become possible :-) + + // func testPerformFetch() { + // let requests = MockedFetchedResultsController(objects: [[]]) + // let friends = MockedFetchedResultsController(objects: [[]]) + + // let dataSource = FriendListDataSource(requestsController: requests, friendsController: friends) + + // // calling some method requiring both requests and friends controller + // _ = dataSource.numberOfSections() + + // XCTAssertTrue(requests.didPerformFetch) + // XCTAssertTrue(friends.didPerformFetch) + // } + + // func testReset() { + // let requests = MockedFetchedResultsController(objects: [[]]) + // let friends = MockedFetchedResultsController(objects: [[]]) + + // let dataSource = FriendListDataSource(requestsController: requests, friendsController: friends) + + // dataSource.reset() + + // XCTAssertTrue(requests.didReset) + // XCTAssertTrue(friends.didReset) + // } + + // func testNumberOfSections_NoRequests_NoFriends() { + // let requests = MockedFetchedResultsController(objects: [[]]) + // let friends = MockedFetchedResultsController(objects: [[]]) + + // let dataSource = FriendListDataSource(requestsController: requests, friendsController: friends) + + // XCTAssertEqual(dataSource.numberOfSections(), 1) + // } + + // func testNumberOfSections_NoRequests_OneFriendSection() { + // let requests = MockedFetchedResultsController(objects: [[]]) + // let friends = MockedFetchedResultsController(objects: [[ NSObject() ]]) + + // let dataSource = FriendListDataSource(requestsController: requests, friendsController: friends) + + // XCTAssertEqual(dataSource.numberOfSections(), 1) + // } + + // func testNumberOfSections_NoRequests_MultipleFriendSection() { + // let requests = MockedFetchedResultsController(objects: [[]]) + // let friends = MockedFetchedResultsController(objects: [ + // [NSObject()], + // [NSObject()], + // [NSObject()], + // ]) + + // let dataSource = FriendListDataSource(requestsController: requests, friendsController: friends) + + // XCTAssertEqual(dataSource.numberOfSections(), 3) + // } + + // func testNumberOfSections_OneRequest_NoFriends() { + // let requests = MockedFetchedResultsController(objects: [[ NSObject() ]]) + // let friends = MockedFetchedResultsController(objects: [[]]) + + // let dataSource = FriendListDataSource(requestsController: requests, friendsController: friends) + + // XCTAssertEqual(dataSource.numberOfSections(), 2) + // } + + // func testNumberOfSections_MultipleRequest_NoFriends() { + // let requests = MockedFetchedResultsController(objects: [[ + // NSObject(), + // NSObject(), + // NSObject(), + // ]]) + // let friends = MockedFetchedResultsController(objects: [[]]) + + // let dataSource = FriendListDataSource(requestsController: requests, friendsController: friends) + + // XCTAssertEqual(dataSource.numberOfSections(), 2) + // } + + // func testNumberOfSections_MultipleRequest_MultipleFriends() { + // let requests = MockedFetchedResultsController(objects: [[ + // NSObject(), + // NSObject(), + // NSObject(), + // ]]) + // let friends = MockedFetchedResultsController(objects: [ + // [NSObject()], + // [NSObject()], + // [NSObject()], + // ]) + + // let dataSource = FriendListDataSource(requestsController: requests, friendsController: friends) + + // XCTAssertEqual(dataSource.numberOfSections(), 4) + // } + + // func testNumberOfRows_Empty() { + // let requests = MockedFetchedResultsController(objects: [[]]) + // let friends = MockedFetchedResultsController(objects: [[]]) + + // let dataSource = FriendListDataSource(requestsController: requests, friendsController: friends) + + // XCTAssertEqual(dataSource.numberOfRowsInSection(0), 0) + // } + + // func testNumberOfRows_NoRequest_Friends() { + // let requests = MockedFetchedResultsController(objects: [[]]) + // let friends = MockedFetchedResultsController(objects: [ + // [NSObject()], + // [NSObject(), NSObject()], + // [NSObject(), NSObject(), NSObject()], + // ]) + + // let dataSource = FriendListDataSource(requestsController: requests, friendsController: friends) + + // XCTAssertEqual(dataSource.numberOfRowsInSection(0), 1) + // XCTAssertEqual(dataSource.numberOfRowsInSection(1), 2) + // XCTAssertEqual(dataSource.numberOfRowsInSection(2), 3) + // } + + // func testNumberOfRows_Requests_Friends() { + // let requests = MockedFetchedResultsController(objects: [[ + // NSObject(), + // NSObject(), + // NSObject(), + // NSObject(), + // ]]) + // let friends = MockedFetchedResultsController(objects: [ + // [NSObject()], + // [NSObject(), NSObject()], + // [NSObject(), NSObject(), NSObject()], + // ]) + + // let dataSource = FriendListDataSource(requestsController: requests, friendsController: friends) + + // XCTAssertEqual(dataSource.numberOfRowsInSection(0), 4) + // XCTAssertEqual(dataSource.numberOfRowsInSection(1), 1) + // XCTAssertEqual(dataSource.numberOfRowsInSection(2), 2) + // XCTAssertEqual(dataSource.numberOfRowsInSection(3), 3) + // } +} diff --git a/AntidoteTests/Info.plist b/AntidoteTests/Info.plist new file mode 100644 index 0000000..526bf85 --- /dev/null +++ b/AntidoteTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 30 + + diff --git a/AntidoteTests/KeychainManagerTests.swift b/AntidoteTests/KeychainManagerTests.swift new file mode 100644 index 0000000..312ad48 --- /dev/null +++ b/AntidoteTests/KeychainManagerTests.swift @@ -0,0 +1,52 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import XCTest + +class KeychainManagerTests: XCTestCase { + func testToxPassword() { + let manager = KeychainManager() + + manager.toxPasswordForActiveAccount = nil + XCTAssertNil(manager.toxPasswordForActiveAccount) + + manager.toxPasswordForActiveAccount = "password" + XCTAssertEqual(manager.toxPasswordForActiveAccount!, "password") + + manager.toxPasswordForActiveAccount = "another" + XCTAssertEqual(manager.toxPasswordForActiveAccount!, "another") + + manager.toxPasswordForActiveAccount = nil + XCTAssertNil(manager.toxPasswordForActiveAccount) + + manager.toxPasswordForActiveAccount = "some pass" + XCTAssertEqual(manager.toxPasswordForActiveAccount!, "some pass") + + manager.deleteActiveAccountData() + XCTAssertNil(manager.toxPasswordForActiveAccount) + } + + func testFailedPinAttemptsNumber() { + let manager = KeychainManager() + + manager.failedPinAttemptsNumber = nil + XCTAssertNil(manager.failedPinAttemptsNumber) + + manager.failedPinAttemptsNumber = 5 + XCTAssertEqual(manager.failedPinAttemptsNumber!, 5) + + manager.failedPinAttemptsNumber = 8 + XCTAssertEqual(manager.failedPinAttemptsNumber!, 8) + + manager.failedPinAttemptsNumber = nil + XCTAssertNil(manager.failedPinAttemptsNumber) + + manager.failedPinAttemptsNumber = 3 + XCTAssertEqual(manager.failedPinAttemptsNumber!, 3) + + manager.deleteActiveAccountData() + XCTAssertNil(manager.failedPinAttemptsNumber) + } +} + diff --git a/AntidoteTests/LoginChoiceViewSnapshotTest.swift b/AntidoteTests/LoginChoiceViewSnapshotTest.swift new file mode 100644 index 0000000..14bb37a --- /dev/null +++ b/AntidoteTests/LoginChoiceViewSnapshotTest.swift @@ -0,0 +1,18 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class LoginChoiceViewSnapshotTest: SnapshotBaseTest { + override func setUp() { + super.setUp() + + recordMode = false + } + + func testDefault() { + let controller = LoginChoiceController(theme: theme) + verifyView(controller.view) + } +} diff --git a/AntidoteTests/MockedChatProgressProtocol.swift b/AntidoteTests/MockedChatProgressProtocol.swift new file mode 100644 index 0000000..0c51012 --- /dev/null +++ b/AntidoteTests/MockedChatProgressProtocol.swift @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +class MockedChatProgressProtocol: ChatProgressProtocol { + var updateProgress: ((_ progress: Float) -> Void)? + var updateEta: ((_ eta: CFTimeInterval, _ bytesPerSecond: OCTToxFileSize) -> Void)? +} diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingCallCellSnapshotTest/testAnsweredCall_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingCallCellSnapshotTest/testAnsweredCall_normal@2x.png new file mode 100644 index 0000000..635c4bf Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingCallCellSnapshotTest/testAnsweredCall_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingCallCellSnapshotTest/testAnsweredCall_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingCallCellSnapshotTest/testAnsweredCall_right-to-left@2x.png new file mode 100644 index 0000000..8f63fde Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingCallCellSnapshotTest/testAnsweredCall_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingCallCellSnapshotTest/testNonAnsweredCall_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingCallCellSnapshotTest/testNonAnsweredCall_normal@2x.png new file mode 100644 index 0000000..8785a29 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingCallCellSnapshotTest/testNonAnsweredCall_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingCallCellSnapshotTest/testNonAnsweredCall_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingCallCellSnapshotTest/testNonAnsweredCall_right-to-left@2x.png new file mode 100644 index 0000000..f34a919 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingCallCellSnapshotTest/testNonAnsweredCall_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testCancelled_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testCancelled_normal@2x.png new file mode 100644 index 0000000..a54a1bb Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testCancelled_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testCancelled_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testCancelled_right-to-left@2x.png new file mode 100644 index 0000000..01a6f2c Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testCancelled_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testDoneWithImage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testDoneWithImage_normal@2x.png new file mode 100644 index 0000000..5ec428e Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testDoneWithImage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testDoneWithImage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testDoneWithImage_right-to-left@2x.png new file mode 100644 index 0000000..bbc938d Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testDoneWithImage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testDone_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testDone_normal@2x.png new file mode 100644 index 0000000..462c582 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testDone_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testDone_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testDone_right-to-left@2x.png new file mode 100644 index 0000000..677ec89 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testDone_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testLoading_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testLoading_normal@2x.png new file mode 100644 index 0000000..32cfc9e Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testLoading_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testLoading_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testLoading_right-to-left@2x.png new file mode 100644 index 0000000..3f11b79 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testLoading_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testPaused_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testPaused_normal@2x.png new file mode 100644 index 0000000..1c09cf5 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testPaused_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testPaused_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testPaused_right-to-left@2x.png new file mode 100644 index 0000000..ec60427 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testPaused_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testWaitingState_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testWaitingState_normal@2x.png new file mode 100644 index 0000000..ae6a453 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testWaitingState_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testWaitingState_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testWaitingState_right-to-left@2x.png new file mode 100644 index 0000000..dd1ece8 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingFileCellSnapshotTest/testWaitingState_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testHugeMessage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testHugeMessage_normal@2x.png new file mode 100644 index 0000000..1296159 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testHugeMessage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testHugeMessage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testHugeMessage_right-to-left@2x.png new file mode 100644 index 0000000..d7949f8 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testHugeMessage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testMediumMessage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testMediumMessage_normal@2x.png new file mode 100644 index 0000000..ae015e2 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testMediumMessage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testMediumMessage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testMediumMessage_right-to-left@2x.png new file mode 100644 index 0000000..cbb5ea9 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testMediumMessage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testSmallMessage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testSmallMessage_normal@2x.png new file mode 100644 index 0000000..4496e56 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testSmallMessage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testSmallMessage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testSmallMessage_right-to-left@2x.png new file mode 100644 index 0000000..0e09fb1 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testSmallMessage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testWithLink_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testWithLink_normal@2x.png new file mode 100644 index 0000000..8b159e3 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testWithLink_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testWithLink_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testWithLink_right-to-left@2x.png new file mode 100644 index 0000000..6f7b620 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatIncomingTextCellSnapshotTest/testWithLink_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testDefault_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testDefault_normal@2x.png new file mode 100644 index 0000000..7cae639 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testDefault_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testDefault_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testDefault_right-to-left@2x.png new file mode 100644 index 0000000..c5ef979 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testDefault_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testLongMessage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testLongMessage_normal@2x.png new file mode 100644 index 0000000..9f9b9d9 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testLongMessage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testLongMessage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testLongMessage_right-to-left@2x.png new file mode 100644 index 0000000..5057f37 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testLongMessage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testUnread_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testUnread_normal@2x.png new file mode 100644 index 0000000..5325264 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testUnread_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testUnread_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testUnread_right-to-left@2x.png new file mode 100644 index 0000000..0cd56c5 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatListCellSnapshotTest/testUnread_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatMovableDateCellSnapshotTest/testDefault_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatMovableDateCellSnapshotTest/testDefault_normal@2x.png new file mode 100644 index 0000000..84942d5 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatMovableDateCellSnapshotTest/testDefault_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatMovableDateCellSnapshotTest/testDefault_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatMovableDateCellSnapshotTest/testDefault_right-to-left@2x.png new file mode 100644 index 0000000..84942d5 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatMovableDateCellSnapshotTest/testDefault_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatMovableDateCellSnapshotTest/testPanned_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatMovableDateCellSnapshotTest/testPanned_normal@2x.png new file mode 100644 index 0000000..b83a6d0 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatMovableDateCellSnapshotTest/testPanned_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatMovableDateCellSnapshotTest/testPanned_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatMovableDateCellSnapshotTest/testPanned_right-to-left@2x.png new file mode 100644 index 0000000..736a6cc Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatMovableDateCellSnapshotTest/testPanned_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingCallCellSnapshotTest/testAnsweredCall_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingCallCellSnapshotTest/testAnsweredCall_normal@2x.png new file mode 100644 index 0000000..d5a8643 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingCallCellSnapshotTest/testAnsweredCall_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingCallCellSnapshotTest/testAnsweredCall_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingCallCellSnapshotTest/testAnsweredCall_right-to-left@2x.png new file mode 100644 index 0000000..63abc3f Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingCallCellSnapshotTest/testAnsweredCall_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingCallCellSnapshotTest/testNonAnsweredCall_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingCallCellSnapshotTest/testNonAnsweredCall_normal@2x.png new file mode 100644 index 0000000..35e6edc Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingCallCellSnapshotTest/testNonAnsweredCall_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingCallCellSnapshotTest/testNonAnsweredCall_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingCallCellSnapshotTest/testNonAnsweredCall_right-to-left@2x.png new file mode 100644 index 0000000..4195a71 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingCallCellSnapshotTest/testNonAnsweredCall_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testCancelledWithImage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testCancelledWithImage_normal@2x.png new file mode 100644 index 0000000..9fe9b43 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testCancelledWithImage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testCancelledWithImage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testCancelledWithImage_right-to-left@2x.png new file mode 100644 index 0000000..c8fc202 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testCancelledWithImage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testCancelled_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testCancelled_normal@2x.png new file mode 100644 index 0000000..6a5d4ff Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testCancelled_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testCancelled_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testCancelled_right-to-left@2x.png new file mode 100644 index 0000000..04ddece Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testCancelled_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testDoneWithImage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testDoneWithImage_normal@2x.png new file mode 100644 index 0000000..bbc938d Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testDoneWithImage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testDoneWithImage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testDoneWithImage_right-to-left@2x.png new file mode 100644 index 0000000..5ec428e Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testDoneWithImage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testDone_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testDone_normal@2x.png new file mode 100644 index 0000000..39a027b Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testDone_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testDone_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testDone_right-to-left@2x.png new file mode 100644 index 0000000..735fdde Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testDone_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testLoadingWithImage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testLoadingWithImage_normal@2x.png new file mode 100644 index 0000000..f6171de Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testLoadingWithImage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testLoadingWithImage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testLoadingWithImage_right-to-left@2x.png new file mode 100644 index 0000000..4c4adaf Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testLoadingWithImage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testLoading_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testLoading_normal@2x.png new file mode 100644 index 0000000..ceb6467 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testLoading_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testLoading_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testLoading_right-to-left@2x.png new file mode 100644 index 0000000..3846257 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testLoading_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testPausedWithImage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testPausedWithImage_normal@2x.png new file mode 100644 index 0000000..0dd7334 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testPausedWithImage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testPausedWithImage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testPausedWithImage_right-to-left@2x.png new file mode 100644 index 0000000..7aade3a Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testPausedWithImage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testPaused_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testPaused_normal@2x.png new file mode 100644 index 0000000..5a90b1c Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testPaused_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testPaused_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testPaused_right-to-left@2x.png new file mode 100644 index 0000000..1846ce3 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testPaused_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testWaitingStateWithImage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testWaitingStateWithImage_normal@2x.png new file mode 100644 index 0000000..960247c Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testWaitingStateWithImage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testWaitingStateWithImage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testWaitingStateWithImage_right-to-left@2x.png new file mode 100644 index 0000000..2082ef5 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testWaitingStateWithImage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testWaitingState_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testWaitingState_normal@2x.png new file mode 100644 index 0000000..4fe9418 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testWaitingState_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testWaitingState_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testWaitingState_right-to-left@2x.png new file mode 100644 index 0000000..cf2e009 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingFileCellSnapshotTest/testWaitingState_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testHugeMessage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testHugeMessage_normal@2x.png new file mode 100644 index 0000000..8d53216 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testHugeMessage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testHugeMessage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testHugeMessage_right-to-left@2x.png new file mode 100644 index 0000000..d5b248d Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testHugeMessage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testMediumMessageUndelivered_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testMediumMessageUndelivered_normal@2x.png new file mode 100644 index 0000000..2962aa3 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testMediumMessageUndelivered_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testMediumMessageUndelivered_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testMediumMessageUndelivered_right-to-left@2x.png new file mode 100644 index 0000000..97e7d3b Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testMediumMessageUndelivered_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testMediumMessage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testMediumMessage_normal@2x.png new file mode 100644 index 0000000..6343744 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testMediumMessage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testMediumMessage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testMediumMessage_right-to-left@2x.png new file mode 100644 index 0000000..2d23e02 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testMediumMessage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testSmallMessage_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testSmallMessage_normal@2x.png new file mode 100644 index 0000000..2d12377 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testSmallMessage_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testSmallMessage_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testSmallMessage_right-to-left@2x.png new file mode 100644 index 0000000..faea13e Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testSmallMessage_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testWithLink_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testWithLink_normal@2x.png new file mode 100644 index 0000000..6d2953d Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testWithLink_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testWithLink_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testWithLink_right-to-left@2x.png new file mode 100644 index 0000000..26f6a95 Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.ChatOutgoingTextCellSnapshotTest/testWithLink_right-to-left@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.LoginChoiceViewSnapshotTest/testDefault_normal@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.LoginChoiceViewSnapshotTest/testDefault_normal@2x.png new file mode 100644 index 0000000..0fc594e Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.LoginChoiceViewSnapshotTest/testDefault_normal@2x.png differ diff --git a/AntidoteTests/ReferenceImages_64/AntidoteTests.LoginChoiceViewSnapshotTest/testDefault_right-to-left@2x.png b/AntidoteTests/ReferenceImages_64/AntidoteTests.LoginChoiceViewSnapshotTest/testDefault_right-to-left@2x.png new file mode 100644 index 0000000..0fc594e Binary files /dev/null and b/AntidoteTests/ReferenceImages_64/AntidoteTests.LoginChoiceViewSnapshotTest/testDefault_right-to-left@2x.png differ diff --git a/AntidoteTests/SnapshotBaseTest.swift b/AntidoteTests/SnapshotBaseTest.swift new file mode 100644 index 0000000..3313fb7 --- /dev/null +++ b/AntidoteTests/SnapshotBaseTest.swift @@ -0,0 +1,44 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import XCTest + +class SnapshotBaseTest: FBSnapshotTestCase { + var theme: Theme! + + var image: UIImage { + get { + let bundle = Bundle(for: type(of: self)) + return UIImage(named: "icon", in:bundle, compatibleWith: nil)! + } + } + + override func setUp() { + super.setUp() + + let filepath = Bundle.main.path(forResource: "default-theme", ofType: "yaml")! + let yamlString = try! NSString(contentsOfFile:filepath, encoding:String.Encoding.utf8.rawValue) as String + + theme = try! Theme(yamlString: yamlString) + } + + func verifyView(_ view: UIView) { + FBSnapshotVerifyView(view, identifier: "normal") + + view.forceRightToLeft() + FBSnapshotVerifyView(view, identifier: "right-to-left") + } +} + +private extension UIView { + func forceRightToLeft() { + if #available(iOS 9.0, *) { + semanticContentAttribute = .forceRightToLeft + } + + for view in subviews { + view.forceRightToLeft() + } + } +} diff --git a/AntidoteTests/SwiftSupport.swift b/AntidoteTests/SwiftSupport.swift new file mode 100644 index 0000000..471bb0d --- /dev/null +++ b/AntidoteTests/SwiftSupport.swift @@ -0,0 +1,125 @@ +/* +* Copyright (c) 2015, Facebook, Inc. +* All rights reserved. +* +* This source code is licensed under the BSD-style license found in the +* LICENSE file in the root directory of this source tree. An additional grant +* of patent rights can be found in the PATENTS file in the same directory. +* +*/ + +#if swift(>=3) + public extension FBSnapshotTestCase { + public func FBSnapshotVerifyView(_ view: UIView, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line) + } + + public func FBSnapshotVerifyLayer(_ layer: CALayer, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line) + } + + private func FBSnapshotVerifyViewOrLayer(_ viewOrLayer: AnyObject, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + let envReferenceImageDirectory = self.getReferenceImageDirectory(withDefault: FB_REFERENCE_IMAGE_DIR) + var error: NSError? + var comparisonSuccess = false + + if let envReferenceImageDirectory = envReferenceImageDirectory { + for suffix in suffixes { + let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)" + if viewOrLayer.isKind(of: UIView.self) { + do { + try compareSnapshot(of: viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance) + comparisonSuccess = true + } catch let error1 as NSError { + error = error1 + comparisonSuccess = false + } + } else if viewOrLayer.isKind(of: CALayer.self) { + do { + try compareSnapshot(of: viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance) + comparisonSuccess = true + } catch let error1 as NSError { + error = error1 + comparisonSuccess = false + } + } else { + assertionFailure("Only UIView and CALayer classes can be snapshotted") + } + + assert(recordMode == false, message: "Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!", file: file, line: line) + + if comparisonSuccess || recordMode { + break + } + + assert(comparisonSuccess, message: "Snapshot comparison failed: \(error)", file: file, line: line) + } + } else { + XCTFail("Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.") + } + } + + func assert(_ assertion: Bool, message: String, file: StaticString, line: UInt) { + if !assertion { + XCTFail(message, file: file, line: line) + } + } + } +#else +public extension FBSnapshotTestCase { + public func FBSnapshotVerifyView(view: UIView, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line) + } + + public func FBSnapshotVerifyLayer(layer: CALayer, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line) + } + + private func FBSnapshotVerifyViewOrLayer(viewOrLayer: AnyObject, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + let envReferenceImageDirectory = self.getReferenceImageDirectoryWithDefault(FB_REFERENCE_IMAGE_DIR) + var error: NSError? + var comparisonSuccess = false + + if let envReferenceImageDirectory = envReferenceImageDirectory { + for suffix in suffixes { + let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)" + if viewOrLayer.isKindOfClass(UIView) { + do { + try compareSnapshotOfView(viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance) + comparisonSuccess = true + } catch let error1 as NSError { + error = error1 + comparisonSuccess = false + } + } else if viewOrLayer.isKindOfClass(CALayer) { + do { + try compareSnapshotOfLayer(viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance) + comparisonSuccess = true + } catch let error1 as NSError { + error = error1 + comparisonSuccess = false + } + } else { + assertionFailure("Only UIView and CALayer classes can be snapshotted") + } + + assert(recordMode == false, message: "Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!", file: file, line: line) + + if comparisonSuccess || recordMode { + break + } + + assert(comparisonSuccess, message: "Snapshot comparison failed: \(error)", file: file, line: line) + } + } else { + XCTFail("Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.") + } + } + + func assert(assertion: Bool, message: String, file: StaticString, line: UInt) { + if !assertion { + XCTFail(message, file: file, line: line) + } + } +} +#endif diff --git a/AntidoteTests/ThemeTest.swift b/AntidoteTests/ThemeTest.swift new file mode 100644 index 0000000..f21ec18 --- /dev/null +++ b/AntidoteTests/ThemeTest.swift @@ -0,0 +1,197 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import XCTest + +class ThemeTest: XCTestCase { + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testParsingFile() { + let string = + "version: 1\n" + + "colors:\n" + + " first: \"AABBCC\"\n" + + " second: \"55667788\"\n" + + "values:\n" + + " login-background: second\n" + + " login-gradient: first\n" + + " login-tox-logo: second\n" + + " login-button-text: first\n" + + " login-button-background: second\n" + + " login-description-label: first\n" + + " login-form-background: second\n" + + " login-form-text: first\n" + + " login-link-color: second\n" + + + " translucent-background: first\n" + + + " normal-background: second\n" + + " normal-text: first\n" + + " link-text: second\n" + + " connecting-background: first\n" + + " connecting-text: second\n" + + " separators-and-borders: first\n" + + " offline-status: second\n" + + " online-status: first\n" + + " away-status: second\n" + + " busy-status: first\n" + + " status-background: second\n" + + " friend-cell-status: first\n" + + " chat-list-cell-message: second\n" + + " chat-list-cell-unread-background: first\n" + + " chat-input-background: second\n" + + " chat-incoming-bubble: first\n" + + " chat-outgoing-bubble: second\n" + + " chat-information-text: second\n" + + " tab-badge-background: first\n" + + " tab-badge-text: second\n" + + " tab-item-active: first\n" + + " tab-item-inactive: second\n" + + " notification-background: first\n" + + " notification-text: second\n" + + " settings-background: first\n" + + " call-text-color: second\n" + + " call-decline-button-background: first\n" + + " call-answer-button-background: second\n" + + " call-control-background: first\n" + + " call-control-selected-background: second\n" + + " call-button-icon-color: first\n" + + " call-button-selected-icon-color: second\n" + + " call-video-preview-background: first\n" + + " rounded-button-text: second\n" + + " rounded-positive-button-background: first\n" + + " rounded-negative-button-background: second\n" + + " empty-screen-placeholder-text: first\n" + + " file-image-background-active: second\n" + + " file-image-cancelled-text: first\n" + + " file-image-accept-button-tint: second\n" + + " file-image-cancel-button-tint: first\n" + + " lock-gradient-top: second\n" + + " lock-gradient-bottom: first\n" + + "" + + let first = UIColor(red: 170.0 / 255.0, green: 187.0 / 255.0, blue: 204.0 / 255.0, alpha: 1.0) + let second = UIColor(red: 85.0 / 255.0, green: 102.0 / 255.0, blue: 119.0 / 255.0, alpha: 136.0 / 255.0) + + do { + let theme = try Theme(yamlString: string) + + XCTAssertEqual(second, theme.colorForType(.LoginBackground)) + XCTAssertEqual(first, theme.colorForType(.LoginGradient)) + XCTAssertEqual(second, theme.colorForType(.LoginToxLogo)) + XCTAssertEqual(first, theme.colorForType(.LoginButtonText)) + XCTAssertEqual(second, theme.colorForType(.LoginButtonBackground)) + XCTAssertEqual(first, theme.colorForType(.LoginDescriptionLabel)) + XCTAssertEqual(second, theme.colorForType(.LoginFormBackground)) + XCTAssertEqual(first, theme.colorForType(.LoginFormText)) + XCTAssertEqual(second, theme.colorForType(.LoginLinkColor)) + + XCTAssertEqual(first, theme.colorForType(.TranslucentBackground)) + + XCTAssertEqual(second, theme.colorForType(.NormalBackground)) + XCTAssertEqual(first, theme.colorForType(.NormalText)) + XCTAssertEqual(second, theme.colorForType(.LinkText)) + XCTAssertEqual(first, theme.colorForType(.ConnectingBackground)) + XCTAssertEqual(second, theme.colorForType(.ConnectingText)) + XCTAssertEqual(first, theme.colorForType(.SeparatorsAndBorders)) + XCTAssertEqual(second, theme.colorForType(.OfflineStatus)) + XCTAssertEqual(first, theme.colorForType(.OnlineStatus)) + XCTAssertEqual(second, theme.colorForType(.AwayStatus)) + XCTAssertEqual(first, theme.colorForType(.BusyStatus)) + XCTAssertEqual(second, theme.colorForType(.StatusBackground)) + XCTAssertEqual(first, theme.colorForType(.FriendCellStatus)) + XCTAssertEqual(second, theme.colorForType(.ChatListCellMessage)) + XCTAssertEqual(first, theme.colorForType(.ChatListCellUnreadBackground)) + XCTAssertEqual(second, theme.colorForType(.ChatInputBackground)) + XCTAssertEqual(first, theme.colorForType(.ChatIncomingBubble)) + XCTAssertEqual(second, theme.colorForType(.ChatOutgoingBubble)) + XCTAssertEqual(second, theme.colorForType(.ChatInformationText)) + XCTAssertEqual(first, theme.colorForType(.TabBadgeBackground)) + XCTAssertEqual(second, theme.colorForType(.TabBadgeText)) + XCTAssertEqual(first, theme.colorForType(.TabItemActive)) + XCTAssertEqual(second, theme.colorForType(.TabItemInactive)) + XCTAssertEqual(first, theme.colorForType(.NotificationBackground)) + XCTAssertEqual(second, theme.colorForType(.NotificationText)) + XCTAssertEqual(first, theme.colorForType(.SettingsBackground)) + XCTAssertEqual(second, theme.colorForType(.CallTextColor)) + XCTAssertEqual(first, theme.colorForType(.CallDeclineButtonBackground)) + XCTAssertEqual(second, theme.colorForType(.CallAnswerButtonBackground)) + XCTAssertEqual(first, theme.colorForType(.CallControlBackground)) + XCTAssertEqual(second, theme.colorForType(.CallControlSelectedBackground)) + XCTAssertEqual(first, theme.colorForType(.CallButtonIconColor)) + XCTAssertEqual(second, theme.colorForType(.CallButtonSelectedIconColor)) + XCTAssertEqual(first, theme.colorForType(.CallVideoPreviewBackground)) + XCTAssertEqual(second, theme.colorForType(.RoundedButtonText)) + XCTAssertEqual(first, theme.colorForType(.RoundedPositiveButtonBackground)) + XCTAssertEqual(second, theme.colorForType(.RoundedNegativeButtonBackground)) + XCTAssertEqual(first, theme.colorForType(.EmptyScreenPlaceholderText)) + XCTAssertEqual(second, theme.colorForType(.FileImageBackgroundActive)) + XCTAssertEqual(first, theme.colorForType(.FileImageCancelledText)) + XCTAssertEqual(second, theme.colorForType(.FileImageAcceptButtonTint)) + XCTAssertEqual(first, theme.colorForType(.FileImageCancelButtonTint)) + XCTAssertEqual(second, theme.colorForType(.LockGradientTop)) + XCTAssertEqual(first, theme.colorForType(.LockGradientBottom)) + } + catch let error as ErrorTheme { + XCTFail(error.debugDescription()) + } + catch { + XCTFail("Theme init failed for unknown reason") + } + } + + func testVersionToHight() { + let string = + "version: 2\n" + + "colors:\n" + + " first: \"AABBCC\"\n" + + "values:\n" + + " login-background: first\n" + + var didThrow = false + + do { + let _ = try Theme(yamlString: string) + } + catch ErrorTheme.wrongVersion(let description) { + didThrow = description == String(localized: "theme_error_cannot_open") + } + catch { + didThrow = false + } + + XCTAssertTrue(didThrow) + } + + func testVersionToLow() { + let string = + "version: 0\n" + + "colors:\n" + + " first: \"AABBCC\"\n" + + "values:\n" + + " login-background: first\n" + + var didThrow = false + + do { + let _ = try Theme(yamlString: string) + } + catch ErrorTheme.wrongVersion(let description) { + didThrow = description == String(localized: "theme_error_cannot_open") + } + catch { + didThrow = false + } + + XCTAssertTrue(didThrow) + } +} diff --git a/AntidoteTests/icon.png b/AntidoteTests/icon.png new file mode 100644 index 0000000..0f93d2f Binary files /dev/null and b/AntidoteTests/icon.png differ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f907457 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,365 @@ +# Changelog + +## [1.4.25](https://github.com/Zoxcore/Antidote/tree/1.4.25) (2023-03-23) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.24...1.4.25) + +**Implemented enhancements:** + +- TOR inside [\#140](https://github.com/Zoxcore/Antidote/issues/140) +- warning: 'sendSynchronousRequest\(\_:returning:\)' was deprecated in iOS 9.0: Use \[NSURLSession dataTaskWithRequest:completionHandler:\] [\#95](https://github.com/Zoxcore/Antidote/issues/95) +- warning: 'fetchAssets\(withALAssetURLs:options:\)' was deprecated in iOS 11: Will be removed in a future release [\#94](https://github.com/Zoxcore/Antidote/issues/94) +- warning: 'UIImagePickerControllerReferenceURL' was deprecated in iOS 11.0: Will be removed in a future release, use PHPicker. [\#93](https://github.com/Zoxcore/Antidote/issues/93) +- 'UILocalNotification' was deprecated in iOS 10.0: Use UserNotifications Framework's UNNotificationRequest [\#92](https://github.com/Zoxcore/Antidote/issues/92) +- Realm: RLM\_ARRAY\_TYPE has been deprecated. Use RLM\_COLLECTION\_TYPE instead [\#91](https://github.com/Zoxcore/Antidote/issues/91) +- add greek to available languages [\#162](https://github.com/Zoxcore/Antidote/pull/162) ([zoff99](https://github.com/zoff99)) +- bootstrap updates [\#159](https://github.com/Zoxcore/Antidote/pull/159) ([zoff99](https://github.com/zoff99)) +- Translations update from Hosted Weblate [\#156](https://github.com/Zoxcore/Antidote/pull/156) ([weblate](https://github.com/weblate)) +- Translations update from Hosted Weblate [\#155](https://github.com/Zoxcore/Antidote/pull/155) ([weblate](https://github.com/weblate)) +- feat: add stale bot [\#154](https://github.com/Zoxcore/Antidote/pull/154) ([Tha14](https://github.com/Tha14)) +- Translations update from Hosted Weblate [\#153](https://github.com/Zoxcore/Antidote/pull/153) ([weblate](https://github.com/weblate)) +- Translations update from Hosted Weblate [\#152](https://github.com/Zoxcore/Antidote/pull/152) ([weblate](https://github.com/weblate)) + +**Fixed bugs:** + +- Copy-pasting new contact toxid doesn't enable Send button [\#149](https://github.com/Zoxcore/Antidote/issues/149) +- try to fix pasting ToxID and activating the button [\#151](https://github.com/Zoxcore/Antidote/pull/151) ([zoff99](https://github.com/zoff99)) + +**Merged pull requests:** + +- Translations update from Hosted Weblate [\#160](https://github.com/Zoxcore/Antidote/pull/160) ([weblate](https://github.com/weblate)) +- Translations update from Hosted Weblate [\#158](https://github.com/Zoxcore/Antidote/pull/158) ([weblate](https://github.com/weblate)) +- Translations update from Hosted Weblate [\#150](https://github.com/Zoxcore/Antidote/pull/150) ([weblate](https://github.com/weblate)) +- Translations update from Hosted Weblate [\#147](https://github.com/Zoxcore/Antidote/pull/147) ([weblate](https://github.com/weblate)) + +## [1.4.24](https://github.com/Zoxcore/Antidote/tree/1.4.24) (2022-11-26) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.23...1.4.24) + +**Implemented enhancements:** + +- Fix status indicators to show user status by default and connections status when debug mode is on. [\#146](https://github.com/Zoxcore/Antidote/pull/146) ([Tha14](https://github.com/Tha14)) + +**Fixed bugs:** + +- Language italian [\#141](https://github.com/Zoxcore/Antidote/issues/141) + +**Merged pull requests:** + +- add missing translation languages [\#143](https://github.com/Zoxcore/Antidote/pull/143) ([zoff99](https://github.com/zoff99)) +- Translations update from Hosted Weblate [\#139](https://github.com/Zoxcore/Antidote/pull/139) ([weblate](https://github.com/weblate)) + +## [1.4.23](https://github.com/Zoxcore/Antidote/tree/1.4.23) (2022-10-09) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.22...1.4.23) + +**Merged pull requests:** + +- whitelist ntfy.sh server for push [\#138](https://github.com/Zoxcore/Antidote/pull/138) ([zoff99](https://github.com/zoff99)) +- Translations update from Hosted Weblate [\#137](https://github.com/Zoxcore/Antidote/pull/137) ([weblate](https://github.com/weblate)) +- Translations update from Hosted Weblate [\#136](https://github.com/Zoxcore/Antidote/pull/136) ([weblate](https://github.com/weblate)) + +## [1.4.22](https://github.com/Zoxcore/Antidote/tree/1.4.22) (2022-09-25) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.21...1.4.22) + +**Merged pull requests:** + +- Revert autodownload change [\#135](https://github.com/Zoxcore/Antidote/pull/135) ([Tha14](https://github.com/Tha14)) +- Translations update from Hosted Weblate [\#134](https://github.com/Zoxcore/Antidote/pull/134) ([weblate](https://github.com/weblate)) +- some small fixes [\#133](https://github.com/Zoxcore/Antidote/pull/133) ([zoff99](https://github.com/zoff99)) +- Translations update from Hosted Weblate [\#132](https://github.com/Zoxcore/Antidote/pull/132) ([weblate](https://github.com/weblate)) +- Translations update from Hosted Weblate [\#131](https://github.com/Zoxcore/Antidote/pull/131) ([weblate](https://github.com/weblate)) +- increase size for autodownload, change autodownload from images to all files, change default to "always". [\#130](https://github.com/Zoxcore/Antidote/pull/130) ([zoff99](https://github.com/zoff99)) +- get and show friend capabilities. [\#129](https://github.com/Zoxcore/Antidote/pull/129) ([zoff99](https://github.com/zoff99)) +- Translations update from Hosted Weblate [\#128](https://github.com/Zoxcore/Antidote/pull/128) ([weblate](https://github.com/weblate)) +- show own capabilites on profile [\#127](https://github.com/Zoxcore/Antidote/pull/127) ([zoff99](https://github.com/zoff99)) +- upgrade to Realm 10.29.0 [\#126](https://github.com/Zoxcore/Antidote/pull/126) ([zoff99](https://github.com/zoff99)) +- capabilites addon [\#125](https://github.com/Zoxcore/Antidote/pull/125) ([zoff99](https://github.com/zoff99)) + +## [1.4.21](https://github.com/Zoxcore/Antidote/tree/1.4.21) (2022-07-01) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.20...1.4.21) + +**Fixed bugs:** + +- Can't export/import profile: Cannot decrypt tox save file [\#122](https://github.com/Zoxcore/Antidote/issues/122) + +**Merged pull requests:** + +- update toxcore 0.2.18 [\#124](https://github.com/Zoxcore/Antidote/pull/124) ([zoff99](https://github.com/zoff99)) + +## [1.4.20](https://github.com/Zoxcore/Antidote/tree/1.4.20) (2022-06-14) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.19...1.4.20) + +**Fixed bugs:** + +- App crashes when calling with a Bluetooth headset [\#100](https://github.com/Zoxcore/Antidote/issues/100) + +**Merged pull requests:** + +- fix bluetooth headset crash [\#121](https://github.com/Zoxcore/Antidote/pull/121) ([zoff99](https://github.com/zoff99)) + +## [1.4.19](https://github.com/Zoxcore/Antidote/tree/1.4.19) (2022-06-04) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.18...1.4.19) + +**Merged pull requests:** + +- location icon change when bg location sharing is activated [\#120](https://github.com/Zoxcore/Antidote/pull/120) ([zoff99](https://github.com/zoff99)) + +## [1.4.18](https://github.com/Zoxcore/Antidote/tree/1.4.18) (2022-05-20) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.17...1.4.18) + +**Merged pull requests:** + +- Location sharing button toggle [\#119](https://github.com/Zoxcore/Antidote/pull/119) ([zoff99](https://github.com/zoff99)) +- Translations update from Hosted Weblate [\#118](https://github.com/Zoxcore/Antidote/pull/118) ([weblate](https://github.com/weblate)) +- update to SnapKit 5.6.0 [\#114](https://github.com/Zoxcore/Antidote/pull/114) ([zoff99](https://github.com/zoff99)) +- update to Realm 10.24.2 [\#113](https://github.com/Zoxcore/Antidote/pull/113) ([zoff99](https://github.com/zoff99)) + +## [1.4.17](https://github.com/Zoxcore/Antidote/tree/1.4.17) (2022-04-18) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.16...1.4.17) + +**Merged pull requests:** + +- location share [\#117](https://github.com/Zoxcore/Antidote/pull/117) ([zoff99](https://github.com/zoff99)) + +## [1.4.16](https://github.com/Zoxcore/Antidote/tree/1.4.16) (2022-04-17) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.15...1.4.16) + +**Merged pull requests:** + +- location tweaks [\#115](https://github.com/Zoxcore/Antidote/pull/115) ([zoff99](https://github.com/zoff99)) + +## [1.4.15](https://github.com/Zoxcore/Antidote/tree/1.4.15) (2022-04-16) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.14...1.4.15) + +**Merged pull requests:** + +- adjust font size in textchatbubbles accoring to system settings for accessability. [\#112](https://github.com/Zoxcore/Antidote/pull/112) ([zoff99](https://github.com/zoff99)) +- update to toxcore 0.2.17 and consolidate pods [\#110](https://github.com/Zoxcore/Antidote/pull/110) ([zoff99](https://github.com/zoff99)) + +## [1.4.14](https://github.com/Zoxcore/Antidote/tree/1.4.14) (2022-03-15) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.13...1.4.14) + +**Merged pull requests:** + +- use location to send messages in the background [\#108](https://github.com/Zoxcore/Antidote/pull/108) ([zoff99](https://github.com/zoff99)) + +## [1.4.13](https://github.com/Zoxcore/Antidote/tree/1.4.13) (2022-03-11) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.12...1.4.13) + +**Merged pull requests:** + +- consolidated updates [\#107](https://github.com/Zoxcore/Antidote/pull/107) ([zoff99](https://github.com/zoff99)) + +## [1.4.12](https://github.com/Zoxcore/Antidote/tree/1.4.12) (2022-02-22) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.11...1.4.12) + +**Merged pull requests:** + +- revert some audio stuff that may disable audio in some setups [\#103](https://github.com/Zoxcore/Antidote/pull/103) ([zoff99](https://github.com/zoff99)) +- boost friend finding with the onion after being offline for a while [\#102](https://github.com/Zoxcore/Antidote/pull/102) ([zoff99](https://github.com/zoff99)) +- log also to system console. [\#101](https://github.com/Zoxcore/Antidote/pull/101) ([zoff99](https://github.com/zoff99)) +- use callkit for incoming calls. [\#99](https://github.com/Zoxcore/Antidote/pull/99) ([zoff99](https://github.com/zoff99)) +- fix code to make analyzer happy. [\#98](https://github.com/Zoxcore/Antidote/pull/98) ([zoff99](https://github.com/zoff99)) + +## [1.4.11](https://github.com/Zoxcore/Antidote/tree/1.4.11) (2022-02-11) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.10...1.4.11) + +**Implemented enhancements:** + +- implement periodic background fetch [\#56](https://github.com/Zoxcore/Antidote/issues/56) +- Turn screen off using the proximity sensor when in a call [\#40](https://github.com/Zoxcore/Antidote/issues/40) + +**Merged pull requests:** + +- use ProximitySensor in Callscreen to blank screen when near to the ear. [\#90](https://github.com/Zoxcore/Antidote/pull/90) ([zoff99](https://github.com/zoff99)) +- 006 reduce audio codec bitrate from 48 to 24 kbits/sec. [\#89](https://github.com/Zoxcore/Antidote/pull/89) ([zoff99](https://github.com/zoff99)) +- 005 allow Bluetooth headsets and use mono audio [\#88](https://github.com/Zoxcore/Antidote/pull/88) ([zoff99](https://github.com/zoff99)) +- 004 tweak the mute mic icon to make it clear when mic is actually muted [\#87](https://github.com/Zoxcore/Antidote/pull/87) ([zoff99](https://github.com/zoff99)) +- Translations update from Hosted Weblate [\#86](https://github.com/Zoxcore/Antidote/pull/86) ([weblate](https://github.com/weblate)) +- 003 update dependencies [\#85](https://github.com/Zoxcore/Antidote/pull/85) ([zoff99](https://github.com/zoff99)) +- 002 do not call bootstrap functions in a background thread to avoid a crash. [\#84](https://github.com/Zoxcore/Antidote/pull/84) ([zoff99](https://github.com/zoff99)) +- 001 implement periodic background fetch [\#83](https://github.com/Zoxcore/Antidote/pull/83) ([zoff99](https://github.com/zoff99)) +- Translations update from Hosted Weblate [\#82](https://github.com/Zoxcore/Antidote/pull/82) ([weblate](https://github.com/weblate)) + +## [1.4.10](https://github.com/Zoxcore/Antidote/tree/1.4.10) (2022-01-23) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.9...1.4.10) + +**Merged pull requests:** + +- fix push extension version [\#81](https://github.com/Zoxcore/Antidote/pull/81) ([zoff99](https://github.com/zoff99)) + +## [1.4.9](https://github.com/Zoxcore/Antidote/tree/1.4.9) (2022-01-22) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.8...1.4.9) + +**Implemented enhancements:** + +- The "my Push URL:" part should be on it's own tile [\#74](https://github.com/Zoxcore/Antidote/issues/74) +- enable multiline textfield to show more lines and limit to 1000 characters maximum input. [\#69](https://github.com/Zoxcore/Antidote/pull/69) ([zoff99](https://github.com/zoff99)) + +**Merged pull requests:** + +- add push service extension [\#80](https://github.com/Zoxcore/Antidote/pull/80) ([zoff99](https://github.com/zoff99)) +- push url on its own block. [\#79](https://github.com/Zoxcore/Antidote/pull/79) ([zoff99](https://github.com/zoff99)) +- update objcTox [\#78](https://github.com/Zoxcore/Antidote/pull/78) ([zoff99](https://github.com/zoff99)) +- specify that we are using version 3.4.4 of Yaml [\#77](https://github.com/Zoxcore/Antidote/pull/77) ([zoff99](https://github.com/zoff99)) +- upgrade to SnapKit 5.0.1 [\#76](https://github.com/Zoxcore/Antidote/pull/76) ([zoff99](https://github.com/zoff99)) +- Translations update from Hosted Weblate [\#75](https://github.com/Zoxcore/Antidote/pull/75) ([weblate](https://github.com/weblate)) + +## [1.4.8](https://github.com/Zoxcore/Antidote/tree/1.4.8) (2022-01-11) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.7...1.4.8) + +**Implemented enhancements:** + +- add debug setting and show meta data of chatmessages [\#68](https://github.com/Zoxcore/Antidote/pull/68) ([zoff99](https://github.com/zoff99)) + +**Merged pull requests:** + +- Translations update from Hosted Weblate [\#73](https://github.com/Zoxcore/Antidote/pull/73) ([weblate](https://github.com/weblate)) +- fix tabview at bottom overlapping virtual home button. [\#72](https://github.com/Zoxcore/Antidote/pull/72) ([zoff99](https://github.com/zoff99)) +- Translations update from Hosted Weblate [\#71](https://github.com/Zoxcore/Antidote/pull/71) ([weblate](https://github.com/weblate)) +- v1.4.8 [\#70](https://github.com/Zoxcore/Antidote/pull/70) ([zoff99](https://github.com/zoff99)) +- tweak APN push notification processing [\#67](https://github.com/Zoxcore/Antidote/pull/67) ([zoff99](https://github.com/zoff99)) +- increase the BackgroundFetch time to 27 seconds. [\#66](https://github.com/Zoxcore/Antidote/pull/66) ([zoff99](https://github.com/zoff99)) +- Translations update from Hosted Weblate [\#65](https://github.com/Zoxcore/Antidote/pull/65) ([weblate](https://github.com/weblate)) + +## [1.4.7](https://github.com/Zoxcore/Antidote/tree/1.4.7) (2021-12-26) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.6...1.4.7) + +**Implemented enhancements:** + +- red background for arrow on Chatlist looks weired on unread messages [\#55](https://github.com/Zoxcore/Antidote/issues/55) + +**Fixed bugs:** + +- text input field at bottom too low, top and iphone notch collide [\#54](https://github.com/Zoxcore/Antidote/issues/54) + +**Merged pull requests:** + +- Translations update from Hosted Weblate [\#64](https://github.com/Zoxcore/Antidote/pull/64) ([weblate](https://github.com/weblate)) +- new release 147 [\#63](https://github.com/Zoxcore/Antidote/pull/63) ([zoff99](https://github.com/zoff99)) +- update objcTox to msgV3 and useragent fix [\#62](https://github.com/Zoxcore/Antidote/pull/62) ([zoff99](https://github.com/zoff99)) +- update FAQ with new features [\#61](https://github.com/Zoxcore/Antidote/pull/61) ([zoff99](https://github.com/zoff99)) +- fix: text input field at bottom too low and make the arrow image view a nice circle shape [\#60](https://github.com/Zoxcore/Antidote/pull/60) ([zoff99](https://github.com/zoff99)) + +## [1.4.6](https://github.com/Zoxcore/Antidote/tree/1.4.6) (2021-12-19) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.5...1.4.6) + +**Merged pull requests:** + +- use dummy message instead of ignoring invalid UTF-8 [\#59](https://github.com/Zoxcore/Antidote/pull/59) ([zoff99](https://github.com/zoff99)) +- prevent crash on invalid UTF-8 incoming message, ready for msgV3 [\#58](https://github.com/Zoxcore/Antidote/pull/58) ([zoff99](https://github.com/zoff99)) + +## [1.4.5](https://github.com/Zoxcore/Antidote/tree/1.4.5) (2021-12-17) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.4...1.4.5) + +**Merged pull requests:** + +- fastlane [\#57](https://github.com/Zoxcore/Antidote/pull/57) ([zoff99](https://github.com/zoff99)) +- fix FCM background fetch [\#53](https://github.com/Zoxcore/Antidote/pull/53) ([zoff99](https://github.com/zoff99)) +- toxcore 0.2.13 [\#52](https://github.com/Zoxcore/Antidote/pull/52) ([zoff99](https://github.com/zoff99)) +- use screen better [\#51](https://github.com/Zoxcore/Antidote/pull/51) ([zoff99](https://github.com/zoff99)) + +## [1.4.4](https://github.com/Zoxcore/Antidote/tree/1.4.4) (2021-12-08) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.2...1.4.4) + +**Fixed bugs:** + +- White Text on White Background while typing a new message [\#14](https://github.com/Zoxcore/Antidote/issues/14) + +**Merged pull requests:** + +- v1.4.4 [\#50](https://github.com/Zoxcore/Antidote/pull/50) ([zoff99](https://github.com/zoff99)) +- Prepare 1.4.4, Remove support for iPad, Add new keys for appstore connect requirements [\#49](https://github.com/Zoxcore/Antidote/pull/49) ([Tha14](https://github.com/Tha14)) +- add apn \(FCM\) push [\#47](https://github.com/Zoxcore/Antidote/pull/47) ([zoff99](https://github.com/zoff99)) +- remove userStatus setting until it works properly again [\#45](https://github.com/Zoxcore/Antidote/pull/45) ([zoff99](https://github.com/zoff99)) +- Do not Translate the status message and fix it to the new Message [\#44](https://github.com/Zoxcore/Antidote/pull/44) ([zoff99](https://github.com/zoff99)) +- Update FAQ to reflect upon push notification feature [\#43](https://github.com/Zoxcore/Antidote/pull/43) ([Tha14](https://github.com/Tha14)) +- add Appstore link [\#42](https://github.com/Zoxcore/Antidote/pull/42) ([zoff99](https://github.com/zoff99)) +- Update FAQ answers\(offline messaging specifically\) [\#39](https://github.com/Zoxcore/Antidote/pull/39) ([Tha14](https://github.com/Tha14)) +- make offline status red, the grey is somewhat hard to see [\#38](https://github.com/Zoxcore/Antidote/pull/38) ([zoff99](https://github.com/zoff99)) +- remove extra directory SwiftSupport in ipa file [\#37](https://github.com/Zoxcore/Antidote/pull/37) ([zoff99](https://github.com/zoff99)) +- override dark mode for now, until the app supports it fully [\#36](https://github.com/Zoxcore/Antidote/pull/36) ([zoff99](https://github.com/zoff99)) +- make green and yellow dots better visible to the eye [\#35](https://github.com/Zoxcore/Antidote/pull/35) ([zoff99](https://github.com/zoff99)) +- update to release [\#34](https://github.com/Zoxcore/Antidote/pull/34) ([zoff99](https://github.com/zoff99)) + +## [1.4.2](https://github.com/Zoxcore/Antidote/tree/1.4.2) (2021-11-26) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.1...1.4.2) + +**Implemented enhancements:** + +- Change notification icon app [\#16](https://github.com/Zoxcore/Antidote/issues/16) +- Change logo on login screen [\#15](https://github.com/Zoxcore/Antidote/issues/15) + +**Fixed bugs:** + +- receiving 2 text messages at the same time crashes the UI [\#30](https://github.com/Zoxcore/Antidote/issues/30) +- adding a new contact with empty message crashes [\#26](https://github.com/Zoxcore/Antidote/issues/26) +- Crash after Videocall ends [\#13](https://github.com/Zoxcore/Antidote/issues/13) + +**Merged pull requests:** + +- Translations update from Hosted Weblate [\#33](https://github.com/Zoxcore/Antidote/pull/33) ([weblate](https://github.com/weblate)) +- two messages crash [\#32](https://github.com/Zoxcore/Antidote/pull/32) ([zoff99](https://github.com/zoff99)) +- add echobot once on start [\#31](https://github.com/Zoxcore/Antidote/pull/31) ([zoff99](https://github.com/zoff99)) +- fix crash on adding a new friend with empty welcome message [\#29](https://github.com/Zoxcore/Antidote/pull/29) ([zoff99](https://github.com/zoff99)) +- fix crash on remove friend [\#28](https://github.com/Zoxcore/Antidote/pull/28) ([zoff99](https://github.com/zoff99)) +- show own connection status [\#27](https://github.com/Zoxcore/Antidote/pull/27) ([zoff99](https://github.com/zoff99)) +- delivered messages with better bg color [\#25](https://github.com/Zoxcore/Antidote/pull/25) ([zoff99](https://github.com/zoff99)) +- CI updates [\#24](https://github.com/Zoxcore/Antidote/pull/24) ([zoff99](https://github.com/zoff99)) +- typing notifications, faux offline messages, push url [\#23](https://github.com/Zoxcore/Antidote/pull/23) ([zoff99](https://github.com/zoff99)) +- fix crash on videocalls [\#22](https://github.com/Zoxcore/Antidote/pull/22) ([zoff99](https://github.com/zoff99)) +- CI updates [\#20](https://github.com/Zoxcore/Antidote/pull/20) ([zoff99](https://github.com/zoff99)) +- notification icon update [\#19](https://github.com/Zoxcore/Antidote/pull/19) ([zoff99](https://github.com/zoff99)) +- Translations update from Weblate [\#18](https://github.com/Zoxcore/Antidote/pull/18) ([weblate](https://github.com/weblate)) +- Translations update from Weblate [\#12](https://github.com/Zoxcore/Antidote/pull/12) ([weblate](https://github.com/weblate)) +- add matrix \(irc\) room [\#11](https://github.com/Zoxcore/Antidote/pull/11) ([zoff99](https://github.com/zoff99)) + +## [1.4.1](https://github.com/Zoxcore/Antidote/tree/1.4.1) (2021-11-03) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.4.0...1.4.1) + +**Merged pull requests:** + +- Remove labels causing crash [\#10](https://github.com/Zoxcore/Antidote/pull/10) ([Tha14](https://github.com/Tha14)) +- Translations update from Weblate [\#8](https://github.com/Zoxcore/Antidote/pull/8) ([weblate](https://github.com/weblate)) +- Translations update from Weblate [\#7](https://github.com/Zoxcore/Antidote/pull/7) ([weblate](https://github.com/weblate)) +- Translations update from Weblate [\#4](https://github.com/Zoxcore/Antidote/pull/4) ([weblate](https://github.com/weblate)) + +## [1.4.0](https://github.com/Zoxcore/Antidote/tree/1.4.0) (2021-11-01) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1.0.0...1.4.0) + +**Merged pull requests:** + +- Translations update from Weblate [\#3](https://github.com/Zoxcore/Antidote/pull/3) ([weblate](https://github.com/weblate)) +- Translations update from Weblate [\#2](https://github.com/Zoxcore/Antidote/pull/2) ([weblate](https://github.com/weblate)) +- Create PRIVACY\_POLICY.md [\#1](https://github.com/Zoxcore/Antidote/pull/1) ([Tha14](https://github.com/Tha14)) + +## [1.0.0](https://github.com/Zoxcore/Antidote/tree/1.0.0) (2016-10-26) + +[Full Changelog](https://github.com/Zoxcore/Antidote/compare/1cdc1eae2d42aa5ee69a14237f58ab5c044d791a...1.0.0) + + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/FAQ/en.md b/FAQ/en.md new file mode 100644 index 0000000..1420025 --- /dev/null +++ b/FAQ/en.md @@ -0,0 +1,78 @@ +# Frequently Asked Questions + +* [How do I import my profile to Antidote?](#how-do-i-import-my-profile-to-antidote) +* [How do I export my profile from Antidote?](#how-do-i-export-my-profile-from-antidote) +* [How to synchronize Tox ID between multiple devices?](#how-to-synchronize-tox-id-between-multiple-devices) +* [How do I receive push notifications in the background?](#how-do-i-receive-push-notifications-in-the-background) +* [Can I send message to offline contacts?](#can-i-send-messages-to-offline-contacts) +* [How to enable PIN and Touch ID?](#how-to-enable-pin-and-touch-id) +* [Does Antidote connect to any third party servers?](#does-antidote-connect-to-any-third-party-servers) +* [More Questions?](#more-questions) +* [Translations](#translations) + + +## How do I import my profile to Antidote? + +To import your profile to Antidote, do the following: + +1. Send the .tox file to your device using any app (Mail, Dropbox, etc.). +2. Use `Open In` menu for this file. +3. Select Antidote in a list of available apps. +4. Check the name of your new profile and press OK. + + +## How do I export my profile from Antidote? + +To export your profile from Antidote, do the following: + +1. Open `Profile` tab +2. Select `Profile Details` +3. Select `Export Profile` option. + + +## How to synchronize Tox ID between multiple devices? + +Multidevice support is being [developed](https://github.com/GrayHatter/toxcore/tree/multi-device) and is not yet complete. For now you can export your .tox profile from one device and import it to another using the guides above. +But we do NOT recommend that you use the same Tox ID on more than 1 device. + + +## How do I receive push notifications in the background? + +Antidote works in the background for only about 30 seconds, after that it will be suspended by iOS. Unfortunately, there is currently no way to extend this time. +Antidote does support push notifications. This is in a beta state, and not fully working in all situations yet. +Push Notifications are only supported currently by TRIfA (Android), Antidote (iOS) and a beta version of qTox (with Push Notification patch applied). + + +## Can I send messages to offline contacts? + +Offline messaging is now supported since version 1.4.2 + + +## How to enable PIN and Touch ID? + +You can protect your profile with PIN or Touch ID. +To do so: + +1. Open `Profile` tab +2. Select `Profile Details` +3. Turn on `PIN Enabled` switch +4. Turn on `Touch ID Enabled` switch (if available). + + +## Does Antidote connect to any third party servers? + +Antidote(exlcuding toxcore) uses the Google Firebase service and a third party server to deliver push notifications to other tox mobile users when they are offline. This makes it possible for Mobile devices to go into sleep mode and save battery and network bandwidth when there is no activity. Rest assured that the push notification does not contain any data, the request that comes from Antidote includes only the FCM token of your contact(s). No ToxID, name or message data is transfered in the process. + + +## More Questions? + +Open an Issue in this Github repository https://github.com/Zoxcore/Antidote/issues + + +## Translations + +Found any translation issues? + +You can help translate Antidote to your language. + +Learn more: https://hosted.weblate.org/engage/antidote/ diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..28bba22 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gem 'cocoapods' +gem 'fastlane' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/PRIVACY_POLICY.md b/PRIVACY_POLICY.md new file mode 100644 index 0000000..1237b50 --- /dev/null +++ b/PRIVACY_POLICY.md @@ -0,0 +1,66 @@ +Privacy Policy +============== + +Last updated: October 31, 2021 + +This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You. + + +Interpretation and Definitions +============================== + +Interpretation +-------------- + +The words of which the initial letter is capitalized have meanings defined under the following conditions. The following definitions shall have the same meaning regardless of whether they appear in singular or in plural. + +Definitions +----------- + +For the purposes of this Privacy Policy: + +* **Account** means a unique account created for You to access our Service or parts of our Service. + +* **Application** means the software program provided by the Company downloaded by You on any electronic device, named Antidote - Tox Client + +* **Company** (referred to as either "the Company", "We", "Us" or "Our" in this Agreement) refers to Antidote - Tox Client. + +* **Country** refers to: Greece + +* **Device** means any device that can access the Service such as a computer, a cellphone or a digital tablet. + +* **Personal Data** is any information that relates to an identified or identifiable individual. + +* **Service** refers to the Application. + +* **Service Provider** means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used. + +* **Usage Data** refers to data collected automatically, either generated by the use of the Service or from the Service infrastructure itself (for example, the duration of a page visit). + +* **You** means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable. + + +Collecting and Using Your Personal Data +======================================= + +Types of Data Collected +----------------------- + +### Personal Data + +While using Our Service, We will never ask you to provide Us with any personally identifiable information that can be used to contact or identify You. + + +Changes to this Privacy Policy +============================== + +We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new Privacy Policy on this page. + +You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page. + +Contact Us +========== + +If you have any questions about this Privacy Policy, You can contact us: + +* By visiting this page on our website: [https://github.com/Zoxcore/Antidote/](https://github.com/Zoxcore/Antidote/) diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..607eb9e --- /dev/null +++ b/Podfile @@ -0,0 +1,32 @@ +source 'https://github.com/CocoaPods/Specs.git' + +platform :ios, '11.0' + +# ignore all warnings from all pods +inhibit_all_warnings! + +def common_pods + pod 'objcTox', :path => 'local_pod_repo/objcTox/' + pod "cmp", :path => 'local_pod_repo/cmp' + pod "toxcore", :path => 'local_pod_repo/toxcore' + pod 'UITextView+Placeholder', '~> 1.1.0' + pod 'SDCAlertView', '~> 2.5.4' + pod 'LNNotificationsUI', :git => 'https://github.com/LeoNatan/LNNotificationsUI.git', :commit => '3f75043fc6e77b4180b76cb6cfff4faa506ab9fc' + pod 'JGProgressHUD', '~> 1.4.0' + pod 'SnapKit', '~> 5.6.0' + pod 'Yaml', '~> 3.4.4' + pod 'Firebase/Messaging', '~> 8.15.0' +end + +target :Antidote do + common_pods +end + +target :AntidoteTests do + common_pods + pod 'FBSnapshotTestCase/Core' +end + +target :ScreenshotsUITests do + common_pods +end diff --git a/README.md b/README.md new file mode 100644 index 0000000..a988744 --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +# Reboot of the Tox Messenger for the IPhone + +   + +[Tox](https://tox.chat/) client for iOS + +[![Release](https://img.shields.io/github/v/release/Zoxcore/Antidote.svg)](https://github.com/Zoxcore/Antidote/releases/latest/) +[![Translations](https://hosted.weblate.org/widgets/antidote/-/svg-badge.svg)](https://hosted.weblate.org/engage/antidote/) +[![License: GPL v3](https://img.shields.io/badge/License-MPL_2.0-blue.svg)](https://opensource.org/licenses/MPL-2.0) +[![Android CI](https://github.com/Zoxcore/Antidote/workflows/Nightly/badge.svg)](https://github.com/Zoxcore/Antidote/actions?query=workflow%3A%22Nightly%22) +[![Android CI](https://github.com/Zoxcore/Antidote/workflows/PullRequest/badge.svg)](https://github.com/Zoxcore/Antidote/actions?query=workflow%3A%22PullRequest%22) + + + +## FAQ + +See [FAQ](FAQ/en.md). + + +     + +## Help Translate the App in your Language + +Use Weblate: +https://hosted.weblate.org/engage/antidote/ + +## Get in touch +* Join discussion on Matrix
+ +## Usage + +#### Install from the App Store +* Antidote - Tox Client
+ + +#### Manual Installation + +Clone repo, install pods and open `Antidote.xcworkspace` file with Xcode 12+. + +``` +git clone https://github.com/Antidote-for-Tox/Antidote.git +cd Antidote +pod install +open Antidote.xcworkspace +``` + +#### Compile on the Commandline +Clone repo, install pods and install Xcode 12+ + +``` +git clone https://github.com/Antidote-for-Tox/Antidote.git +cd Antidote +pod install +env NSUnbufferedIO=YES xcodebuild -workspace ./Antidote.xcworkspace -scheme Antidote -destination 'platform=iOS Simulator,id=EAB9614F-3485-4A6D-8EFB-FC2B5EFB0243' +``` + +## Features + +See [CHANGELOG](CHANGELOG.md) for list of notable changes (unreleased, current and previous versions). + +- one to one conversations +- typing notification +- emoticons +- spell check +- reading/scanning Tox ID via QR code +- file transfer +- read receipts +- push message support (via Apple Push Notification) / Push in qTox. +- multiple profiles +- tox_save import/export +- avatars +- audio calls +- video calls + +## Send Crashreports + +https://developer.apple.com/documentation/xcode/acquiring-crash-reports-and-diagnostic-logs
please first check that the crashreport does NOT contain any private data!
send to zoff@zoxcore.org + + + +## License + +Antidote is available under Mozilla Public License Version 2.0. See the [LICENSE](LICENSE) file for more info. + +## Links + +- [icons8](http://icons8.com/) - icons used in app +- new icon https://icons8.com/icon/jQvC2IpxYkR6/key + + diff --git a/ScreenshotsUITests/Info.plist b/ScreenshotsUITests/Info.plist new file mode 100644 index 0000000..526bf85 --- /dev/null +++ b/ScreenshotsUITests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 30 + + diff --git a/ScreenshotsUITests/ScreenshotUITests-Bridging-Header.h b/ScreenshotsUITests/ScreenshotUITests-Bridging-Header.h new file mode 100644 index 0000000..447701e --- /dev/null +++ b/ScreenshotsUITests/ScreenshotUITests-Bridging-Header.h @@ -0,0 +1,46 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +//import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#undef LOG_INFO +#undef LOG_DEBUG +#import "DDLog.h" +#import "DDASLLogger.h" +#import "DDTTYLogger.h" + +#import +#import +#import +#import + +#import "ExceptionHandling.h" + +#import "FBSnapshotTestCase.h" diff --git a/ScreenshotsUITests/ScreenshotsUITests.swift b/ScreenshotsUITests/ScreenshotsUITests.swift new file mode 100644 index 0000000..9199ed6 --- /dev/null +++ b/ScreenshotsUITests/ScreenshotsUITests.swift @@ -0,0 +1,178 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import XCTest + +private struct Constants { + static let SnapshotConversation = "01_Conversation" + static let SnapshotContactList = "02_ContactList" + static let SnapshotPin = "03_Pin" +} + +class ScreenshotsUITests: XCTestCase { + override func setUp() { + super.setUp() + + let app = XCUIApplication() + app.launchArguments.append("UI_TESTING") + setupSnapshot(app) + app.launch() + } + + override func tearDown() { + super.tearDown() + } + + func testExample() { + // I have no idea why it doesn't work without delay here. + sleep(1) + + createAccount() + + switch InterfaceIdiom.current() { + case .iPhone: + iPhonePart() + case .iPad: + iPadPart() + } + } + + func createAccount() { + let app = XCUIApplication() + + XCUIDevice.shared.orientation = .portrait + + app.buttons[localizedString("create_account")].tap() + + app.textFields[localizedString("create_account_username_placeholder")].tap() + app.textFields[localizedString("create_account_username_placeholder")].typeText("user") + + app.textFields[localizedString("create_account_profile_placeholder")].tap() + app.textFields[localizedString("create_account_profile_placeholder")].typeText("iPhone") + + app.keyboards.buttons["Next"].tap() + + app.secureTextFields[localizedString("password")].tap() + app.secureTextFields[localizedString("password")].typeText("w") + + app.secureTextFields[localizedString("repeat_password")].tap() + app.secureTextFields[localizedString("repeat_password")].typeText("w") + + app.keyboards.buttons["Go"].tap() + + addUIInterruptionMonitor(withDescription: "Notifications alert") { (alert) -> Bool in + let button = alert.buttons["OK"] + if button.exists { + button.tap() + return true + } + return false + } + + } + + func iPhonePart() { + let app = XCUIApplication() + + // need to interact with the app for the handler to fire + app.tapTabBarElement(element: .Chats) + + snapshot(Constants.SnapshotContactList) + + app.tables.staticTexts[localizedString("app_store_screenshot_conversation_7")].tap() + + snapshot(Constants.SnapshotConversation) + + app.navigationBars[localizedString("chats_title")].buttons.element(boundBy: 0).tap() + + app.tapTabBarElement(element: .Profile) + + pinPart() + } + + func iPadPart() { + let app = XCUIApplication() + + app.tables.staticTexts[localizedString("app_store_screenshot_conversation_7")].tap() + + app.children(matching: .window).element(boundBy: 0).children(matching: .other).element.children(matching: .other).element(boundBy: 2).children(matching: .other).element.children(matching: .other).element.children(matching: .other).element.children(matching: .other).element(boundBy: 0).children(matching: .textView).element.tap() + + XCUIDevice.shared.orientation = .landscapeRight + + snapshot(Constants.SnapshotConversation) + + app.navigationBars["Antidote.PrimaryIpad"].children(matching: .other).element.children(matching: .button).element.tap() + + pinPart() + } + + func pinPart() { + let app = XCUIApplication() + + app.tables.staticTexts[localizedString("profile_details")].tap() + app.tables.switches[localizedString("pin_enabled")].tap() + + let button = app.buttons["1"] + button.tap() + button.tap() + button.tap() + + let delayTime = DispatchTime.now() + Double(Int64(1.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) + DispatchQueue.main.asyncAfter(deadline: delayTime) { + snapshot("03_Pin") + } + + app.buttons["7"].press(forDuration: 5.0) + } +} + +extension XCUIApplication { + enum TabBarElement { + case Contacts + case Chats + case Settings + case Profile + + var index: UInt { + switch self { + case .Contacts: + return 1 + case .Chats: + return 2 + case .Settings: + return 3 + case .Profile: + return 4 + } + } + } + + func tapTabBarElement(element: TabBarElement) { + // TODO refactor me pls + let button = children(matching: .window).element(boundBy: 0).children(matching: .other).element.children(matching: .other).element(boundBy: 1).children(matching: .other).element(boundBy: Int(element.index)).children(matching: .button).element + + button.tap() + } +} + +func localizedString(_ key: String) -> String { + class LocalizableDummy {} + + let bundle = Bundle(for: type(of: LocalizableDummy())) + + var language = deviceLanguage + + switch language { + case "en-US": + language = "en" + default: + break + } + + let path = bundle.path(forResource: language, ofType: "lproj")! + + let localizationBundle = Bundle(path: path)! + return NSLocalizedString(key, bundle:localizationBundle, comment: "") +} + diff --git a/XOLDX_fastlane/Appfile b/XOLDX_fastlane/Appfile new file mode 100644 index 0000000..d3225ad --- /dev/null +++ b/XOLDX_fastlane/Appfile @@ -0,0 +1,7 @@ +app_identifier "com.zoxcore.Antidote" # The bundle identifier of your app +apple_id "d@dvor.me" # Your Apple email address + +team_id "NHVF8NSG3J" # Developer Portal Team ID + +# you can even provide different app identifiers, Apple IDs and team names per lane: +# More information: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Appfile.md diff --git a/XOLDX_fastlane/Deliverfile b/XOLDX_fastlane/Deliverfile new file mode 100644 index 0000000..d12e742 --- /dev/null +++ b/XOLDX_fastlane/Deliverfile @@ -0,0 +1,10 @@ +###################### More Options ###################### +# If you want to have even more control, check out the documentation +# https://github.com/fastlane/fastlane/blob/master/deliver/Deliverfile.md + + +###################### Automatically generated ###################### +# Feel free to remove the following line if you use fastlane (which you should) + +app_identifier "org.zoxcore.Antidote" # The bundle identifier of your app +username "d@dvor.me" # your Apple ID user diff --git a/XOLDX_fastlane/Fastfile b/XOLDX_fastlane/Fastfile new file mode 100644 index 0000000..f685646 --- /dev/null +++ b/XOLDX_fastlane/Fastfile @@ -0,0 +1,70 @@ +fastlane_version "1.106.2" + +default_platform :ios + +platform :ios do + before_all do + end + + desc "Runs all the tests" + lane :test do + cocoapods + scan( + scheme: "Antidote", + device: "iPhone 8", + clean: true + ) + end + + desc "Create screenshots" + lane :shots do + snapshot + # frameit + end + + desc "Submit a new Beta Build to Apple TestFlight" + desc "This will also make sure the profile is up to date" + lane :beta do + changelog = prompt( + text: "Changelog: ", + multi_line_end_keyword: "END" + ) + + changelog += " + +# See full changelog at GitHub http://bit.ly/1MsDgUX + +# You can help to translate Antidote to your language. See more information here http://bit.ly/1UqDDBX" + + increment_build_number + cocoapods + cert + sigh + gym( + scheme: "Antidote", + include_symbols: true, + include_bitcode: false + ) + + testflight(changelog: changelog, + skip_submission: false, + distribute_external: true) + end + + desc "Release app to the App Store" + lane :release do + appstore( + force: false, + skip_binary_upload: true, + skip_screenshots: true, + submit_for_review: true, + automatic_release: true + ) + end + + after_all do |lane| + end + + error do |lane, exception| + end +end diff --git a/XOLDX_fastlane/README.md b/XOLDX_fastlane/README.md new file mode 100644 index 0000000..ba32cd1 --- /dev/null +++ b/XOLDX_fastlane/README.md @@ -0,0 +1,60 @@ +fastlane documentation +================ +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +``` +xcode-select --install +``` + +## Choose your installation method: + + + + + + + + + + + + + + +
Homebrew +Installer Script +Rubygems +
macOSmacOSmacOS or Linux with Ruby 2.0.0 or above
brew cask install fastlaneDownload the zip file. Then double click on the install script (or run it in a terminal window).sudo gem install fastlane -NV
+ +# Available Actions +## iOS +### ios test +``` +fastlane ios test +``` +Runs all the tests +### ios shots +``` +fastlane ios shots +``` +Create screenshots +### ios beta +``` +fastlane ios beta +``` +Submit a new Beta Build to Apple TestFlight + +This will also make sure the profile is up to date +### ios release +``` +fastlane ios release +``` +Release app to the App Store + +---- + +This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. +More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). +The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/XOLDX_fastlane/Snapfile b/XOLDX_fastlane/Snapfile new file mode 100644 index 0000000..95650cb --- /dev/null +++ b/XOLDX_fastlane/Snapfile @@ -0,0 +1,20 @@ +devices([ + "iPad Air", + "iPad Pro (12.9 inch)", + "iPhone 4s", + "iPhone 5", + "iPhone 6", + "iPhone 6 Plus" +]) + +languages([ + "en-US", + "fr", + "da", +]) + +scheme "AntidoteScreenshots" +reinstall_app true +clear_previous_screenshots true +number_of_retries 4 +stop_after_first_error true diff --git a/XOLDX_fastlane/SnapshotHelper.swift b/XOLDX_fastlane/SnapshotHelper.swift new file mode 100644 index 0000000..5ef2484 --- /dev/null +++ b/XOLDX_fastlane/SnapshotHelper.swift @@ -0,0 +1,138 @@ +// +// SnapshotHelper.swift +// Example +// +// Created by Felix Krause on 10/8/15. +// Copyright © 2015 Felix Krause. All rights reserved. +// + +import Foundation +import XCTest + +var deviceLanguage = "" +var locale = "" + +@available(*, deprecated, message: "use setupSnapshot: instead") +func setLanguage(_ app: XCUIApplication) { + setupSnapshot(app) +} + +func setupSnapshot(_ app: XCUIApplication) { + Snapshot.setupSnapshot(app) +} + +func snapshot(_ name: String, waitForLoadingIndicator: Bool = true) { + Snapshot.snapshot(name, waitForLoadingIndicator: waitForLoadingIndicator) +} + +open class Snapshot: NSObject { + + open class func setupSnapshot(_ app: XCUIApplication) { + setLanguage(app) + setLocale(app) + setLaunchArguments(app) + } + + class func setLanguage(_ app: XCUIApplication) { + guard let prefix = pathPrefix() else { + return + } + + let path = prefix.appendingPathComponent("language.txt") + + do { + let trimCharacterSet = CharacterSet.whitespacesAndNewlines + deviceLanguage = try NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue).trimmingCharacters(in: trimCharacterSet) as String + app.launchArguments += ["-AppleLanguages", "(\(deviceLanguage))"] + } catch { + print("Couldn't detect/set language...") + } + } + + class func setLocale(_ app: XCUIApplication) { + guard let prefix = pathPrefix() else { + return + } + + let path = prefix.appendingPathComponent("locale.txt") + + do { + let trimCharacterSet = CharacterSet.whitespacesAndNewlines + locale = try NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue).trimmingCharacters(in: trimCharacterSet) as String + } catch { + print("Couldn't detect/set locale...") + } + if locale.isEmpty { + locale = Locale(identifier: deviceLanguage).identifier + } + app.launchArguments += ["-AppleLocale", "\"\(locale)\""] + } + + class func setLaunchArguments(_ app: XCUIApplication) { + guard let prefix = pathPrefix() else { + return + } + + let path = prefix.appendingPathComponent("snapshot-launch_arguments.txt") + app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"] + + do { + let launchArguments = try NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue) as String + let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: []) + let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location:0, length:launchArguments.characters.count)) + let results = matches.map { result -> String in + (launchArguments as NSString).substring(with: result.range) + } + app.launchArguments += results + } catch { + print("Couldn't detect/set launch_arguments...") + } + } + + open class func snapshot(_ name: String, waitForLoadingIndicator: Bool = true) { + if waitForLoadingIndicator { + waitForLoadingIndicatorToDisappear() + } + + print("snapshot: \(name)") // more information about this, check out https://github.com/fastlane/fastlane/tree/master/snapshot#how-does-it-work + + sleep(1) // Waiting for the animation to be finished (kind of) + + #if os(tvOS) + XCUIApplication().childrenMatchingType(.Browser).count + #else + XCUIDevice.shared.orientation = .unknown + #endif + } + + class func waitForLoadingIndicatorToDisappear() { + #if os(tvOS) + return + #endif + + let query = XCUIApplication().statusBars.children(matching: .other).element(boundBy: 1).children(matching: .other) + + while (0.. NSString? { + if let path = ProcessInfo().environment["SIMULATOR_HOST_HOME"] as NSString? { + return path.appendingPathComponent("Library/Caches/tools.fastlane") as NSString? + } + print("Couldn't find Snapshot configuration files at ~/Library/Caches/tools.fastlane") + return nil + } +} + +extension XCUIElement { + var isLoadingIndicator: Bool { + return self.frame.size == CGSize(width: 10, height: 20) + } +} + +// Please don't remove the lines below +// They are used to detect outdated configuration files +// SnapshotHelperVersion [1.2] diff --git a/XOLDX_fastlane/SnapshotHelper2-3.swift b/XOLDX_fastlane/SnapshotHelper2-3.swift new file mode 100644 index 0000000..e5891ee --- /dev/null +++ b/XOLDX_fastlane/SnapshotHelper2-3.swift @@ -0,0 +1,140 @@ +// +// SnapshotHelper.swift +// Example +// +// Created by Felix Krause on 10/8/15. +// Copyright © 2015 Felix Krause. All rights reserved. +// + +// This file should be used if your UI Tests are written in Swift 2.3 + +import Foundation +import XCTest + +var deviceLanguage = "" +var locale = "" + +@available(*, deprecated, message="use setupSnapshot: instead") +func setLanguage(app: XCUIApplication) { + setupSnapshot(app) +} + +func setupSnapshot(app: XCUIApplication) { + Snapshot.setupSnapshot(app) +} + +func snapshot(name: String, waitForLoadingIndicator: Bool = true) { + Snapshot.snapshot(name, waitForLoadingIndicator: waitForLoadingIndicator) +} + +public class Snapshot: NSObject { + + public class func setupSnapshot(app: XCUIApplication) { + setLanguage(app) + setLocale(app) + setLaunchArguments(app) + } + + class func setLanguage(app: XCUIApplication) { + guard let prefix = pathPrefix() else { + return + } + + let path = prefix.stringByAppendingPathComponent("language.txt") + + do { + let trimCharacterSet = NSCharacterSet.whitespaceAndNewlineCharacterSet() + deviceLanguage = try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding).stringByTrimmingCharactersInSet(trimCharacterSet) as String + app.launchArguments += ["-AppleLanguages", "(\(deviceLanguage))"] + } catch { + print("Couldn't detect/set language...") + } + } + + class func setLocale(app: XCUIApplication) { + guard let prefix = pathPrefix() else { + return + } + + let path = prefix.stringByAppendingPathComponent("locale.txt") + + do { + let trimCharacterSet = NSCharacterSet.whitespaceAndNewlineCharacterSet() + locale = try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding).stringByTrimmingCharactersInSet(trimCharacterSet) as String + } catch { + print("Couldn't detect/set locale...") + } + if locale.isEmpty { + locale = NSLocale(localeIdentifier: deviceLanguage).localeIdentifier + } + app.launchArguments += ["-AppleLocale", "\"\(locale)\""] + } + + class func setLaunchArguments(app: XCUIApplication) { + guard let prefix = pathPrefix() else { + return + } + + let path = prefix.stringByAppendingPathComponent("snapshot-launch_arguments.txt") + app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"] + + do { + let launchArguments = try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding) as String + let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: []) + let matches = regex.matchesInString(launchArguments, options: [], range: NSRange(location:0, length:launchArguments.characters.count)) + let results = matches.map { result -> String in + (launchArguments as NSString).substringWithRange(result.range) + } + app.launchArguments += results + } catch { + print("Couldn't detect/set launch_arguments...") + } + } + + public class func snapshot(name: String, waitForLoadingIndicator: Bool = true) { + if waitForLoadingIndicator { + waitForLoadingIndicatorToDisappear() + } + + print("snapshot: \(name)") // more information about this, check out https://github.com/fastlane/fastlane/tree/master/snapshot#how-does-it-work + + sleep(1) // Waiting for the animation to be finished (kind of) + + #if os(tvOS) + XCUIApplication().childrenMatchingType(.Browser).count + #else + XCUIDevice.sharedDevice().orientation = .Unknown + #endif + } + + class func waitForLoadingIndicatorToDisappear() { + #if os(tvOS) + return + #endif + + let query = XCUIApplication().statusBars.childrenMatchingType(.Other).elementBoundByIndex(1).childrenMatchingType(.Other) + + while (0.. NSString? { + if let path = NSProcessInfo().environment["SIMULATOR_HOST_HOME"] as NSString? { + return path.stringByAppendingPathComponent("Library/Caches/tools.fastlane") + } + print("Couldn't find Snapshot configuration files at ~/Library/Caches/tools.fastlane") + return nil + } +} + +extension XCUIElement { + var isLoadingIndicator: Bool { + return self.frame.size == CGSize(width: 10, height: 20) + } +} + +// Please don't remove the lines below +// They are used to detect outdated configuration files +// SnapshotHelperVersion [1.2] diff --git a/XOLDX_fastlane/metadata/app_icon.png b/XOLDX_fastlane/metadata/app_icon.png new file mode 100644 index 0000000..c09a5ca Binary files /dev/null and b/XOLDX_fastlane/metadata/app_icon.png differ diff --git a/XOLDX_fastlane/metadata/copyright.txt b/XOLDX_fastlane/metadata/copyright.txt new file mode 100644 index 0000000..b8b826e --- /dev/null +++ b/XOLDX_fastlane/metadata/copyright.txt @@ -0,0 +1 @@ +2014 Dmytro Vorobiov diff --git a/XOLDX_fastlane/metadata/default/marketing_url.txt b/XOLDX_fastlane/metadata/default/marketing_url.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/XOLDX_fastlane/metadata/default/marketing_url.txt @@ -0,0 +1 @@ + diff --git a/XOLDX_fastlane/metadata/default/name.txt b/XOLDX_fastlane/metadata/default/name.txt new file mode 100644 index 0000000..a636b83 --- /dev/null +++ b/XOLDX_fastlane/metadata/default/name.txt @@ -0,0 +1 @@ +Antidote for Tox diff --git a/XOLDX_fastlane/metadata/default/privacy_url.txt b/XOLDX_fastlane/metadata/default/privacy_url.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/XOLDX_fastlane/metadata/default/privacy_url.txt @@ -0,0 +1 @@ + diff --git a/XOLDX_fastlane/metadata/default/release_notes.txt b/XOLDX_fastlane/metadata/default/release_notes.txt new file mode 100644 index 0000000..b9d22f4 --- /dev/null +++ b/XOLDX_fastlane/metadata/default/release_notes.txt @@ -0,0 +1,3 @@ + + +Have feedback? Contact us at feedback@antidote.im diff --git a/XOLDX_fastlane/metadata/default/support_url.txt b/XOLDX_fastlane/metadata/default/support_url.txt new file mode 100644 index 0000000..ae8713d --- /dev/null +++ b/XOLDX_fastlane/metadata/default/support_url.txt @@ -0,0 +1 @@ +https://github.com/Zoxcore/Antidote diff --git a/XOLDX_fastlane/metadata/en-US/description.txt b/XOLDX_fastlane/metadata/en-US/description.txt new file mode 100644 index 0000000..9b58910 --- /dev/null +++ b/XOLDX_fastlane/metadata/en-US/description.txt @@ -0,0 +1,29 @@ +Antidote is a free Tox client for iOS. + +Whether it's corporations or governments, digital surveillance today is widespread. Antidote is easy-to-use software that connects you with friends and family without anyone else listening in. While other services may require you to pay for features, Antidote is completely free and comes without advertising. + + +INSTANT MESSAGING: Chat instantly across the globe with Antidote's secure messages. + +VOICE: Keep in touch with friends and family using Antidote's completely free and encrypted voice calls. + +VIDEO: Catch up face to face, over Antidote's secure video calls. + +FILE SHARING: Trade files, with no artificial limits or caps. + +ENCRYPTED: Everything you do with Antidote is encrypted using open-source libraries. The only people who can see your conversations are the people you're talking with. + +DISTRIBUTED: Antidote has no central servers that can be raided, shut down, or forced to turn over data — the network is made up of its users. Say goodbye to server outages! + +OPEN SOURCE: Antidote is an open source software. You can find source code at https://github.com/Antidote-for-Tox/Antidote + + +What makes Antidote different? + +Antidote is made by the people who use it — people fed up with the existing options that spy on us, track us, censor us, and keep us from innovating. + +There are no corporate interests, and no hidden agendas. Just the simplicity and functionality that are set free when people truly want to connect. + +If you have any questions or feedback you can email us at feedback@antidote.im. + +This text is a derivative of text from https://tox.chat/, is licensed under CC BY-SA 4.0. \ No newline at end of file diff --git a/XOLDX_fastlane/metadata/en-US/keywords.txt b/XOLDX_fastlane/metadata/en-US/keywords.txt new file mode 100644 index 0000000..51eaa71 --- /dev/null +++ b/XOLDX_fastlane/metadata/en-US/keywords.txt @@ -0,0 +1 @@ +messenger,message,tox,itox,secure,private,calls,video,audio,privacy,security,p2p,encrypted,encrypt \ No newline at end of file diff --git a/XOLDX_fastlane/metadata/fr-FR/description.txt b/XOLDX_fastlane/metadata/fr-FR/description.txt new file mode 100644 index 0000000..873e452 --- /dev/null +++ b/XOLDX_fastlane/metadata/fr-FR/description.txt @@ -0,0 +1,29 @@ +Antidote est un client Tox libre et gratuit pour iOS. + +Que ce soit des sociétés ou des gouvernements, la surveillance numérique est aujourd'hui très répandue. Antidote est un logiciel facile à utiliser qui vous connecte avec vos amis et votre famille, sans que personne d'autre ne sache ce que vous dites. Tandis que les autres grosses entreprises vous demandent de payer pour des fonctionnalités, Antidote est complètement gratuit et sans pub — pour toujours ! + + +MESSAGERIE INSTANTANÉE: Discutez instantanément à travers le monde en profitant des messages sécurisés d'Antidote. + +APPELS AUDIOS: Restez en contact avec vos amis et votre famille en utilisant les appels audio chiffrés et complètement gratuits que vous propose Antidote. + +APPELS VIDÉOS: Partagez en face à face grâce aux appels sécurisés d'Antidote. + +TRANSFERTS DE FICHIERS: Envoyez des fichiers, sans aucune limite artificielle avec une sécurité accrue. + +CHIFFRÉ: Tout ce que vous faites avec Antidote est chiffré avec des librairies open-source. La seule personne qui peut voir vos conversations est celle avec qui vous échangez. + +DISTRIBUÉ: Antidote n'a pas de serveurs qui peuvent être attaqués, arrêtés ou obligés de révéler vos données privées — le réseau est composé de ses utilisateurs. Dites adieu aux pannes de serveur ! + +OPEN SOURCE: Antidote est un logiciel libre. Vous pouvez trouver le code source à l'adresse https://github.com/Antidote-for-Tox/Antidote + + +Qu'est-ce qui rend Antidote différent ? + +Antidote est conçu par les personnes qui l'utilise — des personnes qui en ont marre des solutions existantes qui nous espionnent, nous traquent, nous censurent et nous empêche d'innover. + +Il n'y a aucun intérêts commerciaux, et pas d'intentions cachées. Seulement la simplicité et la fonctionnalité pour les personnes qui souhaitent vraiment se connecter. + +Si vous avez des questions ou des avis vous pouvez nous envoyer un mail à l'adresse feedback@antidote.im + +Ce texte est dérivé du texte de https://tox.chat/ et est sous license CC BY-SA 4.0. \ No newline at end of file diff --git a/XOLDX_fastlane/metadata/fr-FR/keywords.txt b/XOLDX_fastlane/metadata/fr-FR/keywords.txt new file mode 100644 index 0000000..c285f9f --- /dev/null +++ b/XOLDX_fastlane/metadata/fr-FR/keywords.txt @@ -0,0 +1 @@ +chat,message,tox,itox,sécurisé,privé,appels,vidéo,audio,vie privée,sécurité,p2p,chiffré,chiffrer \ No newline at end of file diff --git a/XOLDX_fastlane/metadata/primary_category.txt b/XOLDX_fastlane/metadata/primary_category.txt new file mode 100644 index 0000000..a6bf282 --- /dev/null +++ b/XOLDX_fastlane/metadata/primary_category.txt @@ -0,0 +1 @@ +MZGenre.SocialNetworking diff --git a/XOLDX_fastlane/metadata/primary_first_sub_category.txt b/XOLDX_fastlane/metadata/primary_first_sub_category.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/XOLDX_fastlane/metadata/primary_first_sub_category.txt @@ -0,0 +1 @@ + diff --git a/XOLDX_fastlane/metadata/primary_second_sub_category.txt b/XOLDX_fastlane/metadata/primary_second_sub_category.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/XOLDX_fastlane/metadata/primary_second_sub_category.txt @@ -0,0 +1 @@ + diff --git a/XOLDX_fastlane/metadata/secondary_category.txt b/XOLDX_fastlane/metadata/secondary_category.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/XOLDX_fastlane/metadata/secondary_category.txt @@ -0,0 +1 @@ + diff --git a/XOLDX_fastlane/metadata/secondary_first_sub_category.txt b/XOLDX_fastlane/metadata/secondary_first_sub_category.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/XOLDX_fastlane/metadata/secondary_first_sub_category.txt @@ -0,0 +1 @@ + diff --git a/XOLDX_fastlane/metadata/secondary_second_sub_category.txt b/XOLDX_fastlane/metadata/secondary_second_sub_category.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/XOLDX_fastlane/metadata/secondary_second_sub_category.txt @@ -0,0 +1 @@ + diff --git a/docs/app001.png b/docs/app001.png new file mode 100644 index 0000000..618d3ad Binary files /dev/null and b/docs/app001.png differ diff --git a/docs/app002.png b/docs/app002.png new file mode 100644 index 0000000..6f75af4 Binary files /dev/null and b/docs/app002.png differ diff --git a/docs/app003.png b/docs/app003.png new file mode 100644 index 0000000..97f4087 Binary files /dev/null and b/docs/app003.png differ diff --git a/docs/app004.png b/docs/app004.png new file mode 100644 index 0000000..74ed5a6 Binary files /dev/null and b/docs/app004.png differ diff --git a/docs/applewatch_push.jpg b/docs/applewatch_push.jpg new file mode 100644 index 0000000..333c7e0 Binary files /dev/null and b/docs/applewatch_push.jpg differ diff --git a/docs/appstore-badge.png b/docs/appstore-badge.png new file mode 100644 index 0000000..8e91eb3 Binary files /dev/null and b/docs/appstore-badge.png differ diff --git a/docs/iphone_send_crashreports.png b/docs/iphone_send_crashreports.png new file mode 100644 index 0000000..5d60b70 Binary files /dev/null and b/docs/iphone_send_crashreports.png differ diff --git a/docs/release_checklist.md b/docs/release_checklist.md new file mode 100644 index 0000000..63bf65b --- /dev/null +++ b/docs/release_checklist.md @@ -0,0 +1,17 @@ +## Release Checklist + +- [ ] audio call qTox -> Antidote (hold to ear) +- [ ] audio call qTox -> Antidote (speaker away from ear) +- [ ] audio call qTox -> Antidote (Bluetooth Headset) + +- [ ] video call qTox -> Antidote (hold to ear) + +- [ ] audio call Trifa -> Trifa (hold to ear) +- [ ] audio call Trifa -> Antidote (speaker away from ear) +- [ ] audio call Trifa -> Antidote (Bluetooth Headset) + +- [ ] video call Trifa -> Trifa (hold to ear) + +- [ ] check for valid APN push token in Antidote own profile page + + diff --git a/fastlane/.gitignore b/fastlane/.gitignore new file mode 100644 index 0000000..156b648 --- /dev/null +++ b/fastlane/.gitignore @@ -0,0 +1,3 @@ +report.xml +README.md +test_output diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 0000000..6201947 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,7 @@ +app_identifier "com.zoxcore.Antidote" # The bundle identifier of your app +apple_id "d@dvor.me" # Your Apple email address + +team_id "Y46L589C5C" # Developer Portal Team ID + +# you can even provide different app identifiers, Apple IDs and team names per lane: +# More information: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Appfile.md diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..788d0a4 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,24 @@ + +default_platform :ios + +platform :ios do + before_all do + end + + desc "Release app to the App Store" + lane :release do + appstore( + force: false, + skip_binary_upload: true, + skip_screenshots: true, + submit_for_review: true, + automatic_release: true + ) + end + + after_all do |lane| + end + + error do |lane, exception| + end +end diff --git a/local_pod_repo/cmp/.gitignore b/local_pod_repo/cmp/.gitignore new file mode 100644 index 0000000..d2dcf7f --- /dev/null +++ b/local_pod_repo/cmp/.gitignore @@ -0,0 +1,32 @@ +# OS X +.DS_Store + +# Xcode +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +profile +*.moved-aside +DerivedData +*.hmap +*.ipa + +# Bundler +.bundle + +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control +# +# Note: if you ignore the Pods directory, make sure to uncomment +# `pod install` in .travis.yml +# +# Pods/ diff --git a/local_pod_repo/cmp/LICENSE b/local_pod_repo/cmp/LICENSE new file mode 100644 index 0000000..8adaa77 --- /dev/null +++ b/local_pod_repo/cmp/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/local_pod_repo/cmp/cmp.podspec b/local_pod_repo/cmp/cmp.podspec new file mode 100644 index 0000000..c4f117c --- /dev/null +++ b/local_pod_repo/cmp/cmp.podspec @@ -0,0 +1,30 @@ +# +# Be sure to run `pod lib lint toxcore.podspec' to ensure this is a +# valid spec and remove all comments before submitting the spec. +# +# Any lines starting with a # are optional, but encouraged +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = "cmp" + s.version = "20.0.0" + s.summary = "Cocoapods wrapper for cmp" + s.homepage = "https://github.com/camgunz/cmp" + s.license = 'MIT' + s.author = "Zoff" + s.source = { + :git => "https://github.com/camgunz/cmp.git", + :tag => s.version.to_s, + :submodules => true + } + + s.pod_target_xcconfig = { 'ENABLE_BITCODE' => 'NO', 'OTHER_LDFLAGS' => '-read_only_relocs suppress' } + + s.ios.deployment_target = '8.0' + s.requires_arc = true + + s.source_files = 'cmp/*h', 'cmp/*.c' + +end diff --git a/local_pod_repo/cmp/cmp/.gitignore b/local_pod_repo/cmp/cmp/.gitignore new file mode 100644 index 0000000..ac3c5a7 --- /dev/null +++ b/local_pod_repo/cmp/cmp/.gitignore @@ -0,0 +1,54 @@ +# Object files +*.o +*.ko +*.obj +*.elf + +# Libraries +*.lib +*.a + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Vim swap files +*.swp +*.swo + +# Coverage files +*.gcda +*.gcno +coverage/ + +# Profiling files +*.prof + +# Testing executable +test/test-cmp +cmptest +cmptest2 +cmpaddrtest +cmpmemtest +cmpnofloattest +cmpprof +cmpubtest + +# Example executables +examples/example1 +examples/example2 +example1 +example2 + +# Test data +cmp_data.dat diff --git a/local_pod_repo/cmp/cmp/CMakeLists.txt b/local_pod_repo/cmp/cmp/CMakeLists.txt new file mode 100644 index 0000000..d758bac --- /dev/null +++ b/local_pod_repo/cmp/cmp/CMakeLists.txt @@ -0,0 +1,4 @@ +# Edit following two lines to set component requirements (see docs) +set(COMPONENT_SRCS "cmp.c" ) +set(COMPONENT_ADD_INCLUDEDIRS ".") +register_component() diff --git a/local_pod_repo/cmp/cmp/CODE_OF_CONDUCT.md b/local_pod_repo/cmp/cmp/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d890561 --- /dev/null +++ b/local_pod_repo/cmp/cmp/CODE_OF_CONDUCT.md @@ -0,0 +1,50 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of +fostering an open and welcoming community, we pledge to respect all people who +contribute through reporting issues, posting feature requests, updating +documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free +experience for everyone, regardless of level of experience, gender, gender +identity and expression, sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic + addresses, without explicit permission +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to +fairly and consistently applying these principles to every aspect of managing +this project. Project maintainers who do not follow or enforce the Code of +Conduct may be permanently removed from the project team. + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting a project maintainer at charles.gunyon@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. Maintainers are +obligated to maintain confidentiality with regard to the reporter of an +incident. + + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.3.0, available at +[http://contributor-covenant.org/version/1/3/0/][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/3/0/ diff --git a/local_pod_repo/cmp/cmp/LICENSE b/local_pod_repo/cmp/cmp/LICENSE new file mode 100644 index 0000000..8adaa77 --- /dev/null +++ b/local_pod_repo/cmp/cmp/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/local_pod_repo/cmp/cmp/Makefile b/local_pod_repo/cmp/cmp/Makefile new file mode 100644 index 0000000..eb0b26e --- /dev/null +++ b/local_pod_repo/cmp/cmp/Makefile @@ -0,0 +1,123 @@ +CC ?= gcc +CLANG ?= clang + +EXTRA_CFLAGS ?= -Werror -Wall -Wextra -funsigned-char -fwrapv -Wconversion \ + -Wno-sign-conversion -Wmissing-format-attribute \ + -Wpointer-arith -Wformat-nonliteral -Winit-self \ + -Wwrite-strings -Wshadow -Wenum-compare -Wempty-body \ + -Wparentheses -Wcast-align -Wstrict-aliasing --pedantic-errors +CMPCFLAGS ?= -std=c89 -Wno-c99-extensions +TESTCFLAGS ?= -std=c99 -Wno-error=deprecated-declarations \ + -Wno-deprecated-declarations -O0 +NOFPUTESTCFLAGS ?= $(TESTCFLAGS) -DCMP_NO_FLOAT + +ADDRCFLAGS ?= -fsanitize=address +MEMCFLAGS ?= -fsanitize=memory -fno-omit-frame-pointer \ + -fno-optimize-sibling-calls +UBCFLAGS ?= -fsanitize=undefined + +.PHONY: all clean test coverage + +all: cmpunittest example1 example2 + +profile: cmpprof + @env LD_PRELOAD=/usr/lib/libprofiler.so CPUPROFILE=cmp.prof \ + CPUPROFILE_FREQUENCY=1000 ./cmpprof + @pprof --web ./cmpprof cmp.prof + +test: addrtest memtest nofloattest ubtest unittest + +testprogs: cmpaddrtest cmpmemtest cmpnofloattest cmpubtest cmpunittest + +addrtest: cmpaddrtest + @./cmpaddrtest + +memtest: cmpmemtest + @./cmpmemtest + +nofloattest: cmpnofloattest + @./cmpnofloattest + @rm -f *.gcno *.gcda *.info + +ubtest: cmpubtest + @./cmpubtest + +unittest: cmpunittest + @./cmpunittest + +cmp.o: cmp.c + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CMPCFLAGS) \ + -fprofile-arcs -ftest-coverage -g -I. -c cmp.c + +cmpprof: cmp.o + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(TESTCFLAGS) $(LDFLAGS) \ + -fprofile-arcs -I. \ + -o cmpprof cmp.o test/profile.c test/tests.c test/buf.c test/utils.c \ + -lcmocka -lprofiler + +cmpunittest: cmp.o + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(TESTCFLAGS) $(LDFLAGS) \ + -fprofile-arcs -ftest-coverage -g -I. \ + -o cmpunittest cmp.o test/test.c test/tests.c test/buf.c test/utils.c \ + -lcmocka + +cmpnofloattest: cmp.o + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(NOFPUTESTCFLAGS) $(LDFLAGS) \ + -fprofile-arcs -ftest-coverage -g -I. \ + -o cmpnofloattest cmp.o test/test.c test/tests.c test/buf.c \ + test/utils.c \ + -lcmocka + +clangcmp.o: cmp.c + $(CLANG) $(CFLAGS) $(EXTRA_CFLAGS) $(CMPCFLAGS) \ + -fprofile-arcs -ftest-coverage -g -I. -c cmp.c -o clangcmp.o + +cmpaddrtest: clangcmp.o clean + $(CLANG) $(CFLAGS) $(EXTRA_CFLAGS) $(TESTCFLAGS) $(ADDRCFLAGS) $(LDFLAGS) \ + -I. -o cmpaddrtest \ + cmp.c test/test.c test/tests.c test/buf.c test/utils.c \ + -lcmocka + +cmpmemtest: clangcmp.o clean + $(CLANG) $(CFLAGS) $(EXTRA_CFLAGS) $(TESTCFLAGS) $(MEMCFLAGS) $(LDFLAGS) \ + -I. -o cmpmemtest \ + cmp.c test/test.c test/tests.c test/buf.c test/utils.c \ + -lcmocka + +cmpubtest: clangcmp.o clean + $(CLANG) $(CFLAGS) $(EXTRA_CFLAGS) $(TESTCFLAGS) $(UBCFLAGS) $(LDFLAGS) \ + -I. -o cmpubtest \ + cmp.c test/test.c test/tests.c test/buf.c test/utils.c \ + -lcmocka + +example1: + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) --std=c89 -O3 -I. -o example1 \ + cmp.c examples/example1.c + +example2: + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) --std=c89 -O3 -I. -o example2 \ + cmp.c examples/example2.c + +coverage: + @rm -f base_coverage.info test_coverage.info total_coverage.info + @rm -rf coverage + @lcov -q -c -i -d . -o base_coverage.info + @lcov -q -c -d . -o test_coverage.info + @lcov -q -a base_coverage.info -a test_coverage.info -o total_coverage.info + @lcov -q --summary total_coverage.info + @mkdir coverage + @genhtml -q -o coverage total_coverage.info + +clean: + @rm -f cmp.prof + @rm -f cmpunittest + @rm -f cmpaddrtest + @rm -f cmpmemtest + @rm -f cmpubtest + @rm -f cmpnofloattest + @rm -f cmpprof + @rm -f example1 + @rm -f example2 + @rm -f *.o + @rm -f *.gcno *.gcda *.info + @rm -f cmp_data.dat diff --git a/local_pod_repo/cmp/cmp/README.md b/local_pod_repo/cmp/cmp/README.md new file mode 100644 index 0000000..a726ceb --- /dev/null +++ b/local_pod_repo/cmp/cmp/README.md @@ -0,0 +1,223 @@ +# CMP + +[![Build Status](https://travis-ci.org/camgunz/cmp.svg?branch=master)](https://travis-ci.org/camgunz/cmp) [![Coverage Status](https://coveralls.io/repos/github/camgunz/cmp/badge.svg?branch=develop)](https://coveralls.io/github/camgunz/cmp?branch=develop) + +CMP is a C implementation of the MessagePack serialization format. It +currently implements version 5 of the [MessagePack +Spec](http://github.com/msgpack/msgpack/blob/master/spec.md). + +CMP's goal is to be lightweight and straightforward, forcing nothing on the +programmer. + +## License + +While I'm a big believer in the GPL, I license CMP under the MIT license. + +## Example Usage + +The following examples use a file as the backend, and are modeled after the +examples included with the msgpack-c project. + +```C +#include +#include + +#include "cmp.h" + +static bool read_bytes(void *data, size_t sz, FILE *fh) { + return fread(data, sizeof(uint8_t), sz, fh) == (sz * sizeof(uint8_t)); +} + +static bool file_reader(cmp_ctx_t *ctx, void *data, size_t limit) { + return read_bytes(data, limit, (FILE *)ctx->buf); +} + +static bool file_skipper(cmp_ctx_t *ctx, size_t count) { + return fseek((FILE *)ctx->buf, count, SEEK_CUR); +} + +static size_t file_writer(cmp_ctx_t *ctx, const void *data, size_t count) { + return fwrite(data, sizeof(uint8_t), count, (FILE *)ctx->buf); +} + +static void error_and_exit(const char *msg) { + fprintf(stderr, "%s\n\n", msg); + exit(EXIT_FAILURE); +} + +int main(void) { + FILE *fh = NULL; + cmp_ctx_t cmp = {0}; + uint32_t array_size = 0; + uint32_t str_size = 0; + char hello[6] = {0}; + char message_pack[12] = {0}; + + fh = fopen("cmp_data.dat", "w+b"); + + if (fh == NULL) { + error_and_exit("Error opening data.dat"); + } + + cmp_init(&cmp, fh, file_reader, file_skipper, file_writer); + + if (!cmp_write_array(&cmp, 2)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_str(&cmp, "Hello", 5)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_str(&cmp, "MessagePack", 11)) { + error_and_exit(cmp_strerror(&cmp)); + } + + rewind(fh); + + if (!cmp_read_array(&cmp, &array_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + /* You can read the str byte size and then read str bytes... */ + + if (!cmp_read_str_size(&cmp, &str_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (str_size > (sizeof(hello) - 1)) { + error_and_exit("Packed 'hello' length too long\n"); + } + + if (!read_bytes(hello, str_size, fh)) { + error_and_exit(cmp_strerror(&cmp)); + } + + /* + * ...or you can set the maximum number of bytes to read and do it all in + * one call + */ + + str_size = sizeof(message_pack); + if (!cmp_read_str(&cmp, message_pack, &str_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + printf("Array Length: %u.\n", array_size); + printf("[\"%s\", \"%s\"]\n", hello, message_pack); + + fclose(fh); + + return EXIT_SUCCESS; +} +``` + +## Advanced Usage + +See the `examples` folder. + +## Fast, Lightweight, Flexible, and Robust + +CMP uses no internal buffers; conversions, encoding and decoding are done on +the fly. + +CMP's source and header file together are ~4k LOC. + +CMP makes no heap allocations. + +CMP uses standardized types rather than declaring its own, and it depends only +on `stdbool.h`, `stdint.h` and `string.h`. + +CMP is written using C89 (ANSI C), aside, of course, from its use of +fixed-width integer types and `bool`. + +On the other hand, CMP's test suite requires C99. + +CMP only requires the programmer supply a read function, a write function, and +an optional skip function. In this way, the programmer can use CMP on memory, +files, sockets, etc. + +CMP is portable. It uses fixed-width integer types, and checks the endianness +of the machine at runtime before swapping bytes (MessagePack is big-endian). + +CMP provides a fairly comprehensive error reporting mechanism modeled after +`errno` and `strerror`. + +CMP is thread aware; while contexts cannot be shared between threads, each +thread may use its own context freely. + +CMP is tested using the MessagePack test suite as well as a large set of custom +test cases. Its small test program is compiled with clang using `-Wall -Werror +-Wextra ...` along with several other flags, and generates no compilation +errors in either clang or GCC. + +CMP's source is written as readably as possible, using explicit, descriptive +variable names and a consistent, clear style. + +CMP's source is written to be as secure as possible. Its testing suite checks +for invalid values, and data is always treated as suspect before it passes +validation. + +CMP's API is designed to be clear, convenient and unsurprising. Strings are +null-terminated, binary data is not, error codes are clear, and so on. + +CMP provides optional backwards compatibility for use with other MessagePack +implementations that only implement version 4 of the spec. + +## Building + +There is no build system for CMP. The programmer can drop `cmp.c` and `cmp.h` +in their source tree and modify as necessary. No special compiler settings are +required to build it, and it generates no compilation errors in either clang or +gcc. + +## Versioning + +CMP's versions are single integers. I don't use semantic versioning because +I don't guarantee that any version is completely compatible with any other. In +general, semantic versioning provides a false sense of security. You should be +evaluating compatibility yourself, not relying on some stranger's versioning +convention. + +## Stability + +I only guarantee stability for versions released on +[the releases page](../../releases). While rare, both `master` and `develop` +branches may have errors or mismatched versions. + +## Backwards Compatibility + +Version 4 of the MessagePack spec has no `BIN` type, and provides no `STR8` +marker. In order to remain backwards compatible with version 4 of MessagePack, +do the following: + +Avoid these functions: + + - `cmp_write_bin` + - `cmp_write_bin_marker` + - `cmp_write_str8_marker` + - `cmp_write_str8` + - `cmp_write_bin8_marker` + - `cmp_write_bin8` + - `cmp_write_bin16_marker` + - `cmp_write_bin16` + - `cmp_write_bin32_marker` + - `cmp_write_bin32` + +Use these functions in lieu of their v5 counterparts: + + - `cmp_write_str_marker_v4` instead of `cmp_write_str_marker` + - `cmp_write_str_v4` instead of `cmp_write_str` + - `cmp_write_object_v4` instead of `cmp_write_object` + +## Disabling Floating Point Operations + +Thanks to [tdragon](https://github.com/tdragon) it's possible to disable +floating point operations in CMP by defining `CMP_NO_FLOAT`. No floating point +functionality will be included. Fair warning: this changes the ABI. + +## Setting Endianness at Compile Time + +CMP will honor `WORDS_BIGENDIAN`. If defined to `0` it will convert data +to/from little-endian format when writing/reading. If defined to `1` it won't. +If not defined, CMP will check at runtime. diff --git a/local_pod_repo/cmp/cmp/TODO.md b/local_pod_repo/cmp/cmp/TODO.md new file mode 100644 index 0000000..c079803 --- /dev/null +++ b/local_pod_repo/cmp/cmp/TODO.md @@ -0,0 +1,14 @@ +# To Do + +- Work on fixing double-copy issue + - Essentially everything is written to a `cmp_object_t` before it's written + out to the caller, which is inefficient. The reasoning for this is to not + pollute the caller's environment should an error occur, but in practice + it's probably better to say, "you can't trust the contents of output + arguments you pass to CMP if the call fails". + +- Build real docs + - Probably still just a Markdown file, but still, things have gotten complex + enough that `cmp.h` and `README.md` don't really cover it anymore. + +- Prevent users from using extended types < 0 (reserved by MessagePack) diff --git a/local_pod_repo/cmp/cmp/cmp.c b/local_pod_repo/cmp/cmp/cmp.c new file mode 100644 index 0000000..3f35eb6 --- /dev/null +++ b/local_pod_repo/cmp/cmp/cmp.c @@ -0,0 +1,3561 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include "cmp.h" + +static const uint32_t cmp_version_ = 20; +static const uint32_t cmp_mp_version_ = 5; + +enum { + POSITIVE_FIXNUM_MARKER = 0x00, + FIXMAP_MARKER = 0x80, + FIXARRAY_MARKER = 0x90, + FIXSTR_MARKER = 0xA0, + NIL_MARKER = 0xC0, + FALSE_MARKER = 0xC2, + TRUE_MARKER = 0xC3, + BIN8_MARKER = 0xC4, + BIN16_MARKER = 0xC5, + BIN32_MARKER = 0xC6, + EXT8_MARKER = 0xC7, + EXT16_MARKER = 0xC8, + EXT32_MARKER = 0xC9, + FLOAT_MARKER = 0xCA, + DOUBLE_MARKER = 0xCB, + U8_MARKER = 0xCC, + U16_MARKER = 0xCD, + U32_MARKER = 0xCE, + U64_MARKER = 0xCF, + S8_MARKER = 0xD0, + S16_MARKER = 0xD1, + S32_MARKER = 0xD2, + S64_MARKER = 0xD3, + FIXEXT1_MARKER = 0xD4, + FIXEXT2_MARKER = 0xD5, + FIXEXT4_MARKER = 0xD6, + FIXEXT8_MARKER = 0xD7, + FIXEXT16_MARKER = 0xD8, + STR8_MARKER = 0xD9, + STR16_MARKER = 0xDA, + STR32_MARKER = 0xDB, + ARRAY16_MARKER = 0xDC, + ARRAY32_MARKER = 0xDD, + MAP16_MARKER = 0xDE, + MAP32_MARKER = 0xDF, + NEGATIVE_FIXNUM_MARKER = 0xE0 +}; + +enum { + FIXARRAY_SIZE = 0xF, + FIXMAP_SIZE = 0xF, + FIXSTR_SIZE = 0x1F +}; + +enum { + ERROR_NONE, + STR_DATA_LENGTH_TOO_LONG_ERROR, + BIN_DATA_LENGTH_TOO_LONG_ERROR, + ARRAY_LENGTH_TOO_LONG_ERROR, + MAP_LENGTH_TOO_LONG_ERROR, + INPUT_VALUE_TOO_LARGE_ERROR, + FIXED_VALUE_WRITING_ERROR, + TYPE_MARKER_READING_ERROR, + TYPE_MARKER_WRITING_ERROR, + DATA_READING_ERROR, + DATA_WRITING_ERROR, + EXT_TYPE_READING_ERROR, + EXT_TYPE_WRITING_ERROR, + INVALID_TYPE_ERROR, + LENGTH_READING_ERROR, + LENGTH_WRITING_ERROR, + SKIP_DEPTH_LIMIT_EXCEEDED_ERROR, + INTERNAL_ERROR, + DISABLED_FLOATING_POINT_ERROR, + ERROR_MAX +}; + +static const char * const cmp_error_messages[ERROR_MAX + 1] = { + "No Error", + "Specified string data length is too long (> 0xFFFFFFFF)", + "Specified binary data length is too long (> 0xFFFFFFFF)", + "Specified array length is too long (> 0xFFFFFFFF)", + "Specified map length is too long (> 0xFFFFFFFF)", + "Input value is too large", + "Error writing fixed value", + "Error reading type marker", + "Error writing type marker", + "Error reading packed data", + "Error writing packed data", + "Error reading ext type", + "Error writing ext type", + "Invalid type", + "Error reading size", + "Error writing size", + "Depth limit exceeded while skipping", + "Internal error", + "Floating point operations disabled", + "Max Error" +}; + +#ifdef WORDS_BIGENDIAN +#define is_bigendian() (WORDS_BIGENDIAN) +#else +static const int32_t i_ = 1; +#define is_bigendian() ((*(const char *)&i_) == 0) +#endif + +static uint16_t be16(uint16_t x) { + char *b = (char *)&x; + + if (!is_bigendian()) { + char swap = b[0]; + b[0] = b[1]; + b[1] = swap; + } + + return x; +} + +static int16_t sbe16(int16_t x) { + return (int16_t)be16((uint16_t)x); +} + +static uint32_t be32(uint32_t x) { + char *b = (char *)&x; + + if (!is_bigendian()) { + char swap = b[0]; + b[0] = b[3]; + b[3] = swap; + + swap = b[1]; + b[1] = b[2]; + b[2] = swap; + } + + return x; +} + +static int32_t sbe32(int32_t x) { + return (int32_t)be32((uint32_t)x); +} + +static uint64_t be64(uint64_t x) { + char *b = (char *)&x; + + if (!is_bigendian()) { + char swap; + + swap = b[0]; + b[0] = b[7]; + b[7] = swap; + + swap = b[1]; + b[1] = b[6]; + b[6] = swap; + + swap = b[2]; + b[2] = b[5]; + b[5] = swap; + + swap = b[3]; + b[3] = b[4]; + b[4] = swap; + } + + return x; +} + +static int64_t sbe64(int64_t x) { + return (int64_t)be64((uint64_t)x); +} + +#ifndef CMP_NO_FLOAT +static float decode_befloat(const char *b) { + float f = 0.; + char *fb = (char *)&f; + + if (!is_bigendian()) { + fb[0] = b[3]; + fb[1] = b[2]; + fb[2] = b[1]; + fb[3] = b[0]; + } + else { + fb[0] = b[0]; + fb[1] = b[1]; + fb[2] = b[2]; + fb[3] = b[3]; + } + + return f; +} + +static double decode_bedouble(const char *b) { + double d = 0.; + char *db = (char *)&d; + + if (!is_bigendian()) { + db[0] = b[7]; + db[1] = b[6]; + db[2] = b[5]; + db[3] = b[4]; + db[4] = b[3]; + db[5] = b[2]; + db[6] = b[1]; + db[7] = b[0]; + } + else { + db[0] = b[0]; + db[1] = b[1]; + db[2] = b[2]; + db[3] = b[3]; + db[4] = b[4]; + db[5] = b[5]; + db[6] = b[6]; + db[7] = b[7]; + } + + return d; +} +#endif /* CMP_NO_FLOAT */ + +static bool read_byte(cmp_ctx_t *ctx, uint8_t *x) { + return ctx->read(ctx, x, sizeof(uint8_t)); +} + +static bool write_byte(cmp_ctx_t *ctx, uint8_t x) { + return (ctx->write(ctx, &x, sizeof(uint8_t)) == (sizeof(uint8_t))); +} + +static bool skip_bytes(cmp_ctx_t *ctx, size_t count) { + if (ctx->skip) { + return ctx->skip(ctx, count); + } + else { + uint8_t floor; + size_t i; + + for (i = 0; i < count; i++) { + if (!ctx->read(ctx, &floor, sizeof(uint8_t))) { + return false; + } + } + + return true; + } +} + +static bool read_type_marker(cmp_ctx_t *ctx, uint8_t *marker) { + if (read_byte(ctx, marker)) { + return true; + } + + ctx->error = TYPE_MARKER_READING_ERROR; + return false; +} + +static bool write_type_marker(cmp_ctx_t *ctx, uint8_t marker) { + if (write_byte(ctx, marker)) + return true; + + ctx->error = TYPE_MARKER_WRITING_ERROR; + return false; +} + +static bool write_fixed_value(cmp_ctx_t *ctx, uint8_t value) { + if (write_byte(ctx, value)) + return true; + + ctx->error = FIXED_VALUE_WRITING_ERROR; + return false; +} + +static bool type_marker_to_cmp_type(uint8_t type_marker, uint8_t *cmp_type) { + if (type_marker <= 0x7F) { + *cmp_type = CMP_TYPE_POSITIVE_FIXNUM; + return true; + } + + if (type_marker <= 0x8F) { + *cmp_type = CMP_TYPE_FIXMAP; + return true; + } + + if (type_marker <= 0x9F) { + *cmp_type = CMP_TYPE_FIXARRAY; + return true; + } + + if (type_marker <= 0xBF) { + *cmp_type = CMP_TYPE_FIXSTR; + return true; + } + + if (type_marker >= 0xE0) { + *cmp_type = CMP_TYPE_NEGATIVE_FIXNUM; + return true; + } + + switch (type_marker) { + case NIL_MARKER: + *cmp_type = CMP_TYPE_NIL; + return true; + case FALSE_MARKER: + *cmp_type = CMP_TYPE_BOOLEAN; + return true; + case TRUE_MARKER: + *cmp_type = CMP_TYPE_BOOLEAN; + return true; + case BIN8_MARKER: + *cmp_type = CMP_TYPE_BIN8; + return true; + case BIN16_MARKER: + *cmp_type = CMP_TYPE_BIN16; + return true; + case BIN32_MARKER: + *cmp_type = CMP_TYPE_BIN32; + return true; + case EXT8_MARKER: + *cmp_type = CMP_TYPE_EXT8; + return true; + case EXT16_MARKER: + *cmp_type = CMP_TYPE_EXT16; + return true; + case EXT32_MARKER: + *cmp_type = CMP_TYPE_EXT32; + return true; + case FLOAT_MARKER: + *cmp_type = CMP_TYPE_FLOAT; + return true; + case DOUBLE_MARKER: + *cmp_type = CMP_TYPE_DOUBLE; + return true; + case U8_MARKER: + *cmp_type = CMP_TYPE_UINT8; + return true; + case U16_MARKER: + *cmp_type = CMP_TYPE_UINT16; + return true; + case U32_MARKER: + *cmp_type = CMP_TYPE_UINT32; + return true; + case U64_MARKER: + *cmp_type = CMP_TYPE_UINT64; + return true; + case S8_MARKER: + *cmp_type = CMP_TYPE_SINT8; + return true; + case S16_MARKER: + *cmp_type = CMP_TYPE_SINT16; + return true; + case S32_MARKER: + *cmp_type = CMP_TYPE_SINT32; + return true; + case S64_MARKER: + *cmp_type = CMP_TYPE_SINT64; + return true; + case FIXEXT1_MARKER: + *cmp_type = CMP_TYPE_FIXEXT1; + return true; + case FIXEXT2_MARKER: + *cmp_type = CMP_TYPE_FIXEXT2; + return true; + case FIXEXT4_MARKER: + *cmp_type = CMP_TYPE_FIXEXT4; + return true; + case FIXEXT8_MARKER: + *cmp_type = CMP_TYPE_FIXEXT8; + return true; + case FIXEXT16_MARKER: + *cmp_type = CMP_TYPE_FIXEXT16; + return true; + case STR8_MARKER: + *cmp_type = CMP_TYPE_STR8; + return true; + case STR16_MARKER: + *cmp_type = CMP_TYPE_STR16; + return true; + case STR32_MARKER: + *cmp_type = CMP_TYPE_STR32; + return true; + case ARRAY16_MARKER: + *cmp_type = CMP_TYPE_ARRAY16; + return true; + case ARRAY32_MARKER: + *cmp_type = CMP_TYPE_ARRAY32; + return true; + case MAP16_MARKER: + *cmp_type = CMP_TYPE_MAP16; + return true; + case MAP32_MARKER: + *cmp_type = CMP_TYPE_MAP32; + return true; + default: + return false; + } +} + +static bool read_type_size(cmp_ctx_t *ctx, uint8_t type_marker, + uint8_t cmp_type, + uint32_t *size) { + uint8_t u8temp = 0; + uint16_t u16temp = 0; + uint32_t u32temp = 0; + + switch (cmp_type) { + case CMP_TYPE_POSITIVE_FIXNUM: + *size = 0; + return true; + case CMP_TYPE_FIXMAP: + *size = (type_marker & FIXMAP_SIZE); + return true; + case CMP_TYPE_FIXARRAY: + *size = (type_marker & FIXARRAY_SIZE); + return true; + case CMP_TYPE_FIXSTR: + *size = (type_marker & FIXSTR_SIZE); + return true; + case CMP_TYPE_NIL: + *size = 0; + return true; + case CMP_TYPE_BOOLEAN: + *size = 0; + return true; + case CMP_TYPE_BIN8: + if (!ctx->read(ctx, &u8temp, sizeof(uint8_t))) { + ctx->error = LENGTH_READING_ERROR; + return false; + } + *size = u8temp; + return true; + case CMP_TYPE_BIN16: + if (!ctx->read(ctx, &u16temp, sizeof(uint16_t))) { + ctx->error = LENGTH_READING_ERROR; + return false; + } + *size = be16(u16temp); + return true; + case CMP_TYPE_BIN32: + if (!ctx->read(ctx, &u32temp, sizeof(uint32_t))) { + ctx->error = LENGTH_READING_ERROR; + return false; + } + *size = be32(u32temp); + return true; + case CMP_TYPE_EXT8: + if (!ctx->read(ctx, &u8temp, sizeof(uint8_t))) { + ctx->error = LENGTH_READING_ERROR; + return false; + } + *size = u8temp; + return true; + case CMP_TYPE_EXT16: + if (!ctx->read(ctx, &u16temp, sizeof(uint16_t))) { + ctx->error = LENGTH_READING_ERROR; + return false; + } + *size = be16(u16temp); + return true; + case CMP_TYPE_EXT32: + if (!ctx->read(ctx, &u32temp, sizeof(uint32_t))) { + ctx->error = LENGTH_READING_ERROR; + return false; + } + *size = be32(u32temp); + return true; + case CMP_TYPE_FLOAT: + *size = 4; + return true; + case CMP_TYPE_DOUBLE: + *size = 8; + return true; + case CMP_TYPE_UINT8: + *size = 1; + return true; + case CMP_TYPE_UINT16: + *size = 2; + return true; + case CMP_TYPE_UINT32: + *size = 4; + return true; + case CMP_TYPE_UINT64: + *size = 8; + return true; + case CMP_TYPE_SINT8: + *size = 1; + return true; + case CMP_TYPE_SINT16: + *size = 2; + return true; + case CMP_TYPE_SINT32: + *size = 4; + return true; + case CMP_TYPE_SINT64: + *size = 8; + return true; + case CMP_TYPE_FIXEXT1: + *size = 1; + return true; + case CMP_TYPE_FIXEXT2: + *size = 2; + return true; + case CMP_TYPE_FIXEXT4: + *size = 4; + return true; + case CMP_TYPE_FIXEXT8: + *size = 8; + return true; + case CMP_TYPE_FIXEXT16: + *size = 16; + return true; + case CMP_TYPE_STR8: + if (!ctx->read(ctx, &u8temp, sizeof(uint8_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + *size = u8temp; + return true; + case CMP_TYPE_STR16: + if (!ctx->read(ctx, &u16temp, sizeof(uint16_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + *size = be16(u16temp); + return true; + case CMP_TYPE_STR32: + if (!ctx->read(ctx, &u32temp, sizeof(uint32_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + *size = be32(u32temp); + return true; + case CMP_TYPE_ARRAY16: + if (!ctx->read(ctx, &u16temp, sizeof(uint16_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + *size = be16(u16temp); + return true; + case CMP_TYPE_ARRAY32: + if (!ctx->read(ctx, &u32temp, sizeof(uint32_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + *size = be32(u32temp); + return true; + case CMP_TYPE_MAP16: + if (!ctx->read(ctx, &u16temp, sizeof(uint16_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + *size = be16(u16temp); + return true; + case CMP_TYPE_MAP32: + if (!ctx->read(ctx, &u32temp, sizeof(uint32_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + *size = be32(u32temp); + return true; + case CMP_TYPE_NEGATIVE_FIXNUM: + *size = 0; + return true; + default: + ctx->error = INVALID_TYPE_ERROR; + return false; + } +} + +static bool read_obj_data(cmp_ctx_t *ctx, uint8_t type_marker, + cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + obj->as.u8 = type_marker; + return true; + case CMP_TYPE_NEGATIVE_FIXNUM: + obj->as.s8 = (int8_t)type_marker; + return true; + case CMP_TYPE_NIL: + obj->as.u8 = 0; + return true; + case CMP_TYPE_BOOLEAN: + switch (type_marker) { + case TRUE_MARKER: + obj->as.boolean = true; + return true; + case FALSE_MARKER: + obj->as.boolean = false; + return true; + default: + break; + } + ctx->error = INTERNAL_ERROR; + return false; + case CMP_TYPE_UINT8: + if (!ctx->read(ctx, &obj->as.u8, sizeof(uint8_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + return true; + case CMP_TYPE_UINT16: + if (!ctx->read(ctx, &obj->as.u16, sizeof(uint16_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + obj->as.u16 = be16(obj->as.u16); + return true; + case CMP_TYPE_UINT32: + if (!ctx->read(ctx, &obj->as.u32, sizeof(uint32_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + obj->as.u32 = be32(obj->as.u32); + return true; + case CMP_TYPE_UINT64: + if (!ctx->read(ctx, &obj->as.u64, sizeof(uint64_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + obj->as.u64 = be64(obj->as.u64); + return true; + case CMP_TYPE_SINT8: + if (!ctx->read(ctx, &obj->as.s8, sizeof(int8_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + return true; + case CMP_TYPE_SINT16: + if (!ctx->read(ctx, &obj->as.s16, sizeof(int16_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + obj->as.s16 = sbe16(obj->as.s16); + return true; + case CMP_TYPE_SINT32: + if (!ctx->read(ctx, &obj->as.s32, sizeof(int32_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + obj->as.s32 = sbe32(obj->as.s32); + return true; + case CMP_TYPE_SINT64: + if (!ctx->read(ctx, &obj->as.s64, sizeof(int64_t))) { + ctx->error = DATA_READING_ERROR; + return false; + } + obj->as.s64 = sbe64(obj->as.s64); + return true; + case CMP_TYPE_FLOAT: + { +#ifndef CMP_NO_FLOAT + char bytes[4]; + + if (!ctx->read(ctx, bytes, 4)) { + ctx->error = DATA_READING_ERROR; + return false; + } + obj->as.flt = decode_befloat(bytes); + return true; +#else /* CMP_NO_FLOAT */ + ctx->error = DISABLED_FLOATING_POINT_ERROR; + return false; +#endif /* CMP_NO_FLOAT */ + } + case CMP_TYPE_DOUBLE: + { +#ifndef CMP_NO_FLOAT + char bytes[8]; + + if (!ctx->read(ctx, bytes, 8)) { + ctx->error = DATA_READING_ERROR; + return false; + } + obj->as.dbl = decode_bedouble(bytes); + return true; +#else /* CMP_NO_FLOAT */ + ctx->error = DISABLED_FLOATING_POINT_ERROR; + return false; +#endif /* CMP_NO_FLOAT */ + } + case CMP_TYPE_BIN8: + case CMP_TYPE_BIN16: + case CMP_TYPE_BIN32: + return read_type_size(ctx, type_marker, obj->type, &obj->as.bin_size); + case CMP_TYPE_FIXSTR: + case CMP_TYPE_STR8: + case CMP_TYPE_STR16: + case CMP_TYPE_STR32: + return read_type_size(ctx, type_marker, obj->type, &obj->as.str_size); + case CMP_TYPE_FIXARRAY: + case CMP_TYPE_ARRAY16: + case CMP_TYPE_ARRAY32: + return read_type_size(ctx, type_marker, obj->type, &obj->as.array_size); + case CMP_TYPE_FIXMAP: + case CMP_TYPE_MAP16: + case CMP_TYPE_MAP32: + return read_type_size(ctx, type_marker, obj->type, &obj->as.map_size); + case CMP_TYPE_FIXEXT1: + if (!ctx->read(ctx, &obj->as.ext.type, sizeof(int8_t))) { + ctx->error = EXT_TYPE_READING_ERROR; + return false; + } + obj->as.ext.size = 1; + return true; + case CMP_TYPE_FIXEXT2: + if (!ctx->read(ctx, &obj->as.ext.type, sizeof(int8_t))) { + ctx->error = EXT_TYPE_READING_ERROR; + return false; + } + obj->as.ext.size = 2; + return true; + case CMP_TYPE_FIXEXT4: + if (!ctx->read(ctx, &obj->as.ext.type, sizeof(int8_t))) { + ctx->error = EXT_TYPE_READING_ERROR; + return false; + } + obj->as.ext.size = 4; + return true; + case CMP_TYPE_FIXEXT8: + if (!ctx->read(ctx, &obj->as.ext.type, sizeof(int8_t))) { + ctx->error = EXT_TYPE_READING_ERROR; + return false; + } + obj->as.ext.size = 8; + return true; + case CMP_TYPE_FIXEXT16: + if (!ctx->read(ctx, &obj->as.ext.type, sizeof(int8_t))) { + ctx->error = EXT_TYPE_READING_ERROR; + return false; + } + obj->as.ext.size = 16; + return true; + case CMP_TYPE_EXT8: + if (!read_type_size(ctx, type_marker, obj->type, &obj->as.ext.size)) { + return false; + } + if (!ctx->read(ctx, &obj->as.ext.type, sizeof(int8_t))) { + ctx->error = EXT_TYPE_READING_ERROR; + return false; + } + return true; + case CMP_TYPE_EXT16: + if (!read_type_size(ctx, type_marker, obj->type, &obj->as.ext.size)) { + return false; + } + if (!ctx->read(ctx, &obj->as.ext.type, sizeof(int8_t))) { + ctx->error = EXT_TYPE_READING_ERROR; + return false; + } + return true; + case CMP_TYPE_EXT32: + if (!read_type_size(ctx, type_marker, obj->type, &obj->as.ext.size)) { + return false; + } + if (!ctx->read(ctx, &obj->as.ext.type, sizeof(int8_t))) { + ctx->error = EXT_TYPE_READING_ERROR; + return false; + } + return true; + default: + break; + } + + ctx->error = INVALID_TYPE_ERROR; + return false; +} + +void cmp_init(cmp_ctx_t *ctx, void *buf, cmp_reader read, + cmp_skipper skip, + cmp_writer write) { + ctx->error = ERROR_NONE; + ctx->buf = buf; + ctx->read = read; + ctx->skip = skip; + ctx->write = write; +} + +uint32_t cmp_version(void) { + return cmp_version_; +} + +uint32_t cmp_mp_version(void) { + return cmp_mp_version_; +} + +const char* cmp_strerror(cmp_ctx_t *ctx) { + if (ctx->error > ERROR_NONE && ctx->error < ERROR_MAX) + return cmp_error_messages[ctx->error]; + + return ""; +} + +bool cmp_write_pfix(cmp_ctx_t *ctx, uint8_t c) { + if (c <= 0x7F) + return write_fixed_value(ctx, c); + + ctx->error = INPUT_VALUE_TOO_LARGE_ERROR; + return false; +} + +bool cmp_write_nfix(cmp_ctx_t *ctx, int8_t c) { + if (c >= -32 && c <= -1) + return write_fixed_value(ctx, (uint8_t)c); + + ctx->error = INPUT_VALUE_TOO_LARGE_ERROR; + return false; +} + +bool cmp_write_sfix(cmp_ctx_t *ctx, int8_t c) { + if (c >= 0) + return cmp_write_pfix(ctx, (uint8_t)c); + if (c >= -32 && c <= -1) + return cmp_write_nfix(ctx, c); + + ctx->error = INPUT_VALUE_TOO_LARGE_ERROR; + return false; +} + +bool cmp_write_s8(cmp_ctx_t *ctx, int8_t c) { + if (!write_type_marker(ctx, S8_MARKER)) + return false; + + return ctx->write(ctx, &c, sizeof(int8_t)); +} + +bool cmp_write_s16(cmp_ctx_t *ctx, int16_t s) { + if (!write_type_marker(ctx, S16_MARKER)) + return false; + + s = sbe16(s); + + return ctx->write(ctx, &s, sizeof(int16_t)); +} + +bool cmp_write_s32(cmp_ctx_t *ctx, int32_t i) { + if (!write_type_marker(ctx, S32_MARKER)) + return false; + + i = sbe32(i); + + return ctx->write(ctx, &i, sizeof(int32_t)); +} + +bool cmp_write_s64(cmp_ctx_t *ctx, int64_t l) { + if (!write_type_marker(ctx, S64_MARKER)) + return false; + + l = sbe64(l); + + return ctx->write(ctx, &l, sizeof(int64_t)); +} + +bool cmp_write_integer(cmp_ctx_t *ctx, int64_t d) { + if (d >= 0) + return cmp_write_uinteger(ctx, (uint64_t)d); + if (d >= -32) + return cmp_write_nfix(ctx, (int8_t)d); + if (d >= -128) + return cmp_write_s8(ctx, (int8_t)d); + if (d >= -32768) + return cmp_write_s16(ctx, (int16_t)d); + if (d >= (-2147483647 - 1)) + return cmp_write_s32(ctx, (int32_t)d); + + return cmp_write_s64(ctx, d); +} + +bool cmp_write_ufix(cmp_ctx_t *ctx, uint8_t c) { + return cmp_write_pfix(ctx, c); +} + +bool cmp_write_u8(cmp_ctx_t *ctx, uint8_t c) { + if (!write_type_marker(ctx, U8_MARKER)) + return false; + + return ctx->write(ctx, &c, sizeof(uint8_t)); +} + +bool cmp_write_u16(cmp_ctx_t *ctx, uint16_t s) { + if (!write_type_marker(ctx, U16_MARKER)) + return false; + + s = be16(s); + + return ctx->write(ctx, &s, sizeof(uint16_t)); +} + +bool cmp_write_u32(cmp_ctx_t *ctx, uint32_t i) { + if (!write_type_marker(ctx, U32_MARKER)) + return false; + + i = be32(i); + + return ctx->write(ctx, &i, sizeof(uint32_t)); +} + +bool cmp_write_u64(cmp_ctx_t *ctx, uint64_t l) { + if (!write_type_marker(ctx, U64_MARKER)) + return false; + + l = be64(l); + + return ctx->write(ctx, &l, sizeof(uint64_t)); +} + +bool cmp_write_uinteger(cmp_ctx_t *ctx, uint64_t u) { + if (u <= 0x7F) + return cmp_write_pfix(ctx, (uint8_t)u); + if (u <= 0xFF) + return cmp_write_u8(ctx, (uint8_t)u); + if (u <= 0xFFFF) + return cmp_write_u16(ctx, (uint16_t)u); + if (u <= 0xFFFFFFFF) + return cmp_write_u32(ctx, (uint32_t)u); + + return cmp_write_u64(ctx, u); +} + +#ifndef CMP_NO_FLOAT +bool cmp_write_float(cmp_ctx_t *ctx, float f) { + if (!write_type_marker(ctx, FLOAT_MARKER)) + return false; + + /* + * We may need to swap the float's bytes, but we can't just swap them inside + * the float because the swapped bytes may not constitute a valid float. + * Therefore, we have to create a buffer and swap the bytes there. + */ + if (!is_bigendian()) { + char swapped[sizeof(float)]; + char *fbuf = (char *)&f; + size_t i; + + for (i = 0; i < sizeof(float); i++) + swapped[i] = fbuf[sizeof(float) - i - 1]; + + return ctx->write(ctx, swapped, sizeof(float)); + } + + return ctx->write(ctx, &f, sizeof(float)); +} + +bool cmp_write_double(cmp_ctx_t *ctx, double d) { + if (!write_type_marker(ctx, DOUBLE_MARKER)) + return false; + + /* Same deal for doubles */ + if (!is_bigendian()) { + char swapped[sizeof(double)]; + char *dbuf = (char *)&d; + size_t i; + + for (i = 0; i < sizeof(double); i++) + swapped[i] = dbuf[sizeof(double) - i - 1]; + + return ctx->write(ctx, swapped, sizeof(double)); + } + + return ctx->write(ctx, &d, sizeof(double)); +} + +bool cmp_write_decimal(cmp_ctx_t *ctx, double d) { + float f = (float)d; + double df = (double)f; + + if (df == d) + return cmp_write_float(ctx, f); + else + return cmp_write_double(ctx, d); +} +#endif /* CMP_NO_FLOAT */ + +bool cmp_write_nil(cmp_ctx_t *ctx) { + return write_type_marker(ctx, NIL_MARKER); +} + +bool cmp_write_true(cmp_ctx_t *ctx) { + return write_type_marker(ctx, TRUE_MARKER); +} + +bool cmp_write_false(cmp_ctx_t *ctx) { + return write_type_marker(ctx, FALSE_MARKER); +} + +bool cmp_write_bool(cmp_ctx_t *ctx, bool b) { + if (b) + return cmp_write_true(ctx); + + return cmp_write_false(ctx); +} + +bool cmp_write_u8_as_bool(cmp_ctx_t *ctx, uint8_t b) { + if (b) + return cmp_write_true(ctx); + + return cmp_write_false(ctx); +} + +bool cmp_write_fixstr_marker(cmp_ctx_t *ctx, uint8_t size) { + if (size <= FIXSTR_SIZE) + return write_fixed_value(ctx, FIXSTR_MARKER | size); + + ctx->error = INPUT_VALUE_TOO_LARGE_ERROR; + return false; +} + +bool cmp_write_fixstr(cmp_ctx_t *ctx, const char *data, uint8_t size) { + if (!cmp_write_fixstr_marker(ctx, size)) + return false; + + if (size == 0) + return true; + + if (ctx->write(ctx, data, size)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_str8_marker(cmp_ctx_t *ctx, uint8_t size) { + if (!write_type_marker(ctx, STR8_MARKER)) + return false; + + if (ctx->write(ctx, &size, sizeof(uint8_t))) + return true; + + ctx->error = LENGTH_WRITING_ERROR; + return false; +} + +bool cmp_write_str8(cmp_ctx_t *ctx, const char *data, uint8_t size) { + if (!cmp_write_str8_marker(ctx, size)) + return false; + + if (size == 0) + return true; + + if (ctx->write(ctx, data, size)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_str16_marker(cmp_ctx_t *ctx, uint16_t size) { + if (!write_type_marker(ctx, STR16_MARKER)) + return false; + + size = be16(size); + + if (ctx->write(ctx, &size, sizeof(uint16_t))) + return true; + + ctx->error = LENGTH_WRITING_ERROR; + return false; +} + +bool cmp_write_str16(cmp_ctx_t *ctx, const char *data, uint16_t size) { + if (!cmp_write_str16_marker(ctx, size)) + return false; + + if (size == 0) + return true; + + if (ctx->write(ctx, data, size)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_str32_marker(cmp_ctx_t *ctx, uint32_t size) { + if (!write_type_marker(ctx, STR32_MARKER)) + return false; + + size = be32(size); + + if (ctx->write(ctx, &size, sizeof(uint32_t))) + return true; + + ctx->error = LENGTH_WRITING_ERROR; + return false; +} + +bool cmp_write_str32(cmp_ctx_t *ctx, const char *data, uint32_t size) { + if (!cmp_write_str32_marker(ctx, size)) + return false; + + if (size == 0) + return true; + + if (ctx->write(ctx, data, size)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_str_marker(cmp_ctx_t *ctx, uint32_t size) { + if (size <= FIXSTR_SIZE) + return cmp_write_fixstr_marker(ctx, (uint8_t)size); + if (size <= 0xFF) + return cmp_write_str8_marker(ctx, (uint8_t)size); + if (size <= 0xFFFF) + return cmp_write_str16_marker(ctx, (uint16_t)size); + + return cmp_write_str32_marker(ctx, size); +} + +bool cmp_write_str_marker_v4(cmp_ctx_t *ctx, uint32_t size) { + if (size <= FIXSTR_SIZE) + return cmp_write_fixstr_marker(ctx, (uint8_t)size); + if (size <= 0xFFFF) + return cmp_write_str16_marker(ctx, (uint16_t)size); + + return cmp_write_str32_marker(ctx, size); +} + +bool cmp_write_str(cmp_ctx_t *ctx, const char *data, uint32_t size) { + if (size <= FIXSTR_SIZE) + return cmp_write_fixstr(ctx, data, (uint8_t)size); + if (size <= 0xFF) + return cmp_write_str8(ctx, data, (uint8_t)size); + if (size <= 0xFFFF) + return cmp_write_str16(ctx, data, (uint16_t)size); + + return cmp_write_str32(ctx, data, size); +} + +bool cmp_write_str_v4(cmp_ctx_t *ctx, const char *data, uint32_t size) { + if (size <= FIXSTR_SIZE) + return cmp_write_fixstr(ctx, data, (uint8_t)size); + if (size <= 0xFFFF) + return cmp_write_str16(ctx, data, (uint16_t)size); + + return cmp_write_str32(ctx, data, size); +} + +bool cmp_write_bin8_marker(cmp_ctx_t *ctx, uint8_t size) { + if (!write_type_marker(ctx, BIN8_MARKER)) + return false; + + if (ctx->write(ctx, &size, sizeof(uint8_t))) + return true; + + ctx->error = LENGTH_WRITING_ERROR; + return false; +} + +bool cmp_write_bin8(cmp_ctx_t *ctx, const void *data, uint8_t size) { + if (!cmp_write_bin8_marker(ctx, size)) + return false; + + if (size == 0) + return true; + + if (ctx->write(ctx, data, size)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_bin16_marker(cmp_ctx_t *ctx, uint16_t size) { + if (!write_type_marker(ctx, BIN16_MARKER)) + return false; + + size = be16(size); + + if (ctx->write(ctx, &size, sizeof(uint16_t))) + return true; + + ctx->error = LENGTH_WRITING_ERROR; + return false; +} + +bool cmp_write_bin16(cmp_ctx_t *ctx, const void *data, uint16_t size) { + if (!cmp_write_bin16_marker(ctx, size)) + return false; + + if (size == 0) + return true; + + if (ctx->write(ctx, data, size)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_bin32_marker(cmp_ctx_t *ctx, uint32_t size) { + if (!write_type_marker(ctx, BIN32_MARKER)) + return false; + + size = be32(size); + + if (ctx->write(ctx, &size, sizeof(uint32_t))) + return true; + + ctx->error = LENGTH_WRITING_ERROR; + return false; +} + +bool cmp_write_bin32(cmp_ctx_t *ctx, const void *data, uint32_t size) { + if (!cmp_write_bin32_marker(ctx, size)) + return false; + + if (size == 0) + return true; + + if (ctx->write(ctx, data, size)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_bin_marker(cmp_ctx_t *ctx, uint32_t size) { + if (size <= 0xFF) + return cmp_write_bin8_marker(ctx, (uint8_t)size); + if (size <= 0xFFFF) + return cmp_write_bin16_marker(ctx, (uint16_t)size); + + return cmp_write_bin32_marker(ctx, size); +} + +bool cmp_write_bin(cmp_ctx_t *ctx, const void *data, uint32_t size) { + if (size <= 0xFF) + return cmp_write_bin8(ctx, data, (uint8_t)size); + if (size <= 0xFFFF) + return cmp_write_bin16(ctx, data, (uint16_t)size); + + return cmp_write_bin32(ctx, data, size); +} + +bool cmp_write_fixarray(cmp_ctx_t *ctx, uint8_t size) { + if (size <= FIXARRAY_SIZE) + return write_fixed_value(ctx, FIXARRAY_MARKER | size); + + ctx->error = INPUT_VALUE_TOO_LARGE_ERROR; + return false; +} + +bool cmp_write_array16(cmp_ctx_t *ctx, uint16_t size) { + if (!write_type_marker(ctx, ARRAY16_MARKER)) + return false; + + size = be16(size); + + if (ctx->write(ctx, &size, sizeof(uint16_t))) + return true; + + ctx->error = LENGTH_WRITING_ERROR; + return false; +} + +bool cmp_write_array32(cmp_ctx_t *ctx, uint32_t size) { + if (!write_type_marker(ctx, ARRAY32_MARKER)) + return false; + + size = be32(size); + + if (ctx->write(ctx, &size, sizeof(uint32_t))) + return true; + + ctx->error = LENGTH_WRITING_ERROR; + return false; +} + +bool cmp_write_array(cmp_ctx_t *ctx, uint32_t size) { + if (size <= FIXARRAY_SIZE) + return cmp_write_fixarray(ctx, (uint8_t)size); + if (size <= 0xFFFF) + return cmp_write_array16(ctx, (uint16_t)size); + + return cmp_write_array32(ctx, size); +} + +bool cmp_write_fixmap(cmp_ctx_t *ctx, uint8_t size) { + if (size <= FIXMAP_SIZE) + return write_fixed_value(ctx, FIXMAP_MARKER | size); + + ctx->error = INPUT_VALUE_TOO_LARGE_ERROR; + return false; +} + +bool cmp_write_map16(cmp_ctx_t *ctx, uint16_t size) { + if (!write_type_marker(ctx, MAP16_MARKER)) + return false; + + size = be16(size); + + if (ctx->write(ctx, &size, sizeof(uint16_t))) + return true; + + ctx->error = LENGTH_WRITING_ERROR; + return false; +} + +bool cmp_write_map32(cmp_ctx_t *ctx, uint32_t size) { + if (!write_type_marker(ctx, MAP32_MARKER)) + return false; + + size = be32(size); + + if (ctx->write(ctx, &size, sizeof(uint32_t))) + return true; + + ctx->error = LENGTH_WRITING_ERROR; + return false; +} + +bool cmp_write_map(cmp_ctx_t *ctx, uint32_t size) { + if (size <= FIXMAP_SIZE) + return cmp_write_fixmap(ctx, (uint8_t)size); + if (size <= 0xFFFF) + return cmp_write_map16(ctx, (uint16_t)size); + + return cmp_write_map32(ctx, size); +} + +bool cmp_write_fixext1_marker(cmp_ctx_t *ctx, int8_t type) { + if (!write_type_marker(ctx, FIXEXT1_MARKER)) + return false; + + if (ctx->write(ctx, &type, sizeof(int8_t))) + return true; + + ctx->error = EXT_TYPE_WRITING_ERROR; + return false; +} + +bool cmp_write_fixext1(cmp_ctx_t *ctx, int8_t type, const void *data) { + if (!cmp_write_fixext1_marker(ctx, type)) + return false; + + if (ctx->write(ctx, data, 1)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_fixext2_marker(cmp_ctx_t *ctx, int8_t type) { + if (!write_type_marker(ctx, FIXEXT2_MARKER)) + return false; + + if (ctx->write(ctx, &type, sizeof(int8_t))) + return true; + + ctx->error = EXT_TYPE_WRITING_ERROR; + return false; +} + +bool cmp_write_fixext2(cmp_ctx_t *ctx, int8_t type, const void *data) { + if (!cmp_write_fixext2_marker(ctx, type)) + return false; + + if (ctx->write(ctx, data, 2)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_fixext4_marker(cmp_ctx_t *ctx, int8_t type) { + if (!write_type_marker(ctx, FIXEXT4_MARKER)) + return false; + + if (ctx->write(ctx, &type, sizeof(int8_t))) + return true; + + ctx->error = EXT_TYPE_WRITING_ERROR; + return false; +} + +bool cmp_write_fixext4(cmp_ctx_t *ctx, int8_t type, const void *data) { + if (!cmp_write_fixext4_marker(ctx, type)) + return false; + + if (ctx->write(ctx, data, 4)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_fixext8_marker(cmp_ctx_t *ctx, int8_t type) { + if (!write_type_marker(ctx, FIXEXT8_MARKER)) + return false; + + if (ctx->write(ctx, &type, sizeof(int8_t))) + return true; + + ctx->error = EXT_TYPE_WRITING_ERROR; + return false; +} + +bool cmp_write_fixext8(cmp_ctx_t *ctx, int8_t type, const void *data) { + if (!cmp_write_fixext8_marker(ctx, type)) + return false; + + if (ctx->write(ctx, data, 8)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_fixext16_marker(cmp_ctx_t *ctx, int8_t type) { + if (!write_type_marker(ctx, FIXEXT16_MARKER)) + return false; + + if (ctx->write(ctx, &type, sizeof(int8_t))) + return true; + + ctx->error = EXT_TYPE_WRITING_ERROR; + return false; +} + +bool cmp_write_fixext16(cmp_ctx_t *ctx, int8_t type, const void *data) { + if (!cmp_write_fixext16_marker(ctx, type)) + return false; + + if (ctx->write(ctx, data, 16)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_ext8_marker(cmp_ctx_t *ctx, int8_t type, uint8_t size) { + if (!write_type_marker(ctx, EXT8_MARKER)) + return false; + + if (!ctx->write(ctx, &size, sizeof(uint8_t))) { + ctx->error = LENGTH_WRITING_ERROR; + return false; + } + + if (ctx->write(ctx, &type, sizeof(int8_t))) + return true; + + ctx->error = EXT_TYPE_WRITING_ERROR; + return false; +} + +bool cmp_write_ext8(cmp_ctx_t *ctx, int8_t type, uint8_t size, const void *data) { + if (!cmp_write_ext8_marker(ctx, type, size)) + return false; + + if (ctx->write(ctx, data, size)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_ext16_marker(cmp_ctx_t *ctx, int8_t type, uint16_t size) { + if (!write_type_marker(ctx, EXT16_MARKER)) + return false; + + size = be16(size); + + if (!ctx->write(ctx, &size, sizeof(uint16_t))) { + ctx->error = LENGTH_WRITING_ERROR; + return false; + } + + if (ctx->write(ctx, &type, sizeof(int8_t))) + return true; + + ctx->error = EXT_TYPE_WRITING_ERROR; + return false; +} + +bool cmp_write_ext16(cmp_ctx_t *ctx, int8_t type, uint16_t size, const void *data) { + if (!cmp_write_ext16_marker(ctx, type, size)) + return false; + + if (ctx->write(ctx, data, size)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_ext32_marker(cmp_ctx_t *ctx, int8_t type, uint32_t size) { + if (!write_type_marker(ctx, EXT32_MARKER)) + return false; + + size = be32(size); + + if (!ctx->write(ctx, &size, sizeof(uint32_t))) { + ctx->error = LENGTH_WRITING_ERROR; + return false; + } + + if (ctx->write(ctx, &type, sizeof(int8_t))) + return true; + + ctx->error = EXT_TYPE_WRITING_ERROR; + return false; +} + +bool cmp_write_ext32(cmp_ctx_t *ctx, int8_t type, uint32_t size, const void *data) { + if (!cmp_write_ext32_marker(ctx, type, size)) + return false; + + if (ctx->write(ctx, data, size)) + return true; + + ctx->error = DATA_WRITING_ERROR; + return false; +} + +bool cmp_write_ext_marker(cmp_ctx_t *ctx, int8_t type, uint32_t size) { + if (size == 1) + return cmp_write_fixext1_marker(ctx, type); + if (size == 2) + return cmp_write_fixext2_marker(ctx, type); + if (size == 4) + return cmp_write_fixext4_marker(ctx, type); + if (size == 8) + return cmp_write_fixext8_marker(ctx, type); + if (size == 16) + return cmp_write_fixext16_marker(ctx, type); + if (size <= 0xFF) + return cmp_write_ext8_marker(ctx, type, (uint8_t)size); + if (size <= 0xFFFF) + return cmp_write_ext16_marker(ctx, type, (uint16_t)size); + + return cmp_write_ext32_marker(ctx, type, size); +} + +bool cmp_write_ext(cmp_ctx_t *ctx, int8_t type, uint32_t size, const void *data) { + if (size == 1) + return cmp_write_fixext1(ctx, type, data); + if (size == 2) + return cmp_write_fixext2(ctx, type, data); + if (size == 4) + return cmp_write_fixext4(ctx, type, data); + if (size == 8) + return cmp_write_fixext8(ctx, type, data); + if (size == 16) + return cmp_write_fixext16(ctx, type, data); + if (size <= 0xFF) + return cmp_write_ext8(ctx, type, (uint8_t)size, data); + if (size <= 0xFFFF) + return cmp_write_ext16(ctx, type, (uint16_t)size, data); + + return cmp_write_ext32(ctx, type, size, data); +} + +bool cmp_write_object(cmp_ctx_t *ctx, const cmp_object_t *obj) { + switch(obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + return cmp_write_pfix(ctx, obj->as.u8); + case CMP_TYPE_FIXMAP: + return cmp_write_fixmap(ctx, (uint8_t)obj->as.map_size); + case CMP_TYPE_FIXARRAY: + return cmp_write_fixarray(ctx, (uint8_t)obj->as.array_size); + case CMP_TYPE_FIXSTR: + return cmp_write_fixstr_marker(ctx, (uint8_t)obj->as.str_size); + case CMP_TYPE_NIL: + return cmp_write_nil(ctx); + case CMP_TYPE_BOOLEAN: + if (obj->as.boolean) + return cmp_write_true(ctx); + return cmp_write_false(ctx); + case CMP_TYPE_BIN8: + return cmp_write_bin8_marker(ctx, (uint8_t)obj->as.bin_size); + case CMP_TYPE_BIN16: + return cmp_write_bin16_marker(ctx, (uint16_t)obj->as.bin_size); + case CMP_TYPE_BIN32: + return cmp_write_bin32_marker(ctx, obj->as.bin_size); + case CMP_TYPE_EXT8: + return cmp_write_ext8_marker( + ctx, obj->as.ext.type, (uint8_t)obj->as.ext.size + ); + case CMP_TYPE_EXT16: + return cmp_write_ext16_marker( + ctx, obj->as.ext.type, (uint16_t)obj->as.ext.size + ); + case CMP_TYPE_EXT32: + return cmp_write_ext32_marker(ctx, obj->as.ext.type, obj->as.ext.size); + case CMP_TYPE_FLOAT: +#ifndef CMP_NO_FLOAT + return cmp_write_float(ctx, obj->as.flt); +#else /* CMP_NO_FLOAT */ + ctx->error = DISABLED_FLOATING_POINT_ERROR; + return false; +#endif /* CMP_NO_FLOAT */ + case CMP_TYPE_DOUBLE: +#ifndef CMP_NO_FLOAT + return cmp_write_double(ctx, obj->as.dbl); +#else /* CMP_NO_FLOAT */ + ctx->error = DISABLED_FLOATING_POINT_ERROR; + return false; +#endif + case CMP_TYPE_UINT8: + return cmp_write_u8(ctx, obj->as.u8); + case CMP_TYPE_UINT16: + return cmp_write_u16(ctx, obj->as.u16); + case CMP_TYPE_UINT32: + return cmp_write_u32(ctx, obj->as.u32); + case CMP_TYPE_UINT64: + return cmp_write_u64(ctx, obj->as.u64); + case CMP_TYPE_SINT8: + return cmp_write_s8(ctx, obj->as.s8); + case CMP_TYPE_SINT16: + return cmp_write_s16(ctx, obj->as.s16); + case CMP_TYPE_SINT32: + return cmp_write_s32(ctx, obj->as.s32); + case CMP_TYPE_SINT64: + return cmp_write_s64(ctx, obj->as.s64); + case CMP_TYPE_FIXEXT1: + return cmp_write_fixext1_marker(ctx, obj->as.ext.type); + case CMP_TYPE_FIXEXT2: + return cmp_write_fixext2_marker(ctx, obj->as.ext.type); + case CMP_TYPE_FIXEXT4: + return cmp_write_fixext4_marker(ctx, obj->as.ext.type); + case CMP_TYPE_FIXEXT8: + return cmp_write_fixext8_marker(ctx, obj->as.ext.type); + case CMP_TYPE_FIXEXT16: + return cmp_write_fixext16_marker(ctx, obj->as.ext.type); + case CMP_TYPE_STR8: + return cmp_write_str8_marker(ctx, (uint8_t)obj->as.str_size); + case CMP_TYPE_STR16: + return cmp_write_str16_marker(ctx, (uint16_t)obj->as.str_size); + case CMP_TYPE_STR32: + return cmp_write_str32_marker(ctx, obj->as.str_size); + case CMP_TYPE_ARRAY16: + return cmp_write_array16(ctx, (uint16_t)obj->as.array_size); + case CMP_TYPE_ARRAY32: + return cmp_write_array32(ctx, obj->as.array_size); + case CMP_TYPE_MAP16: + return cmp_write_map16(ctx, (uint16_t)obj->as.map_size); + case CMP_TYPE_MAP32: + return cmp_write_map32(ctx, obj->as.map_size); + case CMP_TYPE_NEGATIVE_FIXNUM: + return cmp_write_nfix(ctx, obj->as.s8); + default: + ctx->error = INVALID_TYPE_ERROR; + return false; + } +} + +bool cmp_write_object_v4(cmp_ctx_t *ctx, const cmp_object_t *obj) { + switch(obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + return cmp_write_pfix(ctx, obj->as.u8); + case CMP_TYPE_FIXMAP: + return cmp_write_fixmap(ctx, (uint8_t)obj->as.map_size); + case CMP_TYPE_FIXARRAY: + return cmp_write_fixarray(ctx, (uint8_t)obj->as.array_size); + case CMP_TYPE_FIXSTR: + return cmp_write_fixstr_marker(ctx, (uint8_t)obj->as.str_size); + case CMP_TYPE_NIL: + return cmp_write_nil(ctx); + case CMP_TYPE_BOOLEAN: + if (obj->as.boolean) + return cmp_write_true(ctx); + return cmp_write_false(ctx); + case CMP_TYPE_EXT8: + return cmp_write_ext8_marker(ctx, obj->as.ext.type, (uint8_t)obj->as.ext.size); + case CMP_TYPE_EXT16: + return cmp_write_ext16_marker( + ctx, obj->as.ext.type, (uint16_t)obj->as.ext.size + ); + case CMP_TYPE_EXT32: + return cmp_write_ext32_marker(ctx, obj->as.ext.type, obj->as.ext.size); + case CMP_TYPE_FLOAT: +#ifndef CMP_NO_FLOAT + return cmp_write_float(ctx, obj->as.flt); +#else /* CMP_NO_FLOAT */ + ctx->error = DISABLED_FLOATING_POINT_ERROR; + return false; +#endif + case CMP_TYPE_DOUBLE: +#ifndef CMP_NO_FLOAT + return cmp_write_double(ctx, obj->as.dbl); +#else + ctx->error = DISABLED_FLOATING_POINT_ERROR; + return false; +#endif + case CMP_TYPE_UINT8: + return cmp_write_u8(ctx, obj->as.u8); + case CMP_TYPE_UINT16: + return cmp_write_u16(ctx, obj->as.u16); + case CMP_TYPE_UINT32: + return cmp_write_u32(ctx, obj->as.u32); + case CMP_TYPE_UINT64: + return cmp_write_u64(ctx, obj->as.u64); + case CMP_TYPE_SINT8: + return cmp_write_s8(ctx, obj->as.s8); + case CMP_TYPE_SINT16: + return cmp_write_s16(ctx, obj->as.s16); + case CMP_TYPE_SINT32: + return cmp_write_s32(ctx, obj->as.s32); + case CMP_TYPE_SINT64: + return cmp_write_s64(ctx, obj->as.s64); + case CMP_TYPE_FIXEXT1: + return cmp_write_fixext1_marker(ctx, obj->as.ext.type); + case CMP_TYPE_FIXEXT2: + return cmp_write_fixext2_marker(ctx, obj->as.ext.type); + case CMP_TYPE_FIXEXT4: + return cmp_write_fixext4_marker(ctx, obj->as.ext.type); + case CMP_TYPE_FIXEXT8: + return cmp_write_fixext8_marker(ctx, obj->as.ext.type); + case CMP_TYPE_FIXEXT16: + return cmp_write_fixext16_marker(ctx, obj->as.ext.type); + case CMP_TYPE_STR16: + return cmp_write_str16_marker(ctx, (uint16_t)obj->as.str_size); + case CMP_TYPE_STR32: + return cmp_write_str32_marker(ctx, obj->as.str_size); + case CMP_TYPE_ARRAY16: + return cmp_write_array16(ctx, (uint16_t)obj->as.array_size); + case CMP_TYPE_ARRAY32: + return cmp_write_array32(ctx, obj->as.array_size); + case CMP_TYPE_MAP16: + return cmp_write_map16(ctx, (uint16_t)obj->as.map_size); + case CMP_TYPE_MAP32: + return cmp_write_map32(ctx, obj->as.map_size); + case CMP_TYPE_NEGATIVE_FIXNUM: + return cmp_write_nfix(ctx, obj->as.s8); + default: + ctx->error = INVALID_TYPE_ERROR; + return false; + } +} + +bool cmp_read_pfix(cmp_ctx_t *ctx, uint8_t *c) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_POSITIVE_FIXNUM) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *c = obj.as.u8; + return true; +} + +bool cmp_read_nfix(cmp_ctx_t *ctx, int8_t *c) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_NEGATIVE_FIXNUM) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *c = obj.as.s8; + return true; +} + +bool cmp_read_sfix(cmp_ctx_t *ctx, int8_t *c) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_NEGATIVE_FIXNUM: + *c = obj.as.s8; + return true; + default: + ctx->error = INVALID_TYPE_ERROR; + return false; + } +} + +bool cmp_read_s8(cmp_ctx_t *ctx, int8_t *c) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_SINT8) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *c = obj.as.s8; + return true; +} + +bool cmp_read_s16(cmp_ctx_t *ctx, int16_t *s) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_SINT16) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *s = obj.as.s16; + return true; +} + +bool cmp_read_s32(cmp_ctx_t *ctx, int32_t *i) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_SINT32) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *i = obj.as.s32; + return true; +} + +bool cmp_read_s64(cmp_ctx_t *ctx, int64_t *l) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_SINT64) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *l = obj.as.s64; + return true; +} + +bool cmp_read_char(cmp_ctx_t *ctx, int8_t *c) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + *c = obj.as.s8; + return true; + case CMP_TYPE_UINT8: + if (obj.as.u8 <= 127) { + *c = (int8_t)obj.as.u8; + return true; + } + break; + default: + break; + } + + ctx->error = INVALID_TYPE_ERROR; + return false; +} + +bool cmp_read_short(cmp_ctx_t *ctx, int16_t *s) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + *s = obj.as.s8; + return true; + case CMP_TYPE_UINT8: + *s = obj.as.u8; + return true; + case CMP_TYPE_SINT16: + *s = obj.as.s16; + return true; + case CMP_TYPE_UINT16: + if (obj.as.u16 <= 32767) { + *s = (int16_t)obj.as.u16; + return true; + } + break; + default: + break; + } + + ctx->error = INVALID_TYPE_ERROR; + return false; +} + +bool cmp_read_int(cmp_ctx_t *ctx, int32_t *i) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + *i = obj.as.s8; + return true; + case CMP_TYPE_UINT8: + *i = obj.as.u8; + return true; + case CMP_TYPE_SINT16: + *i = obj.as.s16; + return true; + case CMP_TYPE_UINT16: + *i = obj.as.u16; + return true; + case CMP_TYPE_SINT32: + *i = obj.as.s32; + return true; + case CMP_TYPE_UINT32: + if (obj.as.u32 <= 2147483647) { + *i = (int32_t)obj.as.u32; + return true; + } + break; + default: + break; + } + + ctx->error = INVALID_TYPE_ERROR; + return false; +} + +bool cmp_read_long(cmp_ctx_t *ctx, int64_t *d) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + *d = obj.as.s8; + return true; + case CMP_TYPE_UINT8: + *d = obj.as.u8; + return true; + case CMP_TYPE_SINT16: + *d = obj.as.s16; + return true; + case CMP_TYPE_UINT16: + *d = obj.as.u16; + return true; + case CMP_TYPE_SINT32: + *d = obj.as.s32; + return true; + case CMP_TYPE_UINT32: + *d = obj.as.u32; + return true; + case CMP_TYPE_SINT64: + *d = obj.as.s64; + return true; + case CMP_TYPE_UINT64: + if (obj.as.u64 <= 9223372036854775807) { + *d = (int64_t)obj.as.u64; + return true; + } + break; + default: + break; + } + + ctx->error = INVALID_TYPE_ERROR; + return false; +} + +bool cmp_read_integer(cmp_ctx_t *ctx, int64_t *d) { + return cmp_read_long(ctx, d); +} + +bool cmp_read_ufix(cmp_ctx_t *ctx, uint8_t *c) { + return cmp_read_pfix(ctx, c); +} + +bool cmp_read_u8(cmp_ctx_t *ctx, uint8_t *c) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_UINT8) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *c = obj.as.u8; + return true; +} + +bool cmp_read_u16(cmp_ctx_t *ctx, uint16_t *s) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_UINT16) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *s = obj.as.u16; + return true; +} + +bool cmp_read_u32(cmp_ctx_t *ctx, uint32_t *i) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_UINT32) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *i = obj.as.u32; + return true; +} + +bool cmp_read_u64(cmp_ctx_t *ctx, uint64_t *l) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_UINT64) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *l = obj.as.u64; + return true; +} + +bool cmp_read_uchar(cmp_ctx_t *ctx, uint8_t *c) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + *c = obj.as.u8; + return true; + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + if (obj.as.s8 >= 0) { + *c = (uint8_t)obj.as.s8; + return true; + } + break; + default: + break; + } + + ctx->error = INVALID_TYPE_ERROR; + return false; +} + +bool cmp_read_ushort(cmp_ctx_t *ctx, uint16_t *s) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + *s = obj.as.u8; + return true; + case CMP_TYPE_UINT16: + *s = obj.as.u16; + return true; + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + if (obj.as.s8 >= 0) { + *s = (uint8_t)obj.as.s8; + return true; + } + break; + case CMP_TYPE_SINT16: + if (obj.as.s16 >= 0) { + *s = (uint16_t)obj.as.s16; + return true; + } + break; + default: + break; + } + + ctx->error = INVALID_TYPE_ERROR; + return false; +} + +bool cmp_read_uint(cmp_ctx_t *ctx, uint32_t *i) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + *i = obj.as.u8; + return true; + case CMP_TYPE_UINT16: + *i = obj.as.u16; + return true; + case CMP_TYPE_UINT32: + *i = obj.as.u32; + return true; + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + if (obj.as.s8 >= 0) { + *i = (uint8_t)obj.as.s8; + return true; + } + break; + case CMP_TYPE_SINT16: + if (obj.as.s16 >= 0) { + *i = (uint16_t)obj.as.s16; + return true; + } + break; + case CMP_TYPE_SINT32: + if (obj.as.s32 >= 0) { + *i = (uint32_t)obj.as.s32; + return true; + } + break; + default: + break; + } + + ctx->error = INVALID_TYPE_ERROR; + return false; +} + +bool cmp_read_ulong(cmp_ctx_t *ctx, uint64_t *u) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + *u = obj.as.u8; + return true; + case CMP_TYPE_UINT16: + *u = obj.as.u16; + return true; + case CMP_TYPE_UINT32: + *u = obj.as.u32; + return true; + case CMP_TYPE_UINT64: + *u = obj.as.u64; + return true; + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + if (obj.as.s8 >= 0) { + *u = (uint8_t)obj.as.s8; + return true; + } + break; + case CMP_TYPE_SINT16: + if (obj.as.s16 >= 0) { + *u = (uint16_t)obj.as.s16; + return true; + } + break; + case CMP_TYPE_SINT32: + if (obj.as.s32 >= 0) { + *u = (uint32_t)obj.as.s32; + return true; + } + break; + case CMP_TYPE_SINT64: + if (obj.as.s64 >= 0) { + *u = (uint64_t)obj.as.s64; + return true; + } + break; + default: + break; + } + + ctx->error = INVALID_TYPE_ERROR; + return false; +} + +bool cmp_read_uinteger(cmp_ctx_t *ctx, uint64_t *u) { + return cmp_read_ulong(ctx, u); +} + +#ifndef CMP_NO_FLOAT +bool cmp_read_float(cmp_ctx_t *ctx, float *f) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_FLOAT) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *f = obj.as.flt; + + return true; +} + +bool cmp_read_double(cmp_ctx_t *ctx, double *d) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_DOUBLE) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *d = obj.as.dbl; + + return true; +} + +bool cmp_read_decimal(cmp_ctx_t *ctx, double *d) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_FLOAT: + *d = (double)obj.as.flt; + return true; + case CMP_TYPE_DOUBLE: + *d = obj.as.dbl; + return true; + default: + ctx->error = INVALID_TYPE_ERROR; + return false; + } +} +#endif /* CMP_NO_FLOAT */ + +bool cmp_read_nil(cmp_ctx_t *ctx) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type == CMP_TYPE_NIL) + return true; + + ctx->error = INVALID_TYPE_ERROR; + return false; +} + +bool cmp_read_bool(cmp_ctx_t *ctx, bool *b) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_BOOLEAN) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + if (obj.as.boolean) + *b = true; + else + *b = false; + + return true; +} + +bool cmp_read_bool_as_u8(cmp_ctx_t *ctx, uint8_t *b) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_BOOLEAN) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + if (obj.as.boolean) + *b = 1; + else + *b = 0; + + return true; +} + +bool cmp_read_str_size(cmp_ctx_t *ctx, uint32_t *size) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_FIXSTR: + case CMP_TYPE_STR8: + case CMP_TYPE_STR16: + case CMP_TYPE_STR32: + *size = obj.as.str_size; + return true; + default: + ctx->error = INVALID_TYPE_ERROR; + return false; + } +} + +bool cmp_read_str(cmp_ctx_t *ctx, char *data, uint32_t *size) { + uint32_t str_size = 0; + + if (!cmp_read_str_size(ctx, &str_size)) + return false; + + if (str_size >= *size) { + *size = str_size; + ctx->error = STR_DATA_LENGTH_TOO_LONG_ERROR; + return false; + } + + if (!ctx->read(ctx, data, str_size)) { + ctx->error = DATA_READING_ERROR; + return false; + } + + data[str_size] = 0; + + *size = str_size; + return true; +} + +bool cmp_read_bin_size(cmp_ctx_t *ctx, uint32_t *size) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_BIN8: + case CMP_TYPE_BIN16: + case CMP_TYPE_BIN32: + *size = obj.as.bin_size; + return true; + default: + ctx->error = INVALID_TYPE_ERROR; + return false; + } +} + +bool cmp_read_bin(cmp_ctx_t *ctx, void *data, uint32_t *size) { + uint32_t bin_size = 0; + + if (!cmp_read_bin_size(ctx, &bin_size)) + return false; + + if (bin_size > *size) { + ctx->error = BIN_DATA_LENGTH_TOO_LONG_ERROR; + return false; + } + + if (!ctx->read(ctx, data, bin_size)) { + ctx->error = DATA_READING_ERROR; + return false; + } + + *size = bin_size; + return true; +} + +bool cmp_read_array(cmp_ctx_t *ctx, uint32_t *size) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_FIXARRAY: + case CMP_TYPE_ARRAY16: + case CMP_TYPE_ARRAY32: + *size = obj.as.array_size; + return true; + default: + ctx->error = INVALID_TYPE_ERROR; + return false; + } +} + +bool cmp_read_map(cmp_ctx_t *ctx, uint32_t *size) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_FIXMAP: + case CMP_TYPE_MAP16: + case CMP_TYPE_MAP32: + *size = obj.as.map_size; + return true; + default: + ctx->error = INVALID_TYPE_ERROR; + return false; + } +} + +bool cmp_read_fixext1_marker(cmp_ctx_t *ctx, int8_t *type) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_FIXEXT1) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *type = obj.as.ext.type; + return true; +} + +bool cmp_read_fixext1(cmp_ctx_t *ctx, int8_t *type, void *data) { + if (!cmp_read_fixext1_marker(ctx, type)) + return false; + + if (ctx->read(ctx, data, 1)) + return true; + + ctx->error = DATA_READING_ERROR; + return false; +} + +bool cmp_read_fixext2_marker(cmp_ctx_t *ctx, int8_t *type) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_FIXEXT2) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *type = obj.as.ext.type; + return true; +} + +bool cmp_read_fixext2(cmp_ctx_t *ctx, int8_t *type, void *data) { + if (!cmp_read_fixext2_marker(ctx, type)) + return false; + + if (ctx->read(ctx, data, 2)) + return true; + + ctx->error = DATA_READING_ERROR; + return false; +} + +bool cmp_read_fixext4_marker(cmp_ctx_t *ctx, int8_t *type) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_FIXEXT4) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *type = obj.as.ext.type; + return true; +} + +bool cmp_read_fixext4(cmp_ctx_t *ctx, int8_t *type, void *data) { + if (!cmp_read_fixext4_marker(ctx, type)) + return false; + + if (ctx->read(ctx, data, 4)) + return true; + + ctx->error = DATA_READING_ERROR; + return false; +} + +bool cmp_read_fixext8_marker(cmp_ctx_t *ctx, int8_t *type) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_FIXEXT8) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *type = obj.as.ext.type; + return true; +} + +bool cmp_read_fixext8(cmp_ctx_t *ctx, int8_t *type, void *data) { + if (!cmp_read_fixext8_marker(ctx, type)) + return false; + + if (ctx->read(ctx, data, 8)) + return true; + + ctx->error = DATA_READING_ERROR; + return false; +} + +bool cmp_read_fixext16_marker(cmp_ctx_t *ctx, int8_t *type) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_FIXEXT16) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *type = obj.as.ext.type; + return true; +} + +bool cmp_read_fixext16(cmp_ctx_t *ctx, int8_t *type, void *data) { + if (!cmp_read_fixext16_marker(ctx, type)) + return false; + + if (ctx->read(ctx, data, 16)) + return true; + + ctx->error = DATA_READING_ERROR; + return false; +} + +bool cmp_read_ext8_marker(cmp_ctx_t *ctx, int8_t *type, uint8_t *size) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_EXT8) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *type = obj.as.ext.type; + *size = (uint8_t)obj.as.ext.size; + + return true; +} + +bool cmp_read_ext8(cmp_ctx_t *ctx, int8_t *type, uint8_t *size, void *data) { + if (!cmp_read_ext8_marker(ctx, type, size)) + return false; + + if (ctx->read(ctx, data, *size)) + return true; + + ctx->error = DATA_READING_ERROR; + return false; +} + +bool cmp_read_ext16_marker(cmp_ctx_t *ctx, int8_t *type, uint16_t *size) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_EXT16) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *type = obj.as.ext.type; + *size = (uint16_t)obj.as.ext.size; + + return true; +} + +bool cmp_read_ext16(cmp_ctx_t *ctx, int8_t *type, uint16_t *size, void *data) { + if (!cmp_read_ext16_marker(ctx, type, size)) + return false; + + if (ctx->read(ctx, data, *size)) + return true; + + ctx->error = DATA_READING_ERROR; + return false; +} + +bool cmp_read_ext32_marker(cmp_ctx_t *ctx, int8_t *type, uint32_t *size) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + if (obj.type != CMP_TYPE_EXT32) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + *type = obj.as.ext.type; + *size = obj.as.ext.size; + + return true; +} + +bool cmp_read_ext32(cmp_ctx_t *ctx, int8_t *type, uint32_t *size, void *data) { + if (!cmp_read_ext32_marker(ctx, type, size)) + return false; + + if (ctx->read(ctx, data, *size)) + return true; + + ctx->error = DATA_READING_ERROR; + return false; +} + +bool cmp_read_ext_marker(cmp_ctx_t *ctx, int8_t *type, uint32_t *size) { + cmp_object_t obj; + + if (!cmp_read_object(ctx, &obj)) + return false; + + switch (obj.type) { + case CMP_TYPE_FIXEXT1: + case CMP_TYPE_FIXEXT2: + case CMP_TYPE_FIXEXT4: + case CMP_TYPE_FIXEXT8: + case CMP_TYPE_FIXEXT16: + case CMP_TYPE_EXT8: + case CMP_TYPE_EXT16: + case CMP_TYPE_EXT32: + *type = obj.as.ext.type; + *size = obj.as.ext.size; + return true; + default: + ctx->error = INVALID_TYPE_ERROR; + return false; + } +} + +bool cmp_read_ext(cmp_ctx_t *ctx, int8_t *type, uint32_t *size, void *data) { + if (!cmp_read_ext_marker(ctx, type, size)) + return false; + + if (ctx->read(ctx, data, *size)) + return true; + + ctx->error = DATA_READING_ERROR; + return false; +} + +bool cmp_read_object(cmp_ctx_t *ctx, cmp_object_t *obj) { + uint8_t type_marker = 0; + + if (!read_type_marker(ctx, &type_marker)) + return false; + + if (!type_marker_to_cmp_type(type_marker, &obj->type)) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + return read_obj_data(ctx, type_marker, obj); +} + +bool cmp_skip_object(cmp_ctx_t *ctx, cmp_object_t *obj) { + uint8_t type_marker = 0; + uint8_t cmp_type; + uint32_t size = 0; + + if (!read_type_marker(ctx, &type_marker)) { + return false; + } + + if (!type_marker_to_cmp_type(type_marker, &cmp_type)) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + switch (cmp_type) { + case CMP_TYPE_FIXARRAY: + case CMP_TYPE_ARRAY16: + case CMP_TYPE_ARRAY32: + case CMP_TYPE_FIXMAP: + case CMP_TYPE_MAP16: + case CMP_TYPE_MAP32: + obj->type = cmp_type; + + if (!read_obj_data(ctx, type_marker, obj)) { + return false; + } + + ctx->error = SKIP_DEPTH_LIMIT_EXCEEDED_ERROR; + + return false; + default: + if (!read_type_size(ctx, type_marker, cmp_type, &size)) { + return false; + } + + if (size) { + switch (cmp_type) { + case CMP_TYPE_FIXEXT1: + case CMP_TYPE_FIXEXT2: + case CMP_TYPE_FIXEXT4: + case CMP_TYPE_FIXEXT8: + case CMP_TYPE_FIXEXT16: + case CMP_TYPE_EXT8: + case CMP_TYPE_EXT16: + case CMP_TYPE_EXT32: + size++; + break; + default: + break; + } + + skip_bytes(ctx, size); + } + } + + return true; +} + +bool cmp_skip_object_flat(cmp_ctx_t *ctx, cmp_object_t *obj) { + size_t element_count = 1; + bool in_container = false; + + while (element_count) { + uint8_t type_marker = 0; + uint8_t cmp_type; + uint32_t size = 0; + + if (!read_type_marker(ctx, &type_marker)) { + return false; + } + + if (!type_marker_to_cmp_type(type_marker, &cmp_type)) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + switch (cmp_type) { + case CMP_TYPE_FIXARRAY: + case CMP_TYPE_ARRAY16: + case CMP_TYPE_ARRAY32: + case CMP_TYPE_FIXMAP: + case CMP_TYPE_MAP16: + case CMP_TYPE_MAP32: + if (in_container) { + obj->type = cmp_type; + + if (!read_obj_data(ctx, type_marker, obj)) { + return false; + } + + ctx->error = SKIP_DEPTH_LIMIT_EXCEEDED_ERROR; + return false; + } + + in_container = true; + + break; + default: + if (!read_type_size(ctx, type_marker, cmp_type, &size)) { + return false; + } + + if (size) { + switch (cmp_type) { + case CMP_TYPE_FIXEXT1: + case CMP_TYPE_FIXEXT2: + case CMP_TYPE_FIXEXT4: + case CMP_TYPE_FIXEXT8: + case CMP_TYPE_FIXEXT16: + case CMP_TYPE_EXT8: + case CMP_TYPE_EXT16: + case CMP_TYPE_EXT32: + size++; + break; + default: + break; + } + + skip_bytes(ctx, size); + } + } + + element_count--; + + switch (cmp_type) { + case CMP_TYPE_FIXARRAY: + case CMP_TYPE_ARRAY16: + case CMP_TYPE_ARRAY32: + if (!read_type_size(ctx, type_marker, cmp_type, &size)) { + return false; + } + element_count += size; + break; + case CMP_TYPE_FIXMAP: + case CMP_TYPE_MAP16: + case CMP_TYPE_MAP32: + if (!read_type_size(ctx, type_marker, cmp_type, &size)) { + return false; + } + element_count += ((size_t)size) * 2; + break; + default: + break; + } + } + + return true; +} + +bool cmp_skip_object_no_limit(cmp_ctx_t *ctx) { + size_t element_count = 1; + + while (element_count) { + uint8_t type_marker = 0; + uint8_t cmp_type = 0; + uint32_t size = 0; + + if (!read_type_marker(ctx, &type_marker)) { + return false; + } + + if (!type_marker_to_cmp_type(type_marker, &cmp_type)) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + switch (cmp_type) { + case CMP_TYPE_FIXARRAY: + case CMP_TYPE_ARRAY16: + case CMP_TYPE_ARRAY32: + case CMP_TYPE_FIXMAP: + case CMP_TYPE_MAP16: + case CMP_TYPE_MAP32: + break; + default: + if (!read_type_size(ctx, type_marker, cmp_type, &size)) { + return false; + } + + if (size) { + switch (cmp_type) { + case CMP_TYPE_FIXEXT1: + case CMP_TYPE_FIXEXT2: + case CMP_TYPE_FIXEXT4: + case CMP_TYPE_FIXEXT8: + case CMP_TYPE_FIXEXT16: + case CMP_TYPE_EXT8: + case CMP_TYPE_EXT16: + case CMP_TYPE_EXT32: + size++; + break; + default: + break; + } + + skip_bytes(ctx, size); + } + } + + element_count--; + + switch (cmp_type) { + case CMP_TYPE_FIXARRAY: + case CMP_TYPE_ARRAY16: + case CMP_TYPE_ARRAY32: + if (!read_type_size(ctx, type_marker, cmp_type, &size)) { + return false; + } + element_count += size; + break; + case CMP_TYPE_FIXMAP: + case CMP_TYPE_MAP16: + case CMP_TYPE_MAP32: + if (!read_type_size(ctx, type_marker, cmp_type, &size)) { + return false; + } + element_count += ((size_t)size) * 2; + break; + default: + break; + } + } + + return true; +} + +bool cmp_skip_object_limit(cmp_ctx_t *ctx, cmp_object_t *obj, uint32_t limit) { + size_t element_count = 1; + uint32_t depth = 0; + + while (element_count) { + uint8_t type_marker = 0; + uint8_t cmp_type; + uint32_t size = 0; + + if (!read_type_marker(ctx, &type_marker)) { + return false; + } + + if (!type_marker_to_cmp_type(type_marker, &cmp_type)) { + ctx->error = INVALID_TYPE_ERROR; + return false; + } + + switch (cmp_type) { + case CMP_TYPE_FIXARRAY: + case CMP_TYPE_ARRAY16: + case CMP_TYPE_ARRAY32: + case CMP_TYPE_FIXMAP: + case CMP_TYPE_MAP16: + case CMP_TYPE_MAP32: + depth++; + + if (depth > limit) { + obj->type = cmp_type; + + if (!read_obj_data(ctx, type_marker, obj)) { + return false; + } + + ctx->error = SKIP_DEPTH_LIMIT_EXCEEDED_ERROR; + + return false; + } + + break; + default: + if (!read_type_size(ctx, type_marker, cmp_type, &size)) { + return false; + } + + if (size) { + switch (cmp_type) { + case CMP_TYPE_FIXEXT1: + case CMP_TYPE_FIXEXT2: + case CMP_TYPE_FIXEXT4: + case CMP_TYPE_FIXEXT8: + case CMP_TYPE_FIXEXT16: + case CMP_TYPE_EXT8: + case CMP_TYPE_EXT16: + case CMP_TYPE_EXT32: + size++; + break; + default: + break; + } + + skip_bytes(ctx, size); + } + } + + element_count--; + + switch (cmp_type) { + case CMP_TYPE_FIXARRAY: + case CMP_TYPE_ARRAY16: + case CMP_TYPE_ARRAY32: + if (!read_type_size(ctx, type_marker, cmp_type, &size)) { + return false; + } + element_count += size; + break; + case CMP_TYPE_FIXMAP: + case CMP_TYPE_MAP16: + case CMP_TYPE_MAP32: + if (!read_type_size(ctx, type_marker, cmp_type, &size)) { + return false; + } + element_count += ((size_t)size) * 2; + break; + default: + break; + } + } + + return true; +} + +bool cmp_object_is_char(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + return true; + default: + return false; + } +} + +bool cmp_object_is_short(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + case CMP_TYPE_SINT16: + return true; + default: + return false; + } +} + +bool cmp_object_is_int(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + case CMP_TYPE_SINT16: + case CMP_TYPE_SINT32: + return true; + default: + return false; + } +} + +bool cmp_object_is_long(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + case CMP_TYPE_SINT16: + case CMP_TYPE_SINT32: + case CMP_TYPE_SINT64: + return true; + default: + return false; + } +} + +bool cmp_object_is_sinteger(const cmp_object_t *obj) { + return cmp_object_is_long(obj); +} + +bool cmp_object_is_uchar(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + return true; + default: + return false; + } +} + +bool cmp_object_is_ushort(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + return true; + case CMP_TYPE_UINT16: + return true; + default: + return false; + } +} + +bool cmp_object_is_uint(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + case CMP_TYPE_UINT16: + case CMP_TYPE_UINT32: + return true; + default: + return false; + } +} + +bool cmp_object_is_ulong(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + case CMP_TYPE_UINT16: + case CMP_TYPE_UINT32: + case CMP_TYPE_UINT64: + return true; + default: + return false; + } +} + +bool cmp_object_is_uinteger(const cmp_object_t *obj) { + return cmp_object_is_ulong(obj); +} + +bool cmp_object_is_float(const cmp_object_t *obj) { + if (obj->type == CMP_TYPE_FLOAT) + return true; + + return false; +} + +bool cmp_object_is_double(const cmp_object_t *obj) { + if (obj->type == CMP_TYPE_DOUBLE) + return true; + + return false; +} + +bool cmp_object_is_nil(const cmp_object_t *obj) { + if (obj->type == CMP_TYPE_NIL) + return true; + + return false; +} + +bool cmp_object_is_bool(const cmp_object_t *obj) { + if (obj->type == CMP_TYPE_BOOLEAN) + return true; + + return false; +} + +bool cmp_object_is_str(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_FIXSTR: + case CMP_TYPE_STR8: + case CMP_TYPE_STR16: + case CMP_TYPE_STR32: + return true; + default: + return false; + } +} + +bool cmp_object_is_bin(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_BIN8: + case CMP_TYPE_BIN16: + case CMP_TYPE_BIN32: + return true; + default: + return false; + } +} + +bool cmp_object_is_array(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_FIXARRAY: + case CMP_TYPE_ARRAY16: + case CMP_TYPE_ARRAY32: + return true; + default: + return false; + } +} + +bool cmp_object_is_map(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_FIXMAP: + case CMP_TYPE_MAP16: + case CMP_TYPE_MAP32: + return true; + default: + return false; + } +} + +bool cmp_object_is_ext(const cmp_object_t *obj) { + switch (obj->type) { + case CMP_TYPE_FIXEXT1: + case CMP_TYPE_FIXEXT2: + case CMP_TYPE_FIXEXT4: + case CMP_TYPE_FIXEXT8: + case CMP_TYPE_FIXEXT16: + case CMP_TYPE_EXT8: + case CMP_TYPE_EXT16: + case CMP_TYPE_EXT32: + return true; + default: + return false; + } +} + +bool cmp_object_as_char(const cmp_object_t *obj, int8_t *c) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + *c = obj->as.s8; + return true; + case CMP_TYPE_UINT8: + if (obj->as.u8 <= 127) { + *c = obj->as.s8; + return true; + } + else { + return false; + } + default: + return false; + } +} + +bool cmp_object_as_short(const cmp_object_t *obj, int16_t *s) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + *s = obj->as.s8; + return true; + case CMP_TYPE_UINT8: + *s = obj->as.u8; + return true; + case CMP_TYPE_SINT16: + *s = obj->as.s16; + return true; + case CMP_TYPE_UINT16: + if (obj->as.u16 <= 32767) { + *s = (int16_t)obj->as.u16; + return true; + } + else { + return false; + } + default: + return false; + } +} + +bool cmp_object_as_int(const cmp_object_t *obj, int32_t *i) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + *i = obj->as.s8; + return true; + case CMP_TYPE_UINT8: + *i = obj->as.u8; + return true; + case CMP_TYPE_SINT16: + *i = obj->as.s16; + return true; + case CMP_TYPE_UINT16: + *i = obj->as.u16; + return true; + case CMP_TYPE_SINT32: + *i = obj->as.s32; + return true; + case CMP_TYPE_UINT32: + if (obj->as.u32 <= 2147483647) { + *i = (int32_t)obj->as.u32; + return true; + } + else { + return false; + } + default: + return false; + } +} + +bool cmp_object_as_long(const cmp_object_t *obj, int64_t *d) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + *d = obj->as.s8; + return true; + case CMP_TYPE_UINT8: + *d = obj->as.u8; + return true; + case CMP_TYPE_SINT16: + *d = obj->as.s16; + return true; + case CMP_TYPE_UINT16: + *d = obj->as.u16; + return true; + case CMP_TYPE_SINT32: + *d = obj->as.s32; + return true; + case CMP_TYPE_UINT32: + *d = obj->as.u32; + return true; + case CMP_TYPE_SINT64: + *d = obj->as.s64; + return true; + case CMP_TYPE_UINT64: + if (obj->as.u64 <= 9223372036854775807) { + *d = (int64_t)obj->as.u64; + return true; + } + else { + return false; + } + default: + return false; + } +} + +bool cmp_object_as_sinteger(const cmp_object_t *obj, int64_t *d) { + return cmp_object_as_long(obj, d); +} + +bool cmp_object_as_uchar(const cmp_object_t *obj, uint8_t *c) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + *c = obj->as.u8; + return true; + default: + return false; + } +} + +bool cmp_object_as_ushort(const cmp_object_t *obj, uint16_t *s) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + *s = obj->as.u8; + return true; + case CMP_TYPE_UINT16: + *s = obj->as.u16; + return true; + default: + return false; + } +} + +bool cmp_object_as_uint(const cmp_object_t *obj, uint32_t *i) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + *i = obj->as.u8; + return true; + case CMP_TYPE_UINT16: + *i = obj->as.u16; + return true; + case CMP_TYPE_UINT32: + *i = obj->as.u32; + return true; + default: + return false; + } +} + +bool cmp_object_as_ulong(const cmp_object_t *obj, uint64_t *u) { + switch (obj->type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + *u = obj->as.u8; + return true; + case CMP_TYPE_UINT16: + *u = obj->as.u16; + return true; + case CMP_TYPE_UINT32: + *u = obj->as.u32; + return true; + case CMP_TYPE_UINT64: + *u = obj->as.u64; + return true; + default: + return false; + } +} + +bool cmp_object_as_uinteger(const cmp_object_t *obj, uint64_t *u) { + return cmp_object_as_ulong(obj, u); +} + +#ifndef CMP_NO_FLOAT +bool cmp_object_as_float(const cmp_object_t *obj, float *f) { + if (obj->type == CMP_TYPE_FLOAT) { + *f = obj->as.flt; + return true; + } + + return false; +} + +bool cmp_object_as_double(const cmp_object_t *obj, double *d) { + if (obj->type == CMP_TYPE_DOUBLE) { + *d = obj->as.dbl; + return true; + } + + return false; +} +#endif /* CMP_NO_FLOAT */ + +bool cmp_object_as_bool(const cmp_object_t *obj, bool *b) { + if (obj->type == CMP_TYPE_BOOLEAN) { + if (obj->as.boolean) + *b = true; + else + *b = false; + + return true; + } + + return false; +} + +bool cmp_object_as_str(const cmp_object_t *obj, uint32_t *size) { + switch (obj->type) { + case CMP_TYPE_FIXSTR: + case CMP_TYPE_STR8: + case CMP_TYPE_STR16: + case CMP_TYPE_STR32: + *size = obj->as.str_size; + return true; + default: + return false; + } +} + +bool cmp_object_as_bin(const cmp_object_t *obj, uint32_t *size) { + switch (obj->type) { + case CMP_TYPE_BIN8: + case CMP_TYPE_BIN16: + case CMP_TYPE_BIN32: + *size = obj->as.bin_size; + return true; + default: + return false; + } +} + +bool cmp_object_as_array(const cmp_object_t *obj, uint32_t *size) { + switch (obj->type) { + case CMP_TYPE_FIXARRAY: + case CMP_TYPE_ARRAY16: + case CMP_TYPE_ARRAY32: + *size = obj->as.array_size; + return true; + default: + return false; + } +} + +bool cmp_object_as_map(const cmp_object_t *obj, uint32_t *size) { + switch (obj->type) { + case CMP_TYPE_FIXMAP: + case CMP_TYPE_MAP16: + case CMP_TYPE_MAP32: + *size = obj->as.map_size; + return true; + default: + return false; + } +} + +bool cmp_object_as_ext(const cmp_object_t *obj, int8_t *type, uint32_t *size) { + switch (obj->type) { + case CMP_TYPE_FIXEXT1: + case CMP_TYPE_FIXEXT2: + case CMP_TYPE_FIXEXT4: + case CMP_TYPE_FIXEXT8: + case CMP_TYPE_FIXEXT16: + case CMP_TYPE_EXT8: + case CMP_TYPE_EXT16: + case CMP_TYPE_EXT32: + *type = obj->as.ext.type; + *size = obj->as.ext.size; + return true; + default: + return false; + } +} + +bool cmp_object_to_str(cmp_ctx_t *ctx, const cmp_object_t *obj, char *data, + uint32_t buf_size) { + uint32_t str_size = 0; + + switch (obj->type) { + case CMP_TYPE_FIXSTR: + case CMP_TYPE_STR8: + case CMP_TYPE_STR16: + case CMP_TYPE_STR32: + str_size = obj->as.str_size; + if (str_size >= buf_size) { + ctx->error = STR_DATA_LENGTH_TOO_LONG_ERROR; + return false; + } + + if (!ctx->read(ctx, data, str_size)) { + ctx->error = DATA_READING_ERROR; + return false; + } + + data[str_size] = 0; + return true; + default: + return false; + } +} + +bool cmp_object_to_bin(cmp_ctx_t *ctx, const cmp_object_t *obj, void *data, + uint32_t buf_size) { + uint32_t bin_size = 0; + + switch (obj->type) { + case CMP_TYPE_BIN8: + case CMP_TYPE_BIN16: + case CMP_TYPE_BIN32: + bin_size = obj->as.bin_size; + if (bin_size > buf_size) { + ctx->error = BIN_DATA_LENGTH_TOO_LONG_ERROR; + return false; + } + + if (!ctx->read(ctx, data, bin_size)) { + ctx->error = DATA_READING_ERROR; + return false; + } + return true; + default: + return false; + } +} + +/* vi: set et ts=2 sw=2: */ + diff --git a/local_pod_repo/cmp/cmp/cmp.h b/local_pod_repo/cmp/cmp/cmp.h new file mode 100644 index 0000000..26dc0d0 --- /dev/null +++ b/local_pod_repo/cmp/cmp/cmp.h @@ -0,0 +1,572 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#ifndef CMP_H_INCLUDED +#define CMP_H_INCLUDED + +#include +#include +#include + +struct cmp_ctx_s; + +typedef bool (*cmp_reader)(struct cmp_ctx_s *ctx, void *data, size_t limit); +typedef bool (*cmp_skipper)(struct cmp_ctx_s *ctx, size_t count); +typedef size_t (*cmp_writer)(struct cmp_ctx_s *ctx, const void *data, + size_t count); + +enum { + CMP_TYPE_POSITIVE_FIXNUM, /* 0 */ + CMP_TYPE_FIXMAP, /* 1 */ + CMP_TYPE_FIXARRAY, /* 2 */ + CMP_TYPE_FIXSTR, /* 3 */ + CMP_TYPE_NIL, /* 4 */ + CMP_TYPE_BOOLEAN, /* 5 */ + CMP_TYPE_BIN8, /* 6 */ + CMP_TYPE_BIN16, /* 7 */ + CMP_TYPE_BIN32, /* 8 */ + CMP_TYPE_EXT8, /* 9 */ + CMP_TYPE_EXT16, /* 10 */ + CMP_TYPE_EXT32, /* 11 */ + CMP_TYPE_FLOAT, /* 12 */ + CMP_TYPE_DOUBLE, /* 13 */ + CMP_TYPE_UINT8, /* 14 */ + CMP_TYPE_UINT16, /* 15 */ + CMP_TYPE_UINT32, /* 16 */ + CMP_TYPE_UINT64, /* 17 */ + CMP_TYPE_SINT8, /* 18 */ + CMP_TYPE_SINT16, /* 19 */ + CMP_TYPE_SINT32, /* 20 */ + CMP_TYPE_SINT64, /* 21 */ + CMP_TYPE_FIXEXT1, /* 22 */ + CMP_TYPE_FIXEXT2, /* 23 */ + CMP_TYPE_FIXEXT4, /* 24 */ + CMP_TYPE_FIXEXT8, /* 25 */ + CMP_TYPE_FIXEXT16, /* 26 */ + CMP_TYPE_STR8, /* 27 */ + CMP_TYPE_STR16, /* 28 */ + CMP_TYPE_STR32, /* 29 */ + CMP_TYPE_ARRAY16, /* 30 */ + CMP_TYPE_ARRAY32, /* 31 */ + CMP_TYPE_MAP16, /* 32 */ + CMP_TYPE_MAP32, /* 33 */ + CMP_TYPE_NEGATIVE_FIXNUM /* 34 */ +}; + +typedef struct cmp_ext_s { + int8_t type; + uint32_t size; +} cmp_ext_t; + +union cmp_object_data_u { + bool boolean; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + int8_t s8; + int16_t s16; + int32_t s32; + int64_t s64; +#ifndef CMP_NO_FLOAT + float flt; + double dbl; +#endif /* CMP_NO_FLOAT */ + uint32_t array_size; + uint32_t map_size; + uint32_t str_size; + uint32_t bin_size; + cmp_ext_t ext; +}; + +typedef struct cmp_ctx_s { + uint8_t error; + void *buf; + cmp_reader read; + cmp_skipper skip; + cmp_writer write; +} cmp_ctx_t; + +typedef struct cmp_object_s { + uint8_t type; + union cmp_object_data_u as; +} cmp_object_t; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ============================================================================ + * === Main API + * ============================================================================ + */ + +/* + * Initializes a CMP context + * + * If you don't intend to read, `read` may be NULL, but calling `*read*` + * functions will crash; there is no check. + * + * `skip` may be NULL, in which case skipping functions will use `read`. + * + * If you don't intend to write, `write` may be NULL, but calling `*write*` + * functions will crash; there is no check. + */ +void cmp_init(cmp_ctx_t *ctx, void *buf, cmp_reader read, + cmp_skipper skip, + cmp_writer write); + +/* Returns CMP's version */ +uint32_t cmp_version(void); + +/* Returns the MessagePack version employed by CMP */ +uint32_t cmp_mp_version(void); + +/* Returns a string description of a CMP context's error */ +const char* cmp_strerror(cmp_ctx_t *ctx); + +/* Writes a signed integer to the backend */ +bool cmp_write_integer(cmp_ctx_t *ctx, int64_t d); + +/* Writes an unsigned integer to the backend */ +bool cmp_write_uinteger(cmp_ctx_t *ctx, uint64_t u); + +/* + * Writes a floating-point value (either single or double-precision) to the + * backend + */ +#ifndef CMP_NO_FLOAT +bool cmp_write_decimal(cmp_ctx_t *ctx, double d); +#endif /* CMP_NO_FLOAT */ + +/* Writes NULL to the backend */ +bool cmp_write_nil(cmp_ctx_t *ctx); + +/* Writes true to the backend */ +bool cmp_write_true(cmp_ctx_t *ctx); + +/* Writes false to the backend */ +bool cmp_write_false(cmp_ctx_t *ctx); + +/* Writes a boolean value to the backend */ +bool cmp_write_bool(cmp_ctx_t *ctx, bool b); + +/* + * Writes an unsigned char's value to the backend as a boolean. This is useful + * if you are using a different boolean type in your application. + */ +bool cmp_write_u8_as_bool(cmp_ctx_t *ctx, uint8_t b); + +/* + * Writes a string to the backend; according to the MessagePack spec, this must + * be encoded using UTF-8, but CMP leaves that job up to the programmer. + */ +bool cmp_write_str(cmp_ctx_t *ctx, const char *data, uint32_t size); + +/* + * Writes a string to the backend. This avoids using the STR8 marker, which + * is unsupported by MessagePack v4, the version implemented by many other + * MessagePack libraries. No encoding is assumed in this case, not that it + * matters. + */ +bool cmp_write_str_v4(cmp_ctx_t *ctx, const char *data, uint32_t size); + +/* + * Writes the string marker to the backend. This is useful if you are writing + * data in chunks instead of a single shot. + */ +bool cmp_write_str_marker(cmp_ctx_t *ctx, uint32_t size); + +/* + * Writes the string marker to the backend. This is useful if you are writing + * data in chunks instead of a single shot. This avoids using the STR8 + * marker, which is unsupported by MessagePack v4, the version implemented by + * many other MessagePack libraries. No encoding is assumed in this case, not + * that it matters. + */ +bool cmp_write_str_marker_v4(cmp_ctx_t *ctx, uint32_t size); + +/* Writes binary data to the backend */ +bool cmp_write_bin(cmp_ctx_t *ctx, const void *data, uint32_t size); + +/* + * Writes the binary data marker to the backend. This is useful if you are + * writing data in chunks instead of a single shot. + */ +bool cmp_write_bin_marker(cmp_ctx_t *ctx, uint32_t size); + +/* Writes an array to the backend. */ +bool cmp_write_array(cmp_ctx_t *ctx, uint32_t size); + +/* Writes a map to the backend. */ +bool cmp_write_map(cmp_ctx_t *ctx, uint32_t size); + +/* Writes an extended type to the backend */ +bool cmp_write_ext(cmp_ctx_t *ctx, int8_t type, uint32_t size, + const void *data); + +/* + * Writes the extended type marker to the backend. This is useful if you want + * to write the type's data in chunks instead of a single shot. + */ +bool cmp_write_ext_marker(cmp_ctx_t *ctx, int8_t type, uint32_t size); + +/* Writes an object to the backend */ +bool cmp_write_object(cmp_ctx_t *ctx, const cmp_object_t *obj); + +/* + * Writes an object to the backend. This avoids using the STR8 marker, which + * is unsupported by MessagePack v4, the version implemented by many other + * MessagePack libraries. + */ +bool cmp_write_object_v4(cmp_ctx_t *ctx, const cmp_object_t *obj); + +/* Reads a signed integer that fits inside a signed char */ +bool cmp_read_char(cmp_ctx_t *ctx, int8_t *c); + +/* Reads a signed integer that fits inside a signed short */ +bool cmp_read_short(cmp_ctx_t *ctx, int16_t *s); + +/* Reads a signed integer that fits inside a signed int */ +bool cmp_read_int(cmp_ctx_t *ctx, int32_t *i); + +/* Reads a signed integer that fits inside a signed long */ +bool cmp_read_long(cmp_ctx_t *ctx, int64_t *d); + +/* Reads a signed integer */ +bool cmp_read_integer(cmp_ctx_t *ctx, int64_t *d); + +/* Reads an unsigned integer that fits inside an unsigned char */ +bool cmp_read_uchar(cmp_ctx_t *ctx, uint8_t *c); + +/* Reads an unsigned integer that fits inside an unsigned short */ +bool cmp_read_ushort(cmp_ctx_t *ctx, uint16_t *s); + +/* Reads an unsigned integer that fits inside an unsigned int */ +bool cmp_read_uint(cmp_ctx_t *ctx, uint32_t *i); + +/* Reads an unsigned integer that fits inside an unsigned long */ +bool cmp_read_ulong(cmp_ctx_t *ctx, uint64_t *u); + +/* Reads an unsigned integer */ +bool cmp_read_uinteger(cmp_ctx_t *ctx, uint64_t *u); + +/* + * Reads a floating point value (either single or double-precision) from the + * backend + */ +#ifndef CMP_NO_FLOAT +bool cmp_read_decimal(cmp_ctx_t *ctx, double *d); +#endif /* CMP_NO_FLOAT */ + +/* "Reads" (more like "skips") a NULL value from the backend */ +bool cmp_read_nil(cmp_ctx_t *ctx); + +/* Reads a boolean from the backend */ +bool cmp_read_bool(cmp_ctx_t *ctx, bool *b); + +/* + * Reads a boolean as an unsigned char from the backend; this is useful if your + * application uses a different boolean type. + */ +bool cmp_read_bool_as_u8(cmp_ctx_t *ctx, uint8_t *b); + +/* Reads a string's size from the backend */ +bool cmp_read_str_size(cmp_ctx_t *ctx, uint32_t *size); + +/* + * Reads a string from the backend; according to the spec, the string's data + * ought to be encoded using UTF-8, but CMP leaves that job up to the programmer. + */ +bool cmp_read_str(cmp_ctx_t *ctx, char *data, uint32_t *size); + +/* Reads the size of packed binary data from the backend */ +bool cmp_read_bin_size(cmp_ctx_t *ctx, uint32_t *size); + +/* Reads packed binary data from the backend */ +bool cmp_read_bin(cmp_ctx_t *ctx, void *data, uint32_t *size); + +/* Reads an array from the backend */ +bool cmp_read_array(cmp_ctx_t *ctx, uint32_t *size); + +/* Reads a map from the backend */ +bool cmp_read_map(cmp_ctx_t *ctx, uint32_t *size); + +/* Reads the extended type's marker from the backend */ +bool cmp_read_ext_marker(cmp_ctx_t *ctx, int8_t *type, uint32_t *size); + +/* Reads an extended type from the backend */ +bool cmp_read_ext(cmp_ctx_t *ctx, int8_t *type, uint32_t *size, void *data); + +/* Reads an object from the backend */ +bool cmp_read_object(cmp_ctx_t *ctx, cmp_object_t *obj); + +/* + * Skips the next object from the backend. If that object is an array or map, + * this function will: + * - If `obj` is not `NULL`, fill in `obj` with that object + * - Set `ctx->error` to `SKIP_DEPTH_LIMIT_EXCEEDED_ERROR` + * - Return `false` + * Otherwise: + * - (Don't touch `obj`) + * - Return `true` + */ +bool cmp_skip_object(cmp_ctx_t *ctx, cmp_object_t *obj); + +/* + * This is similar to `cmp_skip_object`, except it tolerates flat arrays and + * maps. If when skipping such an array or map this function encounters + * another array/map, it will: + * - If `obj` is not `NULL`, fill in `obj` with that (nested) object + * - Set `ctx->error` to `SKIP_DEPTH_LIMIT_EXCEEDED_ERROR` + * - Return `false` + * Otherwise: + * - (Don't touch `obj`) + * - Return `true` + * + * WARNING: This can cause your application to spend an unbounded amount of + * time reading nested data structures. Unless you completely trust + * the data source, you should use `cmp_skip_object`. + */ +bool cmp_skip_object_flat(cmp_ctx_t *ctx, cmp_object_t *obj); + +/* + * This is similar to `cmp_skip_object`, except it will continually skip + * nested data structures. + * + * WARNING: This can cause your application to spend an unbounded amount of + * time reading nested data structures. Unless you completely trust + * the data source, you should use `cmp_skip_object`. + */ +bool cmp_skip_object_no_limit(cmp_ctx_t *ctx); + +/* + * WARNING: THIS FUNCTION IS DEPRECATED AND WILL BE REMOVED IN A FUTURE RELEASE + * + * There is no way to track depths across elements without allocation. For + * example, an array constructed as: `[ [] [] [] [] [] [] [] [] [] [] ]` + * should be able to be skipped with `cmp_skip_object_limit(&cmp, &obj, 2)`. + * However, because we cannot track depth across the elements, there's no way + * to reset it after descending down into each element. + * + * This is similar to `cmp_skip_object`, except it tolerates up to `limit` + * levels of nesting. For example, in order to skip an array that contains a + * map, call `cmp_skip_object_limit(ctx, &obj, 2)`. Or in other words, + * `cmp_skip_object(ctx, &obj)` acts similarly to `cmp_skip_object_limit(ctx, + * &obj, 0)` + * + * Specifically, `limit` refers to depth, not breadth. So in order to skip an + * array that contains two arrays that each contain 3 strings, you would call + * `cmp_skip_object_limit(ctx, &obj, 2). In order to skip an array that + * contains 4 arrays that each contain 1 string, you would still call + * `cmp_skip_object_limit(ctx, &obj, 2). + */ +bool cmp_skip_object_limit(cmp_ctx_t *ctx, cmp_object_t *obj, uint32_t limit) +#ifdef __GNUC__ + __attribute__((deprecated)) +#endif +; + +#ifdef _MSC_VER +#pragma deprecated(cmp_skip_object_limit) +#endif + +/* + * ============================================================================ + * === Specific API + * ============================================================================ + */ + +bool cmp_write_pfix(cmp_ctx_t *ctx, uint8_t c); +bool cmp_write_nfix(cmp_ctx_t *ctx, int8_t c); + +bool cmp_write_sfix(cmp_ctx_t *ctx, int8_t c); +bool cmp_write_s8(cmp_ctx_t *ctx, int8_t c); +bool cmp_write_s16(cmp_ctx_t *ctx, int16_t s); +bool cmp_write_s32(cmp_ctx_t *ctx, int32_t i); +bool cmp_write_s64(cmp_ctx_t *ctx, int64_t l); + +bool cmp_write_ufix(cmp_ctx_t *ctx, uint8_t c); +bool cmp_write_u8(cmp_ctx_t *ctx, uint8_t c); +bool cmp_write_u16(cmp_ctx_t *ctx, uint16_t s); +bool cmp_write_u32(cmp_ctx_t *ctx, uint32_t i); +bool cmp_write_u64(cmp_ctx_t *ctx, uint64_t l); + +#ifndef CMP_NO_FLOAT +bool cmp_write_float(cmp_ctx_t *ctx, float f); +bool cmp_write_double(cmp_ctx_t *ctx, double d); +#endif /* CMP_NO_FLOAT */ + +bool cmp_write_fixstr_marker(cmp_ctx_t *ctx, uint8_t size); +bool cmp_write_fixstr(cmp_ctx_t *ctx, const char *data, uint8_t size); +bool cmp_write_str8_marker(cmp_ctx_t *ctx, uint8_t size); +bool cmp_write_str8(cmp_ctx_t *ctx, const char *data, uint8_t size); +bool cmp_write_str16_marker(cmp_ctx_t *ctx, uint16_t size); +bool cmp_write_str16(cmp_ctx_t *ctx, const char *data, uint16_t size); +bool cmp_write_str32_marker(cmp_ctx_t *ctx, uint32_t size); +bool cmp_write_str32(cmp_ctx_t *ctx, const char *data, uint32_t size); + +bool cmp_write_bin8_marker(cmp_ctx_t *ctx, uint8_t size); +bool cmp_write_bin8(cmp_ctx_t *ctx, const void *data, uint8_t size); +bool cmp_write_bin16_marker(cmp_ctx_t *ctx, uint16_t size); +bool cmp_write_bin16(cmp_ctx_t *ctx, const void *data, uint16_t size); +bool cmp_write_bin32_marker(cmp_ctx_t *ctx, uint32_t size); +bool cmp_write_bin32(cmp_ctx_t *ctx, const void *data, uint32_t size); + +bool cmp_write_fixarray(cmp_ctx_t *ctx, uint8_t size); +bool cmp_write_array16(cmp_ctx_t *ctx, uint16_t size); +bool cmp_write_array32(cmp_ctx_t *ctx, uint32_t size); + +bool cmp_write_fixmap(cmp_ctx_t *ctx, uint8_t size); +bool cmp_write_map16(cmp_ctx_t *ctx, uint16_t size); +bool cmp_write_map32(cmp_ctx_t *ctx, uint32_t size); + +bool cmp_write_fixext1_marker(cmp_ctx_t *ctx, int8_t type); +bool cmp_write_fixext1(cmp_ctx_t *ctx, int8_t type, const void *data); +bool cmp_write_fixext2_marker(cmp_ctx_t *ctx, int8_t type); +bool cmp_write_fixext2(cmp_ctx_t *ctx, int8_t type, const void *data); +bool cmp_write_fixext4_marker(cmp_ctx_t *ctx, int8_t type); +bool cmp_write_fixext4(cmp_ctx_t *ctx, int8_t type, const void *data); +bool cmp_write_fixext8_marker(cmp_ctx_t *ctx, int8_t type); +bool cmp_write_fixext8(cmp_ctx_t *ctx, int8_t type, const void *data); +bool cmp_write_fixext16_marker(cmp_ctx_t *ctx, int8_t type); +bool cmp_write_fixext16(cmp_ctx_t *ctx, int8_t type, const void *data); + +bool cmp_write_ext8_marker(cmp_ctx_t *ctx, int8_t type, uint8_t size); +bool cmp_write_ext8(cmp_ctx_t *ctx, int8_t type, uint8_t size, + const void *data); +bool cmp_write_ext16_marker(cmp_ctx_t *ctx, int8_t type, uint16_t size); +bool cmp_write_ext16(cmp_ctx_t *ctx, int8_t type, uint16_t size, + const void *data); +bool cmp_write_ext32_marker(cmp_ctx_t *ctx, int8_t type, uint32_t size); +bool cmp_write_ext32(cmp_ctx_t *ctx, int8_t type, uint32_t size, + const void *data); + +bool cmp_read_pfix(cmp_ctx_t *ctx, uint8_t *c); +bool cmp_read_nfix(cmp_ctx_t *ctx, int8_t *c); + +bool cmp_read_sfix(cmp_ctx_t *ctx, int8_t *c); +bool cmp_read_s8(cmp_ctx_t *ctx, int8_t *c); +bool cmp_read_s16(cmp_ctx_t *ctx, int16_t *s); +bool cmp_read_s32(cmp_ctx_t *ctx, int32_t *i); +bool cmp_read_s64(cmp_ctx_t *ctx, int64_t *l); + +bool cmp_read_ufix(cmp_ctx_t *ctx, uint8_t *c); +bool cmp_read_u8(cmp_ctx_t *ctx, uint8_t *c); +bool cmp_read_u16(cmp_ctx_t *ctx, uint16_t *s); +bool cmp_read_u32(cmp_ctx_t *ctx, uint32_t *i); +bool cmp_read_u64(cmp_ctx_t *ctx, uint64_t *l); + +#ifndef CMP_NO_FLOAT +bool cmp_read_float(cmp_ctx_t *ctx, float *f); +bool cmp_read_double(cmp_ctx_t *ctx, double *d); +#endif /* CMP_NO_FLOAT */ + +bool cmp_read_fixext1_marker(cmp_ctx_t *ctx, int8_t *type); +bool cmp_read_fixext1(cmp_ctx_t *ctx, int8_t *type, void *data); +bool cmp_read_fixext2_marker(cmp_ctx_t *ctx, int8_t *type); +bool cmp_read_fixext2(cmp_ctx_t *ctx, int8_t *type, void *data); +bool cmp_read_fixext4_marker(cmp_ctx_t *ctx, int8_t *type); +bool cmp_read_fixext4(cmp_ctx_t *ctx, int8_t *type, void *data); +bool cmp_read_fixext8_marker(cmp_ctx_t *ctx, int8_t *type); +bool cmp_read_fixext8(cmp_ctx_t *ctx, int8_t *type, void *data); +bool cmp_read_fixext16_marker(cmp_ctx_t *ctx, int8_t *type); +bool cmp_read_fixext16(cmp_ctx_t *ctx, int8_t *type, void *data); + +bool cmp_read_ext8_marker(cmp_ctx_t *ctx, int8_t *type, uint8_t *size); +bool cmp_read_ext8(cmp_ctx_t *ctx, int8_t *type, uint8_t *size, void *data); +bool cmp_read_ext16_marker(cmp_ctx_t *ctx, int8_t *type, uint16_t *size); +bool cmp_read_ext16(cmp_ctx_t *ctx, int8_t *type, uint16_t *size, void *data); +bool cmp_read_ext32_marker(cmp_ctx_t *ctx, int8_t *type, uint32_t *size); +bool cmp_read_ext32(cmp_ctx_t *ctx, int8_t *type, uint32_t *size, void *data); + +/* + * ============================================================================ + * === Object API + * ============================================================================ + */ + +bool cmp_object_is_char(const cmp_object_t *obj); +bool cmp_object_is_short(const cmp_object_t *obj); +bool cmp_object_is_int(const cmp_object_t *obj); +bool cmp_object_is_long(const cmp_object_t *obj); +bool cmp_object_is_sinteger(const cmp_object_t *obj); +bool cmp_object_is_uchar(const cmp_object_t *obj); +bool cmp_object_is_ushort(const cmp_object_t *obj); +bool cmp_object_is_uint(const cmp_object_t *obj); +bool cmp_object_is_ulong(const cmp_object_t *obj); +bool cmp_object_is_uinteger(const cmp_object_t *obj); +bool cmp_object_is_float(const cmp_object_t *obj); +bool cmp_object_is_double(const cmp_object_t *obj); +bool cmp_object_is_nil(const cmp_object_t *obj); +bool cmp_object_is_bool(const cmp_object_t *obj); +bool cmp_object_is_str(const cmp_object_t *obj); +bool cmp_object_is_bin(const cmp_object_t *obj); +bool cmp_object_is_array(const cmp_object_t *obj); +bool cmp_object_is_map(const cmp_object_t *obj); +bool cmp_object_is_ext(const cmp_object_t *obj); + +bool cmp_object_as_char(const cmp_object_t *obj, int8_t *c); +bool cmp_object_as_short(const cmp_object_t *obj, int16_t *s); +bool cmp_object_as_int(const cmp_object_t *obj, int32_t *i); +bool cmp_object_as_long(const cmp_object_t *obj, int64_t *d); +bool cmp_object_as_sinteger(const cmp_object_t *obj, int64_t *d); +bool cmp_object_as_uchar(const cmp_object_t *obj, uint8_t *c); +bool cmp_object_as_ushort(const cmp_object_t *obj, uint16_t *s); +bool cmp_object_as_uint(const cmp_object_t *obj, uint32_t *i); +bool cmp_object_as_ulong(const cmp_object_t *obj, uint64_t *u); +bool cmp_object_as_uinteger(const cmp_object_t *obj, uint64_t *u); +bool cmp_object_as_float(const cmp_object_t *obj, float *f); +bool cmp_object_as_double(const cmp_object_t *obj, double *d); +bool cmp_object_as_bool(const cmp_object_t *obj, bool *b); +bool cmp_object_as_str(const cmp_object_t *obj, uint32_t *size); +bool cmp_object_as_bin(const cmp_object_t *obj, uint32_t *size); +bool cmp_object_as_array(const cmp_object_t *obj, uint32_t *size); +bool cmp_object_as_map(const cmp_object_t *obj, uint32_t *size); +bool cmp_object_as_ext(const cmp_object_t *obj, int8_t *type, uint32_t *size); + +bool cmp_object_to_str(cmp_ctx_t *ctx, const cmp_object_t *obj, char *data, uint32_t buf_size); +bool cmp_object_to_bin(cmp_ctx_t *ctx, const cmp_object_t *obj, void *data, uint32_t buf_size); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/* + * ============================================================================ + * === Backwards compatibility defines + * ============================================================================ + */ + +#define cmp_write_int cmp_write_integer +#define cmp_write_sint cmp_write_integer +#define cmp_write_sinteger cmp_write_integer +#define cmp_write_uint cmp_write_uinteger +#define cmp_read_sinteger cmp_read_integer + +#endif /* CMP_H_INCLUDED */ + +/* vi: set et ts=2 sw=2: */ + diff --git a/local_pod_repo/cmp/cmp/examples/example1.c b/local_pod_repo/cmp/cmp/examples/example1.c new file mode 100644 index 0000000..56ae178 --- /dev/null +++ b/local_pod_repo/cmp/cmp/examples/example1.c @@ -0,0 +1,116 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include +#include + +#include "cmp.h" + +static bool read_bytes(void *data, size_t sz, FILE *fh) { + return fread(data, sizeof(uint8_t), sz, fh) == (sz * sizeof(uint8_t)); +} + +static bool file_reader(cmp_ctx_t *ctx, void *data, size_t limit) { + return read_bytes(data, limit, (FILE *)ctx->buf); +} + +static bool file_skipper(cmp_ctx_t *ctx, size_t count) { + return fseek((FILE *)ctx->buf, count, SEEK_CUR); +} + +static size_t file_writer(cmp_ctx_t *ctx, const void *data, size_t count) { + return fwrite(data, sizeof(uint8_t), count, (FILE *)ctx->buf); +} + +static void error_and_exit(const char *msg) { + fprintf(stderr, "%s\n\n", msg); + exit(EXIT_FAILURE); +} + +int main(void) { + FILE *fh = NULL; + cmp_ctx_t cmp = {0}; + uint32_t array_size = 0; + uint32_t str_size = 0; + char hello[6] = {0}; + char message_pack[12] = {0}; + + fh = fopen("cmp_data.dat", "w+b"); + + if (fh == NULL) { + error_and_exit("Error opening data.dat"); + } + + cmp_init(&cmp, fh, file_reader, file_skipper, file_writer); + + if (!cmp_write_array(&cmp, 2)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_str(&cmp, "Hello", 5)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_str(&cmp, "MessagePack", 11)) { + error_and_exit(cmp_strerror(&cmp)); + } + + rewind(fh); + + if (!cmp_read_array(&cmp, &array_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + /* You can read the str byte size and then read str bytes... */ + + if (!cmp_read_str_size(&cmp, &str_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (str_size > (sizeof(hello) - 1)) { + error_and_exit("Packed 'hello' length too long\n"); + } + + if (!read_bytes(hello, str_size, fh)) { + error_and_exit(cmp_strerror(&cmp)); + } + + /* + * ...or you can set the maximum number of bytes to read and do it all in + * one call + */ + + str_size = sizeof(message_pack); + if (!cmp_read_str(&cmp, message_pack, &str_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + printf("Array Length: %u.\n", array_size); + printf("[\"%s\", \"%s\"]\n", hello, message_pack); + + fclose(fh); + + return EXIT_SUCCESS; +} + diff --git a/local_pod_repo/cmp/cmp/examples/example2.c b/local_pod_repo/cmp/cmp/examples/example2.c new file mode 100644 index 0000000..8859326 --- /dev/null +++ b/local_pod_repo/cmp/cmp/examples/example2.c @@ -0,0 +1,531 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include + +#include "cmp.h" + +static bool read_bytes(void *data, size_t sz, FILE *fh) { + return fread(data, sizeof(uint8_t), sz, fh) == (sz * sizeof(uint8_t)); +} + +static bool file_reader(cmp_ctx_t *ctx, void *data, size_t limit) { + return read_bytes(data, limit, (FILE *)ctx->buf); +} + +static bool file_skipper(cmp_ctx_t *ctx, size_t count) { + return fseek((FILE *)ctx->buf, count, SEEK_CUR); +} + +static size_t file_writer(cmp_ctx_t *ctx, const void *data, size_t count) { + return fwrite(data, sizeof(uint8_t), count, (FILE *)ctx->buf); +} + +static void error_and_exit(const char *msg) { + fprintf(stderr, "%s\n\n", msg); + exit(EXIT_FAILURE); +} + +int main(void) { + FILE *fh = {0}; + cmp_ctx_t cmp = {0}; + uint16_t year = 1983; + uint8_t month = 5; + uint8_t day = 28; + int64_t sint = 0; + uint64_t uint = 0; + float flt = 0.0f; + double dbl = 0.0; + bool boolean = false; + uint8_t fake_bool = 0; + uint32_t string_size = 0; + uint32_t array_size = 0; + uint32_t binary_size = 0; + uint32_t map_size = 0; + int8_t ext_type = 0; + uint32_t ext_size = 0; + char sbuf[12] = {0}; + + fh = fopen("cmp_data.dat", "w+b"); + + if (fh == NULL) { + error_and_exit("Error opening data.dat"); + } + + cmp_init(&cmp, fh, file_reader, file_skipper, file_writer); + + /* + * When you write an array, you first specify the number of array + * elements, then you write that many elements. + */ + if (!cmp_write_array(&cmp, 9)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_sint(&cmp, -14)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_uint(&cmp, 38)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_float(&cmp, 1.8f)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_double(&cmp, 300.4)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_nil(&cmp)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_true(&cmp)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_false(&cmp)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_bool(&cmp, false)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_u8_as_bool(&cmp, 1)) { + error_and_exit(cmp_strerror(&cmp)); + } + + /* Array full */ + + /* + * Maps work similar to arrays, but the length is in "pairs", so this + * writes 2 pairs to the map. Subsequently, pairs are written in key, + * value order. + */ + + if (!cmp_write_map(&cmp, 2)) { + error_and_exit(cmp_strerror(&cmp)); + } + + /* You can write string data all at once... */ + + if (!cmp_write_str(&cmp, "Greeting", 8)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_str(&cmp, "Hello", 5)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_str(&cmp, "Name", 4)) { + error_and_exit(cmp_strerror(&cmp)); + } + + /* ...or in chunks */ + + if (!cmp_write_str_marker(&cmp, 5)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (file_writer(&cmp, "Li", 2) != 2) { + error_and_exit(strerror(errno)); + } + + if (file_writer(&cmp, "nus", 3) != 3) { + error_and_exit(strerror(errno)); + } + + /* Map full */ + + /* Binary data functions the same as string data */ + + if (!cmp_write_bin(&cmp, "MessagePack", 11)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_write_bin_marker(&cmp, 8)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (file_writer(&cmp, "is ", 3) != 3) { + error_and_exit(strerror(errno)); + } + + if (file_writer(&cmp, "great", 5) != 5) { + error_and_exit(strerror(errno)); + } + + /* + * With extended types, you can create your own custom types. Here we + * create a simple date type. + */ + + /* cmp_write_ext_marker(type, size) */ + if (!cmp_write_ext_marker(&cmp, 1, 4)) { + error_and_exit(cmp_strerror(&cmp)); + } + + file_writer(&cmp, &year, sizeof(uint16_t)); + file_writer(&cmp, &month, sizeof(uint8_t)); + file_writer(&cmp, &day, sizeof(uint8_t)); + + /* Now we can read the data back just as easily */ + + rewind(fh); + + if (!cmp_read_array(&cmp, &array_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (array_size != 9) { + error_and_exit("Array size was not 9"); + } + + if (!cmp_read_sinteger(&cmp, &sint)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (sint != -14) { + error_and_exit("Signed int was not -14"); + } + + if (!cmp_read_uinteger(&cmp, &uint)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (uint != 38) { + error_and_exit("Unsigned int was not 38"); + } + + if (!cmp_read_float(&cmp, &flt)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (flt != 1.8f) { + error_and_exit("Float was not 1.8f"); + } + + if (!cmp_read_double(&cmp, &dbl)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (dbl != 300.4) { + error_and_exit("Double was not 300.f"); + } + + if (!cmp_read_nil(&cmp)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!cmp_read_bool(&cmp, &boolean)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (boolean != true) { + error_and_exit("First boolean was not true"); + } + + if (!cmp_read_bool(&cmp, &boolean)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (boolean != false) { + error_and_exit("Second boolean was not false"); + } + + if (!cmp_read_bool(&cmp, &boolean)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (boolean != false) { + error_and_exit("Third boolean was not false"); + } + + if (!cmp_read_bool_as_u8(&cmp, &fake_bool)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (fake_bool != 1) { + fprintf(stderr, "%u.\n", fake_bool); + error_and_exit("Third boolean (u8) was not 1"); + } + + if (!cmp_read_map(&cmp, &map_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (map_size != 2) { + error_and_exit("Map size was not 2"); + } + + /* + * String reading here. Note that normally strings are encoded using + * UTF-8. I have cleverly restricted this example to ASCII, which overlaps + * UTF-8 encoding, but this must not be assumed in real-world code. + * + * You can read strings in two ways. Either you can read the string's size + * in bytes and then read the bytes manually... + */ + + if (!cmp_read_str_size(&cmp, &string_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (string_size != 8) { + error_and_exit("Greeting string key size was not 8"); + } + + if (!read_bytes(sbuf, 8, fh)) { + error_and_exit(strerror(errno)); + } + + sbuf[string_size] = 0; + + if (strncmp(sbuf, "Greeting", 8) != 0) { + error_and_exit("Greeting string key name was not 'Greeting'"); + } + + /* + * ...or you can set the maximum number of bytes to read and do it all in + * one call. cmp_read_str will write no more than "size" bytes, including + * the terminating NULL, to the passed buffer. If the string's size + * exceeds the passed buffer size, the "size" input is set to the number of + * bytes necessary, not including the terminating NULL. Otherwise, the + * "size" input is set to the number of bytes written, not including the + * terminating NULL. + */ + + string_size = sizeof(sbuf); + if (!cmp_read_str(&cmp, sbuf, &string_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (strncmp(sbuf, "Hello", 5) != 0) { + error_and_exit("Greeting string value was not 'Hello'"); + } + + string_size = sizeof(sbuf); + if (!cmp_read_str(&cmp, sbuf, &string_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (strncmp(sbuf, "Name", 4) != 0) { + error_and_exit("Name key name was not 'Name'"); + } + + string_size = sizeof(sbuf); + if (!cmp_read_str(&cmp, sbuf, &string_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (strncmp(sbuf, "Linus", 5) != 0) { + error_and_exit("Name key value was not 'Linus'"); + } + + memset(sbuf, 0, sizeof(sbuf)); + binary_size = sizeof(sbuf); + if (!cmp_read_bin(&cmp, sbuf, &binary_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (memcmp(sbuf, "MessagePack", 11) != 0) { + error_and_exit("1st binary value was not 'MessagePack'"); + } + + memset(sbuf, 0, sizeof(sbuf)); + binary_size = sizeof(sbuf); + if (!cmp_read_bin(&cmp, sbuf, &binary_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (memcmp(sbuf, "is great", 8) != 0) { + error_and_exit("2nd binary value was not 'is great'"); + } + + if (!cmp_read_ext_marker(&cmp, &ext_type, &ext_size)) { + error_and_exit(cmp_strerror(&cmp)); + } + + if (!read_bytes(&year, sizeof(uint16_t), fh)) { + error_and_exit(strerror(errno)); + } + + if (!read_bytes(&month, sizeof(uint8_t), fh)) { + error_and_exit(strerror(errno)); + } + + if (!read_bytes(&day, sizeof(uint8_t), fh)) { + error_and_exit(strerror(errno)); + } + + if (year != 1983) { + error_and_exit("Year was not 1983"); + } + + if (month != 5) { + error_and_exit("Month was not 5"); + } + + if (day != 28) { + error_and_exit("Day was not 28"); + } + + rewind(fh); + + /* Alternately, you can read objects until the stream is empty */ + while (1) { + cmp_object_t obj; + + if (!cmp_read_object(&cmp, &obj)) { + if (feof(fh)) { + break; + } + + error_and_exit(cmp_strerror(&cmp)); + } + + switch (obj.type) { + case CMP_TYPE_POSITIVE_FIXNUM: + case CMP_TYPE_UINT8: + printf("Unsigned Integer: %u\n", obj.as.u8); + break; + case CMP_TYPE_FIXMAP: + case CMP_TYPE_MAP16: + case CMP_TYPE_MAP32: + printf("Map: %u\n", obj.as.map_size); + break; + case CMP_TYPE_FIXARRAY: + case CMP_TYPE_ARRAY16: + case CMP_TYPE_ARRAY32: + printf("Array: %u\n", obj.as.array_size); + break; + case CMP_TYPE_FIXSTR: + case CMP_TYPE_STR8: + case CMP_TYPE_STR16: + case CMP_TYPE_STR32: + if (!read_bytes(sbuf, obj.as.str_size, fh)) { + error_and_exit(strerror(errno)); + } + sbuf[obj.as.str_size] = 0; + printf("String: %s\n", sbuf); + break; + case CMP_TYPE_BIN8: + case CMP_TYPE_BIN16: + case CMP_TYPE_BIN32: + memset(sbuf, 0, sizeof(sbuf)); + if (!read_bytes(sbuf, obj.as.bin_size, fh)) { + error_and_exit(strerror(errno)); + } + printf("Binary: %s\n", sbuf); + break; + case CMP_TYPE_NIL: + printf("NULL\n"); + break; + case CMP_TYPE_BOOLEAN: + if (obj.as.boolean) { + printf("Boolean: true\n"); + } + else { + printf("Boolean: false\n"); + } + break; + case CMP_TYPE_EXT8: + case CMP_TYPE_EXT16: + case CMP_TYPE_EXT32: + case CMP_TYPE_FIXEXT1: + case CMP_TYPE_FIXEXT2: + case CMP_TYPE_FIXEXT4: + case CMP_TYPE_FIXEXT8: + case CMP_TYPE_FIXEXT16: + if (obj.as.ext.type == 1) { /* Date object */ + if (!read_bytes(&year, sizeof(uint16_t), fh)) { + error_and_exit(strerror(errno)); + } + + if (!read_bytes(&month, sizeof(uint8_t), fh)) { + error_and_exit(strerror(errno)); + } + + if (!read_bytes(&day, sizeof(uint8_t), fh)) { + error_and_exit(strerror(errno)); + } + + printf("Date: %u/%u/%u\n", year, month, day); + } + else { + printf("Extended type {%d, %u}: ", + obj.as.ext.type, obj.as.ext.size + ); + while (obj.as.ext.size--) { + read_bytes(sbuf, sizeof(uint8_t), fh); + printf("%02x ", sbuf[0]); + } + printf("\n"); + } + break; + case CMP_TYPE_FLOAT: + printf("Float: %f\n", obj.as.flt); + break; + case CMP_TYPE_DOUBLE: + printf("Double: %f\n", obj.as.dbl); + break; + case CMP_TYPE_UINT16: + printf("Unsigned Integer: %u\n", obj.as.u16); + break; + case CMP_TYPE_UINT32: + printf("Unsigned Integer: %u\n", obj.as.u32); + break; + case CMP_TYPE_UINT64: + printf("Unsigned Integer: %" PRIu64 "\n", obj.as.u64); + break; + case CMP_TYPE_NEGATIVE_FIXNUM: + case CMP_TYPE_SINT8: + printf("Signed Integer: %d\n", obj.as.s8); + break; + case CMP_TYPE_SINT16: + printf("Signed Integer: %d\n", obj.as.s16); + break; + case CMP_TYPE_SINT32: + printf("Signed Integer: %d\n", obj.as.s32); + break; + case CMP_TYPE_SINT64: + printf("Signed Integer: %" PRId64 "\n", obj.as.s64); + break; + default: + printf("Unrecognized object type %u\n", obj.type); + break; + } + } + + fclose(fh); + + return EXIT_SUCCESS; +} diff --git a/local_pod_repo/cmp/cmp/test/buf.c b/local_pod_repo/cmp/cmp/test/buf.c new file mode 100644 index 0000000..e2c247f --- /dev/null +++ b/local_pod_repo/cmp/cmp/test/buf.c @@ -0,0 +1,569 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "buf.h" + + +static void check_cursor(buf_t *buf) { + if (buf->cursor > buf->size) + buf->size = buf->cursor; +} + +buf_t* M_BufferNew(void) { + buf_t *buf = calloc(1, sizeof(buf_t)); + + if (buf == NULL) + error_and_exit("M_BufferNew: Calloc returned NULL."); + + M_BufferInit(buf); + + return buf; +} + +buf_t* M_BufferNewWithCapacity(size_t capacity) { + buf_t *buf = calloc(1, sizeof(buf_t)); + + if (buf == NULL) + error_and_exit("M_BufferNew: calloc returned NULL"); + + M_BufferInitWithCapacity(buf, capacity); + + return buf; +} + +void M_BufferInit(buf_t *buf) { + buf->size = 0; + buf->capacity = 0; + buf->cursor = 0; + buf->data = NULL; +} + +void M_BufferInitWithCapacity(buf_t *buf, size_t capacity) { + M_BufferInit(buf); + M_BufferEnsureTotalCapacity(buf, capacity); +} + +size_t M_BufferGetCapacity(const buf_t *buf) { + return buf->capacity; +} + +size_t M_BufferGetSize(const buf_t *buf) { + return buf->size; +} + +size_t M_BufferGetCursor(const buf_t *buf) { + return buf->cursor; +} + +char* M_BufferGetData(const buf_t *buf) { + return buf->data; +} + +char* M_BufferGetDataAtCursor(const buf_t *buf) { + return buf->data + buf->cursor; +} + +void M_BufferEnsureCapacity(buf_t *buf, size_t capacity) { + size_t needed_capacity = buf->cursor + capacity; + + if (buf->capacity < needed_capacity) { + buf->data = realloc(buf->data, needed_capacity * sizeof(uint8_t)); + + if (buf->data == NULL) { + error_and_exit( + "M_BufferEnsureCapacity: Reallocating buffer data failed" + ); + } + + memset(buf->data + buf->capacity, 0, needed_capacity - buf->capacity); + buf->capacity = needed_capacity; + } +} + +void M_BufferEnsureTotalCapacity(buf_t *buf, size_t capacity) { + if (buf->capacity < capacity) { + size_t old_capacity = buf->capacity; + + buf->capacity = capacity; + buf->data = realloc(buf->data, buf->capacity * sizeof(uint8_t)); + + if (buf->data == NULL) + error_and_exit("M_BufferEnsureCapacity: Allocating buffer data failed"); + + memset(buf->data + old_capacity, 0, buf->capacity - old_capacity); + } +} + +void M_BufferCopy(buf_t *dst, const buf_t *src) { + M_BufferSetData(dst, M_BufferGetData(src), M_BufferGetSize(src)); +} + +void M_BufferCursorCopy(buf_t *dst, const buf_t *src) { + M_BufferWrite( + dst, + M_BufferGetDataAtCursor(src), + M_BufferGetSize(src) - (M_BufferGetCursor(src) - 1) + ); +} + +bool M_BufferMove(buf_t *buf, size_t dpos, size_t spos, size_t count) { + if ((spos + count) > M_BufferGetSize(buf)) + return false; + + M_BufferEnsureTotalCapacity(buf, dpos + count); + + memmove(M_BufferGetData(buf) + dpos, M_BufferGetData(buf) + spos, count); + + return true; +} + +void M_BufferSetData(buf_t *buf, const void *data, size_t size) { + M_BufferClear(buf); + M_BufferEnsureTotalCapacity(buf, size); + M_BufferWrite(buf, data, size); +} + +void M_BufferSetString(buf_t *buf, const char *data, size_t length) { + M_BufferClear(buf); + M_BufferWriteString(buf, data, length); +} + +bool M_BufferSetFile(buf_t *buf, const char *filename) { + FILE *fp; + size_t length; + bool out = false; + + if ((fp = fopen(filename, "rb")) == NULL) + return false; + + fseek(fp, 0, SEEK_END); + length = ftell(fp); + fseek(fp, 0, SEEK_SET); + + M_BufferClear(buf); + M_BufferEnsureTotalCapacity(buf, length); + + if (fread(buf->data, sizeof(uint8_t), length, fp) == length) { + buf->cursor = length; + buf->size = length; + out = true; + } + else { + M_BufferClear(buf); + out = false; + } + + fclose(fp); + return out; +} + +bool M_BufferSeek(buf_t *buf, size_t pos) { + if (pos > buf->size) + return false; + + buf->cursor = pos; + return true; +} + +bool M_BufferSeekBackward(buf_t *buf, size_t count) { + if (count > buf->cursor) + return false; + + buf->cursor -= count; + return true; +} + +bool M_BufferSeekForward(buf_t *buf, size_t count) { + if (buf->cursor + count > buf->size) + return false; + + buf->cursor += count; + return true; +} + +uint8_t M_BufferPeek(const buf_t *buf) { + return *(buf->data + buf->cursor); +} + +void M_BufferWrite(buf_t *buf, const void *data, size_t size) { + M_BufferEnsureCapacity(buf, size); + memcpy(buf->data + buf->cursor, data, size); + buf->cursor += size; + + check_cursor(buf); +} + +void M_BufferWriteBool(buf_t *buf, bool b) { + M_BufferWriteBools(buf, &b, 1); +} + +void M_BufferWriteBools(buf_t *buf, const bool *bools, size_t count) { + M_BufferEnsureCapacity(buf, count * sizeof(bool)); + M_BufferWriteChars(buf, (char *)bools, count * sizeof(bool)); +} + +void M_BufferWriteChar(buf_t *buf, char c) { + M_BufferWriteChars(buf, &c, 1); +} + +void M_BufferWriteChars(buf_t *buf, const char *chars, size_t count) { + M_BufferWrite(buf, chars, count * sizeof(char)); +} + +void M_BufferWriteUChar(buf_t *buf, unsigned char c) { + M_BufferWriteUChars(buf, &c, 1); +} + +void M_BufferWriteUChars(buf_t *buf, const unsigned char *uchars, size_t count) { + M_BufferWrite(buf, uchars, count * sizeof(unsigned char)); +} + +void M_BufferWriteShort(buf_t *buf, short s) { + M_BufferWriteShorts(buf, &s, 1); +} + +void M_BufferWriteShorts(buf_t *buf, const short *shorts, size_t count) { + M_BufferEnsureCapacity(buf, count * sizeof(short)); + M_BufferWriteChars(buf, (char *)shorts, count * sizeof(short)); +} + +void M_BufferWriteUShort(buf_t *buf, unsigned short s) { + M_BufferWriteUShorts(buf, &s, 1); +} + +void M_BufferWriteUShorts(buf_t *buf, const unsigned short *ushorts, + size_t count) { + M_BufferEnsureCapacity(buf, count * sizeof(unsigned short)); + M_BufferWriteChars(buf, (char *)ushorts, count * sizeof(unsigned short)); +} + +void M_BufferWriteInt(buf_t *buf, int i) { + M_BufferWriteInts(buf, &i, 1); +} + +void M_BufferWriteInts(buf_t *buf, const int *ints, size_t count) { + M_BufferEnsureCapacity(buf, count * sizeof(int)); + M_BufferWriteChars(buf, (char *)ints, count * sizeof(int)); +} + +void M_BufferWriteUInt(buf_t *buf, unsigned int s) { + M_BufferWriteUInts(buf, &s, 1); +} + +void M_BufferWriteUInts(buf_t *buf, const unsigned int *uints, size_t count) { + M_BufferEnsureCapacity(buf, count * sizeof(unsigned int)); + M_BufferWriteChars(buf, (char *)uints, count * sizeof(unsigned int)); +} + +void M_BufferWriteLong(buf_t *buf, int64_t l) { + M_BufferWriteLongs(buf, &l, 1); +} + +void M_BufferWriteLongs(buf_t *buf, const int64_t *longs, size_t count) { + M_BufferEnsureCapacity(buf, count * sizeof(int64_t)); + M_BufferWriteChars(buf, (char *)longs, count * sizeof(int64_t)); +} + +void M_BufferWriteULong(buf_t *buf, uint64_t l) { + M_BufferWriteULongs(buf, &l, 1); +} + +void M_BufferWriteULongs(buf_t *buf, const uint64_t *ulongs, size_t count) { + M_BufferEnsureCapacity(buf, count * sizeof(int64_t)); + M_BufferWriteChars(buf, (char *)ulongs, count * sizeof(uint64_t)); +} + +#ifndef CMP_NO_FLOAT +void M_BufferWriteFloat(buf_t *buf, float f) { + M_BufferWriteFloats(buf, &f, 1); +} + +void M_BufferWriteFloats(buf_t *buf, const float *floats, size_t count) { + M_BufferEnsureCapacity(buf, count * sizeof(float)); + M_BufferWriteChars(buf, (char *)floats, count * sizeof(floats)); +} + +void M_BufferWriteDouble(buf_t *buf, double d) { + M_BufferWriteDoubles(buf, &d, 1); +} + +void M_BufferWriteDoubles(buf_t *buf, const double *doubles, size_t count) { + M_BufferEnsureCapacity(buf, count * sizeof(double)); + M_BufferWriteChars(buf, (char *)doubles, count * sizeof(doubles)); +} +#endif + +void M_BufferWriteString(buf_t *buf, const char *string, size_t length) { + M_BufferEnsureCapacity(buf, length + 1); + strncpy(buf->data + buf->cursor, string, length + 1); + buf->cursor += (length + 1); + + check_cursor(buf); +} + +void M_BufferWriteZeros(buf_t *buf, size_t count) { + M_BufferEnsureCapacity(buf, count); + + for (size_t i = 0; i < count; i++) + buf->data[buf->cursor++] = 0; + + check_cursor(buf); +} + +bool M_BufferEqualsString(const buf_t *buf, const char *s) { + if (strncmp(buf->data + buf->cursor, s, buf->size - buf->cursor) == 0) + return true; + + return false; +} + +bool M_BufferEqualsData(const buf_t *buf, const void *d, size_t size) { + if (buf->cursor + size > buf->size) + return false; + + if (memcmp(buf->data + buf->cursor, d, size) == 0) + return true; + + return false; +} + +bool M_BufferRead(buf_t *buf, void *data, size_t size) { + if (buf->cursor + size > buf->size) + return false; + + if (size == 1) + *((char *)data) = *(buf->data + buf->cursor); + else + memcpy(data, buf->data + buf->cursor, size); + + buf->cursor += size; + + return true; +} + +bool M_BufferReadBool(buf_t *buf, bool *b) { + return M_BufferReadBools(buf, b, 1); +} + +bool M_BufferReadBools(buf_t *buf, bool *b, size_t count) { + return M_BufferRead(buf, b, count * sizeof(bool)); +} + +bool M_BufferReadChar(buf_t *buf, char *c) { + return M_BufferReadChars(buf, c, 1); +} + +bool M_BufferReadChars(buf_t *buf, char *c, size_t count) { + return M_BufferRead(buf, c, count * sizeof(char)); +} + +bool M_BufferReadUChar(buf_t *buf, unsigned char *c) { + return M_BufferReadUChars(buf, c, 1); +} + +bool M_BufferReadUChars(buf_t *buf, unsigned char *c, size_t count) { + return M_BufferRead(buf, c, count * sizeof(unsigned char)); +} + +bool M_BufferReadShort(buf_t *buf, short *s) { + return M_BufferReadShorts(buf, s, 1); +} + +bool M_BufferReadShorts(buf_t *buf, short *s, size_t count) { + return M_BufferRead(buf, s, count * sizeof(short)); +} + +bool M_BufferReadUShort(buf_t *buf, unsigned short *s) { + return M_BufferReadUShorts(buf, s, 1); +} + +bool M_BufferReadUShorts(buf_t *buf, unsigned short *s, size_t count) { + return M_BufferRead(buf, s, count * sizeof(unsigned short)); +} + +bool M_BufferReadInt(buf_t *buf, int *i) { + return M_BufferReadInts(buf, i, 1); +} + +bool M_BufferReadInts(buf_t *buf, int *i, size_t count) { + return M_BufferRead(buf, i, count * sizeof(int)); +} + +bool M_BufferReadUInt(buf_t *buf, unsigned int *i) { + return M_BufferReadUInts(buf, i, 1); +} + +bool M_BufferReadUInts(buf_t *buf, unsigned int *i, size_t count) { + return M_BufferRead(buf, i, count * sizeof(unsigned int)); +} + +bool M_BufferReadLong(buf_t *buf, int64_t *l) { + return M_BufferReadLongs(buf, l, 1); +} + +bool M_BufferReadLongs(buf_t *buf, int64_t *l, size_t count) { + return M_BufferRead(buf, l, count * sizeof(int64_t)); +} + +bool M_BufferReadULong(buf_t *buf, uint64_t *l) { + return M_BufferReadULongs(buf, l, 1); +} + +bool M_BufferReadULongs(buf_t *buf, uint64_t *l, size_t count) { + return M_BufferRead(buf, l, count * sizeof(uint64_t)); +} + +#ifndef CMP_NO_FLOAT +bool M_BufferReadFloat(buf_t *buf, float *f) { + return M_BufferReadFloats(buf, f, 1); +} + +bool M_BufferReadFloats(buf_t *buf, float *f, size_t count) { + return M_BufferRead(buf, f, count * sizeof(float)); +} + +bool M_BufferReadDouble(buf_t *buf, double *d) { + return M_BufferReadDoubles(buf, d, 1); +} + +bool M_BufferReadDoubles(buf_t *buf, double *d, size_t count) { + return M_BufferRead(buf, d, count * sizeof(double)); +} +#endif + +bool M_BufferReadString(buf_t *buf, char *s, size_t length) { + return M_BufferRead(buf, s, length); +} + +bool M_BufferReadStringDup(const buf_t *buf, char **s) { + char *d = buf->data + buf->cursor; + size_t length = strlen(d); + + if (buf->cursor + length > buf->size) + return false; + + (*s) = strdup(buf->data + buf->cursor); + return true; +} + +bool M_BufferCopyString(buf_t *dst, const buf_t *src) { + char *s = src->data + src->cursor; + size_t length = strlen(s); + + if (src->cursor + length >= src->size) + return false; + + M_BufferWriteString(dst, s, length); + return true; +} + +void M_BufferCompact(buf_t *buf) { + if (buf->size < buf->capacity) { + char *new_buf = calloc(buf->size, sizeof(uint8_t)); + + if (buf->data == NULL) + error_and_exit("M_BufferCompact: Allocating new buffer data failed"); + + memcpy(new_buf, buf->data, buf->size); + free(buf->data); + buf->data = new_buf; + buf->capacity = buf->size; + if (buf->cursor > buf->size) + buf->cursor = buf->size; + } +} + +void M_BufferTruncate(buf_t *buf, size_t new_size) { + size_t old_size = buf->size; + + if (new_size >= buf->size) + errorf_and_exit("M_BufferTruncate: %zu >= %zu.", new_size, buf->size); + + memset(buf->data + new_size, 0, old_size - new_size); + buf->size = new_size; + + if (buf->cursor >= buf->size) + buf->cursor = buf->size - 1; +} + +void M_BufferZero(const buf_t *buf) { + memset(buf->data, 0, buf->capacity); +} + +void M_BufferClear(buf_t *buf) { + buf->size = 0; + buf->cursor = 0; + M_BufferZero(buf); +} + +void M_BufferFree(buf_t *buf) { + free(buf->data); + memset(buf, 0, sizeof(buf_t)); + buf->data = NULL; +} + +void M_BufferPrint(const buf_t *buf) { + printf("Buffer capacity, size and cursor: [%zu, %zu, %zu].\n", + buf->capacity, + buf->size, + buf->cursor + ); + + for (size_t i = 0; i < MIN(64, buf->size); i++) { + printf("%02X ", (unsigned char)buf->data[i]); + + if ((i > 0) && (((i + 1) % 25) == 0)) + printf("\n"); + } + + printf("\n"); +} + +void M_BufferPrintAll(const buf_t *buf) { + printf("Buffer capacity, size and cursor: [%zu, %zu, %zu].\n", + buf->capacity, + buf->size, + buf->cursor + ); + + for (size_t i = 0; i < buf->size; i++) { + printf("%02X ", (unsigned char)buf->data[i]); + + if ((i > 0) && (((i + 1) % 25) == 0)) + printf("\n"); + } + + printf("\n"); +} + +/* vi: set et ts=2 sw=2: */ + diff --git a/local_pod_repo/cmp/cmp/test/buf.h b/local_pod_repo/cmp/cmp/test/buf.h new file mode 100644 index 0000000..6dd5f72 --- /dev/null +++ b/local_pod_repo/cmp/cmp/test/buf.h @@ -0,0 +1,138 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#ifndef M_BUF_H__ +#define M_BUF_H__ + +typedef struct buf_s { + size_t capacity; + size_t size; + size_t cursor; + char *data; +} buf_t; + +buf_t* M_BufferNew(void); +buf_t* M_BufferNewWithCapacity(size_t capacity); +void M_BufferInit(buf_t *buf); +void M_BufferInitWithCapacity(buf_t *buf, size_t capacity); + +size_t M_BufferGetCapacity(const buf_t *buf); +size_t M_BufferGetSize(const buf_t *buf); +size_t M_BufferGetCursor(const buf_t *buf); +char* M_BufferGetData(const buf_t *buf); +char* M_BufferGetDataAtCursor(const buf_t *buf); + +void M_BufferEnsureCapacity(buf_t *buf, size_t capacity); +void M_BufferEnsureTotalCapacity(buf_t *buf, size_t capacity); + +void M_BufferCopy(buf_t *dst, const buf_t *src); +void M_BufferCursorCopy(buf_t *dst, const buf_t *src); +bool M_BufferMove(buf_t *buf, size_t dpos, size_t spos, size_t count); + +void M_BufferSetData(buf_t *buf, const void *data, size_t size); +void M_BufferSetString(buf_t *buf, const char *data, size_t length); +bool M_BufferSetFile(buf_t *buf, const char *filename); + +bool M_BufferSeek(buf_t *buf, size_t pos); +bool M_BufferSeekBackward(buf_t *buf, size_t count); +bool M_BufferSeekForward(buf_t *buf, size_t count); + +uint8_t M_BufferPeek(const buf_t *buf); + +void M_BufferWrite(buf_t *buf, const void *data, size_t size); +void M_BufferWriteBool(buf_t *buf, bool b); +void M_BufferWriteBools(buf_t *buf, const bool *bools, size_t count); +void M_BufferWriteChar(buf_t *buf, char c); +void M_BufferWriteChars(buf_t *buf, const char *chars, size_t count); +void M_BufferWriteUChar(buf_t *buf, unsigned char c); +void M_BufferWriteUChars(buf_t *buf, const unsigned char *uchars, + size_t count); +void M_BufferWriteShort(buf_t *buf, short s); +void M_BufferWriteShorts(buf_t *buf, const short *shorts, size_t count); +void M_BufferWriteUShort(buf_t *buf, unsigned short s); +void M_BufferWriteUShorts(buf_t *buf, const unsigned short *ushorts, + size_t count); +void M_BufferWriteInt(buf_t *buf, int i); +void M_BufferWriteInts(buf_t *buf, const int *ints, size_t count); +void M_BufferWriteUInt(buf_t *buf, unsigned int i); +void M_BufferWriteUInts(buf_t *buf, const unsigned int *ints, + size_t count); +void M_BufferWriteLong(buf_t *buf, int64_t l); +void M_BufferWriteLongs(buf_t *buf, const int64_t *longs, size_t count); +void M_BufferWriteULong(buf_t *buf, uint64_t l); +void M_BufferWriteULongs(buf_t *buf, const uint64_t *longs, size_t count); +#ifndef CMP_NO_FLOAT +void M_BufferWriteFloat(buf_t *buf, float f); +void M_BufferWriteFloats(buf_t *buf, const float *floats, size_t count); +void M_BufferWriteDouble(buf_t *buf, double d); +void M_BufferWriteDoubles(buf_t *buf, const double *doubles, size_t count); +#endif +void M_BufferWriteString(buf_t *buf, const char *string, size_t length); +void M_BufferWriteZeros(buf_t *buf, size_t count); + +bool M_BufferEqualsString(const buf_t *buf, const char *s); +bool M_BufferEqualsData(const buf_t *buf, const void *d, size_t size); + +bool M_BufferRead(buf_t *buf, void *data, size_t size); +bool M_BufferReadBool(buf_t *buf, bool *b); +bool M_BufferReadBools(buf_t *buf, bool *b, size_t count); +bool M_BufferReadChar(buf_t *buf, char *c); +bool M_BufferReadChars(buf_t *buf, char *c, size_t count); +bool M_BufferReadUChar(buf_t *buf, unsigned char *c); +bool M_BufferReadUChars(buf_t *buf, unsigned char *c, size_t count); +bool M_BufferReadShort(buf_t *buf, short *s); +bool M_BufferReadShorts(buf_t *buf, short *shorts, size_t count); +bool M_BufferReadUShort(buf_t *buf, unsigned short *s); +bool M_BufferReadUShorts(buf_t *buf, unsigned short *s, size_t count); +bool M_BufferReadInt(buf_t *buf, int *i); +bool M_BufferReadInts(buf_t *buf, int *i, size_t count); +bool M_BufferReadUInt(buf_t *buf, unsigned int *i); +bool M_BufferReadUInts(buf_t *buf, unsigned int *i, size_t count); +bool M_BufferReadLong(buf_t *buf, int64_t *l); +bool M_BufferReadLongs(buf_t *buf, int64_t *l, size_t count); +bool M_BufferReadULong(buf_t *buf, uint64_t *l); +bool M_BufferReadULongs(buf_t *buf, uint64_t *l, size_t count); +#ifndef CMP_NO_FLOAT +bool M_BufferReadFloat(buf_t *buf, float *f); +bool M_BufferReadFloats(buf_t *buf, float *f, size_t count); +bool M_BufferReadDouble(buf_t *buf, double *d); +bool M_BufferReadDoubles(buf_t *buf, double *d, size_t count); +#endif +bool M_BufferReadString(buf_t *buf, char *s, size_t length); +bool M_BufferReadStringDup(const buf_t *buf, char **s); +bool M_BufferCopyString(buf_t *dst, const buf_t *src); + +void M_BufferCompact(buf_t *buf); +void M_BufferTruncate(buf_t *buf, size_t new_size); +void M_BufferZero(const buf_t *buf); +void M_BufferClear(buf_t *buf); +void M_BufferFree(buf_t *buf); + +void M_BufferPrint(const buf_t *buf); +void M_BufferPrintAll(const buf_t *buf); + +#endif + +/* vi: set et ts=2 sw=2: */ + diff --git a/local_pod_repo/cmp/cmp/test/cases.mpac b/local_pod_repo/cmp/cmp/test/cases.mpac new file mode 100644 index 0000000..5ec08c6 Binary files /dev/null and b/local_pod_repo/cmp/cmp/test/cases.mpac differ diff --git a/local_pod_repo/cmp/cmp/test/profile.c b/local_pod_repo/cmp/cmp/test/profile.c new file mode 100644 index 0000000..80bf9d0 --- /dev/null +++ b/local_pod_repo/cmp/cmp/test/profile.c @@ -0,0 +1,55 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include + +#include "tests.h" + +int main(void) { + test_msgpack(NULL); + test_fixedint(NULL); + test_numbers(NULL); + test_nil(NULL); + test_boolean(NULL); + test_bin(NULL); + test_string(NULL); + test_array(NULL); + test_map(NULL); + test_ext(NULL); + test_obj(NULL); + +#ifndef CMP_NO_FLOAT + test_float_flip(NULL); +#endif + + test_skipping(NULL); + test_deprecated_limited_skipping(NULL); + test_errors(NULL); + test_version(NULL); + test_conversions(NULL); + + return EXIT_SUCCESS; +} + +/* vi: set et ts=2 sw=2: */ diff --git a/local_pod_repo/cmp/cmp/test/test.c b/local_pod_repo/cmp/cmp/test/test.c new file mode 100644 index 0000000..89d263e --- /dev/null +++ b/local_pod_repo/cmp/cmp/test/test.c @@ -0,0 +1,67 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include +#include +#include +#include + +#include + +#include "tests.h" + +int main(void) { + /* Use the old CMocka API because Travis' latest Ubuntu is Trusty */ + const UnitTest tests[17] = { + unit_test(test_msgpack), + unit_test(test_fixedint), + unit_test(test_numbers), + unit_test(test_nil), + unit_test(test_boolean), + unit_test(test_bin), + unit_test(test_string), + unit_test(test_array), + unit_test(test_map), + unit_test(test_ext), + unit_test(test_obj), + +#ifndef CMP_NO_FLOAT + unit_test(test_float_flip), +#endif + + unit_test(test_skipping), + unit_test(test_deprecated_limited_skipping), + unit_test(test_errors), + unit_test(test_version), + unit_test(test_conversions), + }; + + if (run_tests(tests)) { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/* vi: set et ts=2 sw=2: */ diff --git a/local_pod_repo/cmp/cmp/test/tests.c b/local_pod_repo/cmp/cmp/test/tests.c new file mode 100644 index 0000000..9e1046f --- /dev/null +++ b/local_pod_repo/cmp/cmp/test/tests.c @@ -0,0 +1,5951 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "buf.h" +#include "cmp.h" + +static int reader_successes = -1; +static int writer_successes = -1; +static int skipper_successes = -1; + +#ifndef CMP_NO_FLOAT + +#ifndef assert_float_equal +#define assert_float_equal(f1, f2) \ + assert_memory_equal(&(f1), &(f2), sizeof(float)) +#endif + +#ifndef assert_double_equal +#define assert_double_equal(d1, d2) \ + assert_memory_equal(&(d1), &(d2), sizeof(double)) +#endif + +#endif + +#define test_format(wfunc, rfunc, otype, ctype, in, fmt, dlen) do { \ + M_BufferClear(&buf); \ + assert_true(wfunc(&cmp, in)); \ + M_BufferSeek(&buf, 0); \ + assert_memory_equal(buf.data, fmt, dlen); \ + M_BufferSeek(&buf, 0); \ + assert_true(cmp_read_object(&cmp, &obj)); \ + assert_true(obj.as.otype == in); \ + M_BufferSeek(&buf, 0); \ + do { \ + ctype value; \ + assert_true(rfunc(&cmp, (ctype *)&value)); \ + assert_true(in == value); \ + } while (0); \ +} while (0) + +#define test_format_with_length(wfunc, rfunc, otype, in, len, fmt, dlen) \ + M_BufferClear(&buf); \ + assert_true(wfunc(&cmp, in, len)); \ + M_BufferSeek(&buf, 0); \ + assert_memory_equal(buf.data, fmt, dlen); \ + M_BufferSeek(&buf, 0); \ + assert_true(cmp_read_object(&cmp, &obj)); \ + assert_true(obj.as.otype == len); \ + M_BufferSeek(&buf, 0); \ + do { \ + char ldata[len + 1]; \ + uint32_t data_length = len + 1; \ + memset(ldata, 0, sizeof(ldata)); \ + assert_true(rfunc(&cmp, ldata, &data_length)); \ + assert_int_equal(data_length, len); \ + assert_memory_equal(ldata, in, len); \ + } while (0); + +#define test_int_format(wfunc, rfunc, otype, ctype, in, fmt, len) do { \ + ctype value; \ + M_BufferSeek(&buf, 0); \ + assert_true(wfunc(&cmp, in)); \ + assert_memory_equal(buf.data, fmt, len); \ + M_BufferSeek(&buf, 0); \ + assert_true(cmp_read_object(&cmp, &obj)); \ + assert_int_equal(obj.as.otype, in); \ + M_BufferSeek(&buf, 0); \ + assert_true(rfunc(&cmp, &value)); \ + assert_int_equal(in, value); \ +} while (0) + +#ifndef CMP_NO_FLOAT +#define test_float_format(wfunc, rfunc, otype, ctype, in, fmt, len) do { \ + ctype value; \ + M_BufferSeek(&buf, 0); \ + assert_true(wfunc(&cmp, in)); \ + assert_memory_equal(buf.data, fmt, len); \ + M_BufferSeek(&buf, 0); \ + assert_true(cmp_read_object(&cmp, &obj)); \ + assert_true(in == obj.as.otype); \ + M_BufferSeek(&buf, 0); \ + assert_true(rfunc(&cmp, &value)); \ + assert_true(in == value); \ +} while (0) + +#define test_double_format(wfunc, rfunc, otype, ctype, in, fmt, len) do { \ + ctype value; \ + M_BufferSeek(&buf, 0); \ + assert_true(wfunc(&cmp, in)); \ + assert_memory_equal(buf.data, fmt, len); \ + M_BufferSeek(&buf, 0); \ + assert_true(cmp_read_object(&cmp, &obj)); \ + assert_true(in == obj.as.otype); \ + M_BufferSeek(&buf, 0); \ + assert_true(rfunc(&cmp, &value)); \ + assert_true(in == value); \ +} while (0) +#endif + +#define test_format_no_input(wfunc, otype, fmt, dlen, out) do { \ + M_BufferClear(&buf); \ + assert_true(wfunc(&cmp)); \ + M_BufferSeek(&buf, 0); \ + assert_memory_equal(buf.data, fmt, dlen); \ + M_BufferSeek(&buf, 0); \ + assert_true(cmp_read_object(&cmp, &obj)); \ + assert_int_equal(obj.as.otype, out); \ +} while (0) + +#define obj_write(func, val) \ + M_BufferSeek(&buf, 0); \ + func(&cmp, val); \ + M_BufferSeek(&buf, 0); \ + cmp_read_object(&cmp, &obj); + +#define obj_write_no_val(func) \ + M_BufferSeek(&buf, 0); \ + func(&cmp); \ + M_BufferSeek(&buf, 0); \ + cmp_read_object(&cmp, &obj); + +#define obj_write_len(func, val, len) \ + M_BufferSeek(&buf, 0); \ + func(&cmp, val, len); \ + M_BufferSeek(&buf, 0); \ + cmp_read_object(&cmp, &obj); + +#define obj_test(func, as_func, type, ctype, val) do { \ + ctype var; \ + assert_true(func(&obj)); \ + assert_true(as_func(&obj, &var)); \ + assert_true(var == val); \ +} while (0); + +#define obj_str_test(val) do { \ + uint32_t length; \ + assert_true(cmp_object_is_str(&obj)); \ + assert_true(cmp_object_as_str(&obj, &length)); \ + assert_string_equal(M_BufferGetDataAtCursor(&buf), val); \ +} while (0); + +#define obj_to_str_test(val) do { \ + uint32_t length; \ + char data[255]; \ + assert_true(cmp_object_is_str(&obj)); \ + assert_true(cmp_object_as_str(&obj, &length)); \ + assert_true(cmp_object_to_str(&cmp, &obj, data, 255)); \ + assert_string_equal(data, val); \ +} while (0); + +#define obj_bin_test(val, inlength) do { \ + uint32_t length; \ + assert_true(cmp_object_is_bin(&obj)); \ + assert_true(cmp_object_as_bin(&obj, &length)); \ + assert_int_equal(length, inlength); \ + assert_memory_equal(M_BufferGetDataAtCursor(&buf), val, inlength); \ +} while (0); + +#define obj_to_bin_test(val, inlength) do { \ + uint32_t length; \ + unsigned char data[255]; \ + assert_true(cmp_object_is_bin(&obj)); \ + assert_true(cmp_object_as_bin(&obj, &length)); \ + assert_int_equal(length, inlength); \ + assert_true(cmp_object_to_bin(&cmp, &obj, data, 255)); \ + assert_memory_equal(data, val, length); \ +} while (0); + +#define obj_array_test(val1, val2) do { \ + uint32_t length; \ + uint64_t var1; \ + uint64_t var2; \ + assert_true(cmp_object_is_array(&obj)); \ + assert_true(cmp_object_as_array(&obj, &length)); \ + assert_int_equal(length, 2); \ + assert_true(cmp_read_uinteger(&cmp, &var1)); \ + assert_true(cmp_read_uinteger(&cmp, &var2)); \ + assert_true(var1 == val1); \ + assert_true(var2 == val2); \ +} while (0); + +#define obj_map_test(inkey, invalue) do { \ + uint32_t length; \ + uint64_t key; \ + uint64_t value; \ + assert_true(cmp_object_is_map(&obj)); \ + assert_true(cmp_object_as_map(&obj, &length)); \ + assert_int_equal(length, 1); \ + assert_true(cmp_read_uinteger(&cmp, &key)); \ + assert_true(cmp_read_uinteger(&cmp, &value)); \ + assert_true(key == inkey); \ + assert_true(value == invalue); \ +} while (0); + +#define obj_ext_test(func, as_func, type, ctype, inkey, invalue) \ + do { \ + uint32_t length; \ + uint32_t key; \ + uint32_t value; \ + assert_true(cmp_object_is_map(&obj)); \ + assert_true(cmp_object_as_map(&obj, &length)); \ + assert_int_equal(length, 1); \ + assert_true(cmp_read_uinteger(&cmp, &key)); \ + assert_true(cmp_read_uinteger(&cmp, &value)); \ + assert_true(key == inkey); \ + assert_true(value == invalue); \ + } while (0); + +#define obj_test_no_read(func, type) \ + assert_true(func(&obj)); + +#define obj_test_not(func, type) \ + assert_false(func(&obj)); + +#define test_fixext_format(wfunc, etype, esize, in, fmt, dlen) \ + M_BufferClear(&buf); \ + assert_true(wfunc(&cmp, etype, in)); \ + M_BufferSeek(&buf, 0); \ + assert_memory_equal(buf.data, fmt, dlen); \ + M_BufferSeek(&buf, 0); \ + assert_true(cmp_read_object(&cmp, &obj)); \ + assert_true(obj.as.ext.type == etype); \ + assert_true(obj.as.ext.size == esize); \ + M_BufferSeek(&buf, 0); \ + do { \ + char edata[esize]; \ + int8_t dummy_type = etype; \ + uint32_t dummy_size = esize; \ + memset(edata, 0, sizeof(edata)); \ + assert_true(cmp_read_ext(&cmp, &dummy_type, &dummy_size, edata)); \ + assert_true(dummy_type == etype); \ + assert_true(dummy_size == esize); \ + assert_memory_equal(edata, in, esize); \ + } while (0) + +#define test_ext_format(wfunc, etype, esize, in, fmt, dlen) \ + M_BufferClear(&buf); \ + assert_true(wfunc(&cmp, etype, esize, in)); \ + M_BufferSeek(&buf, 0); \ + assert_memory_equal(buf.data, fmt, dlen); \ + M_BufferSeek(&buf, 0); \ + assert_true(cmp_read_object(&cmp, &obj)); \ + assert_int_equal(obj.as.ext.type, etype); \ + assert_int_equal(obj.as.ext.size, esize); \ + M_BufferSeek(&buf, 0); \ + do { \ + char edata[esize]; \ + int8_t dummy_type = 0; \ + uint32_t dummy_size = 0; \ + assert_true(cmp_read_ext(&cmp, &dummy_type, &dummy_size, edata)); \ + assert_int_equal(dummy_type, etype); \ + assert_int_equal(dummy_size, esize); \ + assert_memory_equal(edata, in, esize); \ + } while (0) + +static bool buf_reader(cmp_ctx_t *ctx, void *data, size_t limit) { + if (!reader_successes) { + return false; + } + + if (reader_successes > 0) { + reader_successes--; + } + + buf_t *buf = (buf_t *)ctx->buf; + + return M_BufferRead(buf, data, limit); +} + +static size_t buf_writer(cmp_ctx_t *ctx, const void *data, size_t sz) { + if (!writer_successes) { + return false; + } + + if (writer_successes > 0) { + writer_successes--; + } + + buf_t *buf = (buf_t *)ctx->buf; + size_t pos = M_BufferGetCursor(buf); + + M_BufferWrite(buf, (void *)data, sz); + + return M_BufferGetCursor(buf) - pos; +} + +static bool buf_skipper(cmp_ctx_t *ctx, size_t count) { + if (!skipper_successes) { + return false; + } + + if (skipper_successes > 0) { + skipper_successes--; + } + + buf_t *buf = (buf_t *)ctx->buf; + + return M_BufferSeekForward(buf, count); +} + +void setup_cmp_and_buf(cmp_ctx_t *cmp, buf_t *buf) { + reader_successes = -1; + writer_successes = -1; + skipper_successes = -1; + M_BufferInitWithCapacity(buf, 32); + cmp_init(cmp, buf, buf_reader, buf_skipper, buf_writer); +} + +void teardown_cmp_and_buf(cmp_ctx_t *cmp, buf_t *buf) { + reader_successes = -1; + writer_successes = -1; + skipper_successes = -1; + M_BufferFree(buf); + cmp->error = 0; + cmp->buf = NULL; + cmp->read = NULL; + cmp->skip = NULL; + cmp->write = NULL; +} + +void test_msgpack(void **state) { + buf_t in_buf; + buf_t out_buf; + cmp_ctx_t in_cmp; + cmp_ctx_t out_cmp; + cmp_object_t obj; + + (void)state; + + setup_cmp_and_buf(&in_cmp, &in_buf); + M_BufferSetFile(&in_buf, "cases.mpac"); + M_BufferSeek(&in_buf, 0); + + setup_cmp_and_buf(&out_cmp, &out_buf); + M_BufferEnsureCapacity(&out_buf, M_BufferGetSize(&in_buf)); + + while (M_BufferGetCursor(&in_buf) < (M_BufferGetSize(&in_buf))) { + assert_true(cmp_read_object(&in_cmp, &obj)); + assert_true(cmp_write_object(&out_cmp, &obj)); + } + + assert_memory_equal(in_buf.data, out_buf.data, out_buf.size); + + teardown_cmp_and_buf(&in_cmp, &in_buf); + teardown_cmp_and_buf(&out_cmp, &out_buf); +} + +void test_fixedint(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + assert_false(cmp_write_pfix(&cmp, 128)); + cmp.error = 0; + + assert_false(cmp_write_pfix(&cmp, 200)); + cmp.error = 0; + + assert_false(cmp_write_pfix(&cmp, -1)); + cmp.error = 0; + + assert_false(cmp_write_pfix(&cmp, -31)); + cmp.error = 0; + + assert_false(cmp_write_pfix(&cmp, -32)); + cmp.error = 0; + + assert_false(cmp_write_pfix(&cmp, -127)); + cmp.error = 0; + + assert_false(cmp_write_pfix(&cmp, -128)); + cmp.error = 0; + + assert_false(cmp_write_ufix(&cmp, -128)); + cmp.error = 0; + + assert_false(cmp_write_ufix(&cmp, -1)); + cmp.error = 0; + + assert_false(cmp_write_ufix(&cmp, -128)); + cmp.error = 0; + + assert_false(cmp_write_sfix(&cmp, -33)); + cmp.error = 0; + + assert_false(cmp_write_nfix(&cmp, 0)); + cmp.error = 0; + + assert_false(cmp_write_nfix(&cmp, 1)); + cmp.error = 0; + + assert_false(cmp_write_nfix(&cmp, -33)); + cmp.error = 0; + + test_int_format( + cmp_write_ufix, cmp_read_uinteger, u8, uint64_t, 0, "\x00", 1 + ); + test_int_format( + cmp_write_ufix, cmp_read_uinteger, u8, uint64_t, -0, "\x00", 1 + ); + test_int_format( + cmp_write_sfix, cmp_read_uinteger, u8, uint64_t, 0, "\x00", 1 + ); + test_int_format( + cmp_write_sfix, cmp_read_sinteger, s8, int64_t, -0, "\x00", 1 + ); + test_int_format( + cmp_write_sfix, cmp_read_uinteger, u8, uint64_t, 127, "\x7f", 1 + ); + test_int_format( + cmp_write_sfix, cmp_read_sinteger, s8, int64_t, -32, "\xe0", 1 + ); + test_int_format( + cmp_write_pfix, cmp_read_uinteger, u8, uint64_t, 0, "\x00", 1 + ); + test_int_format( + cmp_write_pfix, cmp_read_uinteger, u8, uint64_t, 1, "\x01", 1 + ); + test_int_format( + cmp_write_pfix, cmp_read_uinteger, u8, uint64_t, 127, "\x7f", 1 + ); + test_int_format( + cmp_write_nfix, cmp_read_sinteger, s8, int64_t, -1, "\xff", 1 + ); + test_int_format( + cmp_write_nfix, cmp_read_sinteger, s8, int64_t, -32, "\xe0", 1 + ); + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_numbers(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + int8_t s8; + int16_t s16; + int32_t s32; + int64_t s64; + +#ifndef CMP_NO_FLOAT + float f; + double d; +#endif + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + test_int_format( + cmp_write_s8, cmp_read_sinteger, s8, int64_t, 0, "\xd0\x00", 2 + ); + test_int_format( + cmp_write_s8, cmp_read_sinteger, s8, int64_t, 1, "\xd0\x01", 2 + ); + test_int_format( + cmp_write_s8, cmp_read_sinteger, s8, int64_t, -1, "\xd0\xff", 2 + ); + test_int_format( + cmp_write_s8, cmp_read_sinteger, s8, int64_t, 127, "\xd0\x7f", 2 + ); + test_int_format( + cmp_write_s8, cmp_read_sinteger, s8, int64_t, -128, "\xd0\x80", 2 + ); + + test_int_format( + cmp_write_s16, cmp_read_sinteger, s16, int64_t, 0, "\xd1\x00\x00", 3 + ); + test_int_format( + cmp_write_s16, cmp_read_sinteger, s16, int64_t, 1, "\xd1\x00\x01", 3 + ); + test_int_format( + cmp_write_s16, cmp_read_sinteger, s16, int64_t, -1, "\xd1\xff\xff", 3 + ); + test_int_format( + cmp_write_s16, cmp_read_sinteger, s16, int64_t, 127, "\xd1\x00\x7f", 3 + ); + test_int_format( + cmp_write_s16, cmp_read_sinteger, s16, int64_t, -128, "\xd1\xff\x80", 3 + ); + test_int_format( + cmp_write_s16, cmp_read_sinteger, s16, int64_t, 256, "\xd1\x01\x00", 3 + ); + test_int_format( + cmp_write_s16, cmp_read_sinteger, s16, int64_t, 32767, "\xd1\x7f\xff", 3 + ); + test_int_format( + cmp_write_s16, cmp_read_sinteger, s16, int64_t, -32768, "\xd1\x80\x00", 3 + ); + + test_int_format( + cmp_write_s32, cmp_read_sinteger, s32, int64_t, 0, "\xd2\x00\x00\x00\x00", 5 + ); + test_int_format( + cmp_write_s32, cmp_read_sinteger, s32, int64_t, 1, "\xd2\x00\x00\x00\x01", 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + -1, + "\xd2\xff\xff\xff\xff", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + 127, + "\xd2\x00\x00\x00\x7f", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + -128, + "\xd2\xff\xff\xff\x80", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + 256, + "\xd2\x00\x00\x01\x00", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + 32767, + "\xd2\x00\x00\x7f\xff", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + -32768, + "\xd2\xff\xff\x80\x00", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + 65535, + "\xd2\x00\x00\xff\xff", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + -65536, + "\xd2\xff\xff\x00\x00", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + 8388607, + "\xd2\x00\x7f\xff\xff", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + -8388608, + "\xd2\xff\x80\x00\x00", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + 16777215, + "\xd2\x00\xff\xff\xff", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + -16777216, + "\xd2\xff\x00\x00\x00", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + 2147483647, + "\xd2\x7f\xff\xff\xff", + 5 + ); + test_int_format( + cmp_write_s32, + cmp_read_sinteger, + s32, + int64_t, + -2147483648, + "\xd2\x80\x00\x00\x00", + 5 + ); + + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + 0, + "\xd3\x00\x00\x00\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + 1, + "\xd3\x00\x00\x00\x00\x00\x00\x00\x01", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + -1, + "\xd3\xff\xff\xff\xff\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + 127, + "\xd3\x00\x00\x00\x00\x00\x00\x00\x7f", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + -128, + "\xd3\xff\xff\xff\xff\xff\xff\xff\x80", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + 256, + "\xd3\x00\x00\x00\x00\x00\x00\x01\x00", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + 32767, + "\xd3\x00\x00\x00\x00\x00\x00\x7f\xff", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + -32768, + "\xd3\xff\xff\xff\xff\xff\xff\x80\x00", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + 65535, + "\xd3\x00\x00\x00\x00\x00\x00\xff\xff", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + -65536, + "\xd3\xff\xff\xff\xff\xff\xff\x00\x00", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + 8388607, + "\xd3\x00\x00\x00\x00\x00\x7f\xff\xff", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + -8388608, + "\xd3\xff\xff\xff\xff\xff\x80\x00\x00", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + 16777215, + "\xd3\x00\x00\x00\x00\x00\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + -16777216, + "\xd3\xff\xff\xff\xff\xff\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + 2147483647, + "\xd3\x00\x00\x00\x00\x7f\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + -2147483648, + "\xd3\xff\xff\xff\xff\x80\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + 4294967295, + "\xd3\x00\x00\x00\x00\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_s64, + cmp_read_sinteger, + s64, + int64_t, + -4294967296, + "\xd3\xff\xff\xff\xff\x00\x00\x00\x00", + 9 + ); + + test_int_format( + cmp_write_sint, cmp_read_uinteger, u8, uint64_t, 0, "\x00", 1 + ); + test_int_format( + cmp_write_sint, cmp_read_uinteger, u8, uint64_t, 1, "\x01", 1 + ); + test_int_format( + cmp_write_sint, cmp_read_uinteger, u8, uint64_t, 127, "\x7f", 1 + ); + test_int_format( + cmp_write_sint, cmp_read_uinteger, u8, uint64_t, 128, "\xcc\x80", 2 + ); + test_int_format( + cmp_write_sint, cmp_read_uinteger, u8, uint64_t, 255, "\xcc\xff", 2 + ); + test_int_format( + cmp_write_sint, cmp_read_uinteger, u16, uint64_t, 256, "\xcd\x01\x00", 3 + ); + test_int_format( + cmp_write_sint, cmp_read_uinteger, u16, uint64_t, 32767, "\xcd\x7f\xff", 3 + ); + test_int_format( + cmp_write_sint, cmp_read_uinteger, u16, uint64_t, 32768, "\xcd\x80\x00", 3 + ); + test_int_format( + cmp_write_sint, cmp_read_uinteger, u16, uint64_t, 65535, "\xcd\xff\xff", 3 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u32, + uint64_t, + 65536, + "\xce\x00\x01\x00\x00", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u32, + uint64_t, + 8388607, + "\xce\x00\x7f\xff\xff", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u32, + uint64_t, + 8388608, + "\xce\x00\x80\x00\x00", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u32, + uint64_t, + 16777215, + "\xce\x00\xff\xff\xff", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u32, + uint64_t, + 16777216, + "\xce\x01\x00\x00\x00", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u32, + uint64_t, + 2147483647, + "\xce\x7f\xff\xff\xff", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 4294967296, + "\xcf\x00\x00\x00\x01\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 549755813887, + "\xcf\x00\x00\x00\x7f\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 549755813888, + "\xcf\x00\x00\x00\x80\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 1099511627775, + "\xcf\x00\x00\x00\xff\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 1099511627776, + "\xcf\x00\x00\x01\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 140737488355327, + "\xcf\x00\x00\x7f\xff\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 140737488355328, + "\xcf\x00\x00\x80\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 281474976710655, + "\xcf\x00\x00\xff\xff\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 281474976710656, + "\xcf\x00\x01\x00\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 36028797018963967, + "\xcf\x00\x7f\xff\xff\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 36028797018963968, + "\xcf\x00\x80\x00\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 72057594037927935, + "\xcf\x00\xff\xff\xff\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 72057594037927936, + "\xcf\x01\x00\x00\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_uinteger, + u64, + uint64_t, + 9223372036854775807, + "\xcf\x7f\xff\xff\xff\xff\xff\xff\xff", + 9 + ); + + test_int_format( + cmp_write_sint, cmp_read_sinteger, s8, int64_t, -1, "\xff", 1 + ); + test_int_format( + cmp_write_sint, cmp_read_sinteger, s8, int64_t, -32, "\xe0", 1 + ); + test_int_format( + cmp_write_sint, cmp_read_sinteger, s8, int64_t, -127, "\xd0\x81", 2 + ); + test_int_format( + cmp_write_sint, cmp_read_sinteger, s8, int64_t, -128, "\xd0\x80", 2 + ); + test_int_format( + cmp_write_sint, cmp_read_sinteger, s16, int64_t, -255, "\xd1\xff\x01", 3 + ); + test_int_format( + cmp_write_sint, cmp_read_sinteger, s16, int64_t, -256, "\xd1\xff\x00", 3 + ); + test_int_format( + cmp_write_sint, cmp_read_sinteger, s16, int64_t, -32767, "\xd1\x80\x01", 3 + ); + test_int_format( + cmp_write_sint, cmp_read_sinteger, s16, int64_t, -32768, "\xd1\x80\x00", 3 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s32, + int64_t, + -65535, + "\xd2\xff\xff\x00\x01", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s32, + int64_t, + -65536, + "\xd2\xff\xff\x00\x00", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s32, + int64_t, + -8388607, + "\xd2\xff\x80\x00\x01", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s32, + int64_t, + -8388608, + "\xd2\xff\x80\x00\x00", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s32, + int64_t, + -16777215, + "\xd2\xff\x00\x00\x01", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s32, + int64_t, + -16777216, + "\xd2\xff\x00\x00\x00", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s32, + int64_t, + -2147483647, + "\xd2\x80\x00\x00\x01", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s32, + int64_t, + -2147483648, + "\xd2\x80\x00\x00\x00", + 5 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -4294967295, + "\xd3\xff\xff\xff\xff\x00\x00\x00\x01", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -4294967296, + "\xd3\xff\xff\xff\xff\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -549755813887, + "\xd3\xff\xff\xff\x80\x00\x00\x00\x01", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -549755813888, + "\xd3\xff\xff\xff\x80\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -1099511627775, + "\xd3\xff\xff\xff\x00\x00\x00\x00\x01", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -1099511627776, + "\xd3\xff\xff\xff\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -140737488355327, + "\xd3\xff\xff\x80\x00\x00\x00\x00\x01", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -140737488355328, + "\xd3\xff\xff\x80\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -281474976710655, + "\xd3\xff\xff\x00\x00\x00\x00\x00\x01", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -281474976710656, + "\xd3\xff\xff\x00\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -36028797018963967, + "\xd3\xff\x80\x00\x00\x00\x00\x00\x01", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -36028797018963968, + "\xd3\xff\x80\x00\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -72057594037927935, + "\xd3\xff\x00\x00\x00\x00\x00\x00\x01", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -72057594037927936, + "\xd3\xff\x00\x00\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_sint, + cmp_read_sinteger, + s64, + int64_t, + -9223372036854775807, + "\xd3\x80\x00\x00\x00\x00\x00\x00\x01", + 9 + ); + + test_int_format(cmp_write_u8, + cmp_read_uinteger, + u8, + uint64_t, + 0, + "\xcc\x00", + 1); + test_int_format(cmp_write_u8, + cmp_read_uinteger, + u8, + uint64_t, + 1, + "\xcc\x01", + 1); + test_int_format( + cmp_write_u8, + cmp_read_uinteger, + u8, + uint64_t, + 127, + "\xcc\x7f", + 1 + ); + test_int_format( + cmp_write_u8, + cmp_read_uinteger, + u8, + uint64_t, + 255, + "\xcc\xff", + 1 + ); + + test_int_format( + cmp_write_u16, cmp_read_uinteger, u16, uint64_t, 0, "\xcd\x00\x00", 2 + ); + test_int_format( + cmp_write_u16, cmp_read_uinteger, u16, uint64_t, 1, "\xcd\x00\x01", 2 + ); + test_int_format( + cmp_write_u16, cmp_read_uinteger, u16, uint64_t, 127, "\xcd\x00\x7f", 2 + ); + test_int_format( + cmp_write_u16, cmp_read_uinteger, u16, uint64_t, 256, "\xcd\x01\x00", 2 + ); + test_int_format( + cmp_write_u16, cmp_read_uinteger, u16, uint64_t, 32767, "\xcd\x7f\xff", 2 + ); + test_int_format( + cmp_write_u16, cmp_read_uinteger, u16, uint64_t, 65535, "\xcd\xff\xff", 2 + ); + + test_int_format( + cmp_write_u32, + cmp_read_uinteger, + u32, + uint64_t, + 0, + "\xce\x00\x00\x00\x00", + 5 + ); + test_int_format( + cmp_write_u32, + cmp_read_uinteger, + u32, + uint64_t, + 1, + "\xce\x00\x00\x00\x01", + 5 + ); + test_int_format( + cmp_write_u32, + cmp_read_uinteger, + u32, + uint64_t, + 127, + "\xce\x00\x00\x00\x7f", + 5 + ); + test_int_format( + cmp_write_u32, + cmp_read_uinteger, + u32, + uint64_t, + 256, + "\xce\x00\x00\x01\x00", + 5 + ); + test_int_format( + cmp_write_u32, + cmp_read_uinteger, + u32, + uint64_t, + 32767, + "\xce\x00\x00\x7f\xff", + 5 + ); + test_int_format( + cmp_write_u32, + cmp_read_uinteger, + u32, + uint64_t, + 65535, + "\xce\x00\x00\xff\xff", + 5 + ); + test_int_format( + cmp_write_u32, + cmp_read_uinteger, + u32, + uint64_t, + 8388607, + "\xce\x00\x7f\xff\xff", + 5 + ); + test_int_format( + cmp_write_u32, + cmp_read_uinteger, + u32, + uint64_t, + 16777215, + "\xce\x00\xff\xff\xff", + 5 + ); + test_int_format( + cmp_write_u32, + cmp_read_uinteger, + u32, + uint64_t, + 2147483647, + "\xce\x7f\xff\xff\xff", + 5 + ); + test_int_format( + cmp_write_u32, + cmp_read_uinteger, + u32, + uint64_t, + 4294967295, + "\xce\xff\xff\xff\xff", + 5 + ); + + test_int_format( + cmp_write_u64, + cmp_read_uinteger, + u64, + uint64_t, + 0, + "\xcf\x00\x00\x00\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_u64, + cmp_read_uinteger, + u64, + uint64_t, + 1, + "\xcf\x00\x00\x00\x00\x00\x00\x00\x01", + 9 + ); + test_int_format( + cmp_write_u64, + cmp_read_uinteger, + u64, + uint64_t, + 127, + "\xcf\x00\x00\x00\x00\x00\x00\x00\x7f", + 9 + ); + test_int_format( + cmp_write_u64, + cmp_read_uinteger, + u64, + uint64_t, + 256, + "\xcf\x00\x00\x00\x00\x00\x00\x01\x00", + 9 + ); + test_int_format( + cmp_write_u64, + cmp_read_uinteger, + u64, + uint64_t, + 32767, + "\xcf\x00\x00\x00\x00\x00\x00\x7f\xff", + 9 + ); + test_int_format( + cmp_write_u64, + cmp_read_uinteger, + u64, + uint64_t, + 65535, + "\xcf\x00\x00\x00\x00\x00\x00\xff\xff", + 9 + ); + test_int_format( + cmp_write_u64, + cmp_read_uinteger, + u64, + uint64_t, + 8388607, + "\xcf\x00\x00\x00\x00\x00\x7f\xff\xff", + 9 + ); + test_int_format( + cmp_write_u64, + cmp_read_uinteger, + u64, + uint64_t, + 16777215, + "\xcf\x00\x00\x00\x00\x00\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_u64, + cmp_read_uinteger, + u64, + uint64_t, + 2147483647, + "\xcf\x00\x00\x00\x00\x7f\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_u64, + cmp_read_uinteger, + u64, + uint64_t, + 4294967295, + "\xcf\x00\x00\x00\x00\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_u64, + cmp_read_uinteger, + u64, + uint64_t, + 0xFFFFFFFFFFFFFFFE, + "\xcf\xff\xff\xff\xff\xff\xff\xff\xfe", + 9 + ); + test_int_format( + cmp_write_u64, + cmp_read_uinteger, + u64, + uint64_t, + 0xFFFFFFFFFFFFFFFF, + "\xcf\xff\xff\xff\xff\xff\xff\xff\xff", + 9 + ); + + test_int_format( + cmp_write_uint, cmp_read_uinteger, u8, uint64_t, 0, "\x00", 1 + ); + test_int_format( + cmp_write_uint, cmp_read_uinteger, u8, uint64_t, 1, "\x01", 1 + ); + test_int_format( + cmp_write_uint, cmp_read_uinteger, u8, uint64_t, 127, "\x7f", 1 + ); + test_int_format( + cmp_write_uint, cmp_read_uinteger, u8, uint64_t, 128, "\xcc\x80", 2 + ); + test_int_format( + cmp_write_uint, cmp_read_uinteger, u8, uint64_t, 255, "\xcc\xff", 2 + ); + test_int_format( + cmp_write_uint, cmp_read_uinteger, u16, uint64_t, 256, "\xcd\x01\x00", 3 + ); + test_int_format( + cmp_write_uint, cmp_read_uinteger, u16, uint64_t, 32767, "\xcd\x7f\xff", 3 + ); + test_int_format( + cmp_write_uint, cmp_read_uinteger, u16, uint64_t, 32768, "\xcd\x80\x00", 3 + ); + test_int_format( + cmp_write_uint, cmp_read_uinteger, u16, uint64_t, 65535, "\xcd\xff\xff", 3 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u32, + uint64_t, + 65536, + "\xce\x00\x01\x00\x00", + 5 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u32, + uint64_t, + 8388607, + "\xce\x00\x7f\xff\xff", + 5 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u32, + uint64_t, + 8388608, + "\xce\x00\x80\x00\x00", + 5 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u32, + uint64_t, + 16777215, + "\xce\x00\xff\xff\xff", + 5 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u32, + uint64_t, + 16777216, + "\xce\x01\x00\x00\x00", + 5 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u32, + uint64_t, + 2147483647, + "\xce\x7f\xff\xff\xff", + 5 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u32, + uint64_t, + 2147483648, + "\xce\x80\x00\x00\x00", + 5 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u32, + uint64_t, + 4294967295, + "\xce\xff\xff\xff\xff", + 5 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 4294967296, + "\xcf\x00\x00\x00\x01\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 549755813887, + "\xcf\x00\x00\x00\x7f\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 549755813888, + "\xcf\x00\x00\x00\x80\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 1099511627775, + "\xcf\x00\x00\x00\xff\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 1099511627776, + "\xcf\x00\x00\x01\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 140737488355327, + "\xcf\x00\x00\x7f\xff\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 140737488355328, + "\xcf\x00\x00\x80\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 281474976710655, + "\xcf\x00\x00\xff\xff\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 281474976710656, + "\xcf\x00\x01\x00\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 36028797018963967, + "\xcf\x00\x7f\xff\xff\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 36028797018963968, + "\xcf\x00\x80\x00\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 72057594037927935, + "\xcf\x00\xff\xff\xff\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 72057594037927936, + "\xcf\x01\x00\x00\x00\x00\x00\x00\x00", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 9223372036854775807, + "\xcf\x7f\xff\xff\xff\xff\xff\xff\xff", + 9 + ); + test_int_format( + cmp_write_uint, + cmp_read_uinteger, + u64, + uint64_t, + 0xFFFFFFFFFFFFFFFF, + "\xcf\xff\xff\xff\xff\xff\xff\xff\xff", + 9 + ); + +#ifndef CMP_NO_FLOAT + test_float_format( + cmp_write_float, + cmp_read_float, + flt, + float, + 0.0f, + "\xca\x00\x00\x00\x00", + 5 + ); + test_float_format( + cmp_write_float, + cmp_read_float, + flt, + float, + -0.0f, + "\xca\x80\x00\x00\x00", + 5 + ); + test_float_format( + cmp_write_float, + cmp_read_float, + flt, + float, + 1.0f, + "\xca\x3f\x80\x00\x00", + 5 + ); + test_float_format( + cmp_write_float, + cmp_read_float, + flt, + float, + -1.0f, + "\xca\xbf\x80\x00\x00", + 5 + ); + test_float_format( + cmp_write_float, + cmp_read_float, + flt, + float, + 65535.0f, + "\xca\x47\x7f\xff\x00", + 5 + ); + test_float_format( + cmp_write_float, + cmp_read_float, + flt, + float, + -65535.0f, + "\xca\xc7\x7f\xff\x00", + 5 + ); + test_float_format( + cmp_write_float, + cmp_read_float, + flt, + float, + 32767.0f, + "\xca\x46\xff\xfe\x00", + 5 + ); + test_float_format( + cmp_write_float, + cmp_read_float, + flt, + float, + -32767.0f, + "\xca\xc6\xff\xfe\x00", + 5 + ); + + test_double_format( + cmp_write_double, + cmp_read_double, + dbl, + double, + 0.0, + "\xcb\x00\x00\x00\x00\x00\x00\x00\x00", + 9 + ); + test_double_format( + cmp_write_double, + cmp_read_double, + dbl, + double, + -0.0, + "\xcb\x80\x00\x00\x00\x00\x00\x00\x00", + 9 + ); + test_double_format( + cmp_write_double, + cmp_read_double, + dbl, + double, + 1.0, + "\xcb\x3f\xf0\x00\x00\x00\x00\x00\x00", + 9 + ); + test_double_format( + cmp_write_double, + cmp_read_double, + dbl, + double, + -1.0, + "\xcb\xbf\xf0\x00\x00\x00\x00\x00\x00", + 9 + ); + test_double_format( + cmp_write_double, + cmp_read_double, + dbl, + double, + 2147483647.0, + "\xcb\x41\xdf\xff\xff\xff\xc0\x00\x00", + 9 + ); + test_double_format( + cmp_write_double, + cmp_read_double, + dbl, + double, + -2147483647.0, + "\xcb\xc1\xdf\xff\xff\xff\xc0\x00\x00", + 9 + ); + test_double_format( + cmp_write_double, + cmp_read_double, + dbl, + double, + 4294967295.0, + "\xcb\x41\xef\xff\xff\xff\xe0\x00\x00", + 9 + ); + test_double_format( + cmp_write_double, + cmp_read_double, + dbl, + double, + -4294967295.0, + "\xcb\xc1\xef\xff\xff\xff\xe0\x00\x00", + 9 + ); + + test_double_format( + cmp_write_decimal, + cmp_read_decimal, + flt, + double, + 2.0f, + "\xca\x40\x00\x00\x00", + 5 + ); + + test_double_format( + cmp_write_decimal, + cmp_read_decimal, + dbl, + double, + 1111111111111111.125000, + "\xcb\x43\x0f\x94\x65\xb8\xab\x8e\x39", + 9 + ); +#endif + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_sfix(&cmp, 1)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_sfix(&cmp, &s8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_sfix(&cmp, -1)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_sfix(&cmp, &s8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_pfix(&cmp, 1)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_pfix(&cmp, &u8)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_ufix(&cmp, &u8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_u8(&cmp, 200)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_u8(&cmp, &u8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_u16(&cmp, 300)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_u16(&cmp, &u16)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_u32(&cmp, 70000)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_u32(&cmp, &u32)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_u64(&cmp, 0x100000002)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_u64(&cmp, &u64)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_nfix(&cmp, -1)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_nfix(&cmp, &s8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_s8(&cmp, -100)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_s8(&cmp, &s8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_s16(&cmp, -200)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_s16(&cmp, &s16)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_s32(&cmp, -33000)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_s32(&cmp, &s32)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_s64(&cmp, 0x80000002)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_s64(&cmp, &s64)); + +#ifndef CMP_NO_FLOAT + M_BufferSeek(&buf, 0); + assert_true(cmp_write_float(&cmp, 1.1f)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_float(&cmp, &f)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_double(&cmp, 1.1)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_double(&cmp, &d)); +#endif + + M_BufferClear(&buf); + assert_true(cmp_write_s8(&cmp, -1)); + assert_true(cmp_write_s8(&cmp, 1)); + assert_true(cmp_write_u8(&cmp, 1)); + assert_true(cmp_write_u8(&cmp, 1)); + + assert_true(cmp_write_s8(&cmp, -100)); + assert_true(cmp_write_s8(&cmp, 100)); + assert_true(cmp_write_u8(&cmp, 100)); + assert_true(cmp_write_u8(&cmp, 200)); + + assert_true(cmp_write_s16(&cmp, -200)); + assert_true(cmp_write_s16(&cmp, 300)); + assert_true(cmp_write_u16(&cmp, 300)); + assert_true(cmp_write_u16(&cmp, 33000)); + + assert_true(cmp_write_s32(&cmp, -33000)); + assert_true(cmp_write_s32(&cmp, 33000)); + assert_true(cmp_write_u32(&cmp, 33000)); + assert_true(cmp_write_u32(&cmp, 0x81000000)); + + assert_true(cmp_write_s64(&cmp, 0xFFFFFFFFFFFFFFFC)); + assert_true(cmp_write_s64(&cmp, 0x7FFFFFFFFFFFFFFC)); + assert_true(cmp_write_u64(&cmp, 0x7FFFFFFFFFFFFFFC)); + assert_true(cmp_write_u64(&cmp, 0x800000000000000C)); + +#ifndef CMP_NO_FLOAT + assert_true(cmp_write_decimal(&cmp, 1.1f)); + assert_true(cmp_write_decimal(&cmp, 1.1)); +#endif + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_uchar(&cmp, &u8)); + assert_true(cmp_read_uchar(&cmp, &u8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_uchar(&cmp, &u8)); + assert_true(cmp_read_uchar(&cmp, &u8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_uchar(&cmp, &u8)); + assert_true(cmp_read_uchar(&cmp, &u8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_ushort(&cmp, &u16)); + assert_true(cmp_read_ushort(&cmp, &u16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_ushort(&cmp, &u16)); + assert_true(cmp_read_ushort(&cmp, &u16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_ushort(&cmp, &u16)); + assert_true(cmp_read_ushort(&cmp, &u16)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_uint(&cmp, &u32)); + assert_true(cmp_read_uint(&cmp, &u32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_uint(&cmp, &u32)); + assert_true(cmp_read_uint(&cmp, &u32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_uint(&cmp, &u32)); + assert_true(cmp_read_uint(&cmp, &u32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_uint(&cmp, &u32)); + assert_true(cmp_read_uint(&cmp, &u32)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_ulong(&cmp, &u64)); + assert_true(cmp_read_ulong(&cmp, &u64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_ulong(&cmp, &u64)); + assert_true(cmp_read_ulong(&cmp, &u64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_ulong(&cmp, &u64)); + assert_true(cmp_read_ulong(&cmp, &u64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_ulong(&cmp, &u64)); + assert_true(cmp_read_ulong(&cmp, &u64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_ulong(&cmp, &u64)); + assert_true(cmp_read_ulong(&cmp, &u64)); + +#ifndef CMP_NO_FLOAT + assert_true(cmp_read_decimal(&cmp, &d)); + assert_true(cmp_read_decimal(&cmp, &d)); +#endif + + reader_successes = 0; + M_BufferSeek(&buf, 0); + assert_false(cmp_read_char(&cmp, &s8)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_uchar(&cmp, &u8)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_short(&cmp, &s16)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_ushort(&cmp, &u16)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_int(&cmp, &s32)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_uint(&cmp, &u32)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_long(&cmp, &s64)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_ulong(&cmp, &u64)); + +#ifndef CMP_NO_FLOAT + M_BufferSeek(&buf, 0); + assert_false(cmp_read_decimal(&cmp, &d)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_decimal(&cmp, &d)); +#endif + + reader_successes = -1; + + M_BufferClear(&buf); + assert_true(cmp_write_u8(&cmp, 200)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_char(&cmp, &s8)); + + M_BufferClear(&buf); + assert_true(cmp_write_u64(&cmp, 0xFFFFFFFFFFFFFFFE)); + assert_true(cmp_write_s64(&cmp, 0xFFFFFFFFFFFFFFFE)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_char(&cmp, &s8)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_uchar(&cmp, &u8)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_short(&cmp, &s16)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_ushort(&cmp, &u16)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_int(&cmp, &s32)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_uint(&cmp, &u32)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_long(&cmp, &s64)); + +#ifndef CMP_NO_FLOAT + M_BufferSeek(&buf, 0); + assert_false(cmp_read_decimal(&cmp, &d)); +#endif + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_ulong(&cmp, &u64)); + assert_false(cmp_read_ulong(&cmp, &u64)); + + M_BufferClear(&buf); + assert_true(cmp_write_s8(&cmp, 100)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_uchar(&cmp, &u8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_ushort(&cmp, &u16)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_uint(&cmp, &u32)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_ulong(&cmp, &u64)); + + M_BufferClear(&buf); + assert_true(cmp_write_s16(&cmp, 300)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_ushort(&cmp, &u16)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_uint(&cmp, &u32)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_ulong(&cmp, &u64)); + + M_BufferClear(&buf); + assert_true(cmp_write_s32(&cmp, 40000)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_uint(&cmp, &u32)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_ulong(&cmp, &u64)); + + M_BufferClear(&buf); + assert_true(cmp_write_s64(&cmp, 0x6FFFFFFFFFFFFFFE)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_ulong(&cmp, &u64)); + + M_BufferClear(&buf); + assert_true(cmp_write_s8(&cmp, -100)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_uchar(&cmp, &u8)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_ushort(&cmp, &u16)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_uint(&cmp, &u32)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_ulong(&cmp, &u64)); + + M_BufferClear(&buf); + assert_true(cmp_write_u8(&cmp, 4)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT8); + assert_true(cmp_object_as_char(&obj, &s8)); + assert_true(cmp_object_as_short(&obj, &s16)); + assert_true(cmp_object_as_int(&obj, &s32)); + assert_true(cmp_object_as_long(&obj, &s64)); + + M_BufferClear(&buf); + assert_true(cmp_write_u8(&cmp, 200)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT8); + assert_false(cmp_object_as_char(&obj, &s8)); + assert_true(cmp_object_as_short(&obj, &s16)); + assert_true(cmp_object_as_int(&obj, &s32)); + assert_true(cmp_object_as_long(&obj, &s64)); + + M_BufferClear(&buf); + assert_true(cmp_write_u16(&cmp, 30000)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT16); + assert_false(cmp_object_as_char(&obj, &s8)); + assert_true(cmp_object_as_short(&obj, &s16)); + assert_true(cmp_object_as_int(&obj, &s32)); + assert_true(cmp_object_as_long(&obj, &s64)); + + M_BufferClear(&buf); + assert_true(cmp_write_u16(&cmp, 60000)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT16); + assert_false(cmp_object_as_char(&obj, &s8)); + assert_false(cmp_object_as_short(&obj, &s16)); + assert_true(cmp_object_as_int(&obj, &s32)); + assert_true(cmp_object_as_long(&obj, &s64)); + + M_BufferClear(&buf); + assert_true(cmp_write_u32(&cmp, 2000000000)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT32); + assert_false(cmp_object_as_char(&obj, &s8)); + assert_false(cmp_object_as_short(&obj, &s16)); + assert_true(cmp_object_as_int(&obj, &s32)); + assert_true(cmp_object_as_long(&obj, &s64)); + + M_BufferClear(&buf); + assert_true(cmp_write_u32(&cmp, 3000000000)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT32); + assert_false(cmp_object_as_char(&obj, &s8)); + assert_false(cmp_object_as_short(&obj, &s16)); + assert_false(cmp_object_as_int(&obj, &s32)); + assert_true(cmp_object_as_long(&obj, &s64)); + + M_BufferClear(&buf); + assert_true(cmp_write_u64(&cmp, LONG_MAX - 10)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT64); + assert_false(cmp_object_as_char(&obj, &s8)); + assert_false(cmp_object_as_short(&obj, &s16)); + assert_false(cmp_object_as_int(&obj, &s32)); + assert_true(cmp_object_as_long(&obj, &s64)); + + M_BufferClear(&buf); + assert_true(cmp_write_u64(&cmp, ((uint64_t)LONG_MAX) + 10)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT64); + assert_false(cmp_object_as_char(&obj, &s8)); + assert_false(cmp_object_as_short(&obj, &s16)); + assert_false(cmp_object_as_int(&obj, &s32)); + assert_false(cmp_object_as_long(&obj, &s64)); + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_conversions(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + int8_t s8; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + bool b; +#ifndef CMP_NO_FLOAT + float f; + double d; +#endif + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + M_BufferClear(&buf); + assert_true(cmp_write_nil(&cmp)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_false(cmp_object_as_uchar(&obj, &u8)); + assert_false(cmp_object_as_ushort(&obj, &u16)); + assert_false(cmp_object_as_uint(&obj, &u32)); + assert_false(cmp_object_as_ulong(&obj, &u64)); +#ifndef CMP_NO_FLOAT + assert_false(cmp_object_as_float(&obj, &f)); + assert_false(cmp_object_as_double(&obj, &d)); +#endif + assert_false(cmp_object_as_bool(&obj, &b)); + assert_false(cmp_object_as_str(&obj, &u32)); + assert_false(cmp_object_as_bin(&obj, &u32)); + assert_false(cmp_object_as_array(&obj, &u32)); + assert_false(cmp_object_as_map(&obj, &u32)); + assert_false(cmp_object_as_ext(&obj, &s8, &u32)); + + assert_false(cmp_object_to_str(&cmp, &obj, NULL, 0)); + assert_false(cmp_object_to_bin(&cmp, &obj, NULL, 0)); + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_nil(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + test_format_no_input(cmp_write_nil, u8, "\xc0", 1, 0); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_nil(&cmp)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_nil(&cmp)); + + reader_successes = 0; + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_nil(&cmp)); + + reader_successes = -1; + + M_BufferClear(&buf); + assert_true(cmp_write_true(&cmp)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_nil(&cmp)); + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_boolean(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + bool b; + uint8_t u8; + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + test_format_no_input(cmp_write_false, boolean, "\xc2", 1, false); + test_format_no_input(cmp_write_true, boolean, "\xc3", 1, true); + test_format(cmp_write_bool, cmp_read_bool, boolean, bool, false, "\xc2", 1); + test_format(cmp_write_bool, cmp_read_bool, boolean, bool, true, "\xc3", 1); + test_format( + cmp_write_u8_as_bool, cmp_read_bool_as_u8, boolean, uint8_t, 0, "\xc2", 1 + ); + test_format( + cmp_write_u8_as_bool, cmp_read_bool_as_u8, boolean, uint8_t, 1, "\xc3", 1 + ); + + M_BufferClear(&buf); + + assert_true(cmp_write_true(&cmp)); + reader_successes = 0; + M_BufferSeek(&buf, 0); + assert_false(cmp_read_bool(&cmp, &b)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_bool_as_u8(&cmp, &u8)); + + reader_successes = -1; + + M_BufferClear(&buf); + + assert_true(cmp_write_nil(&cmp)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_bool(&cmp, &b)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_bool_as_u8(&cmp, &u8)); + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_bin(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + uint32_t size; + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + test_format_with_length( + cmp_write_bin8, cmp_read_bin, bin_size, "", 0, "\xc4\x00", 2 + ); + test_format_with_length( + cmp_write_bin8, + cmp_read_bin, + bin_size, + "Hey there\n", + 10, + "\xc4\x0aHey there\n", + 12 + ); + test_format_with_length( + cmp_write_bin16, cmp_read_bin, bin_size, "", 0, "\xc5\x00\x00", 3 + ); + test_format_with_length( + cmp_write_bin16, + cmp_read_bin, + bin_size, + "Hey there\n", + 10, + "\xc5\x00\x0aHey there\n", + 13 + ); + test_format_with_length( + cmp_write_bin32, cmp_read_bin, bin_size, "", 0, "\xc6\x00\x00\x00\x00", 5 + ); + test_format_with_length( + cmp_write_bin32, + cmp_read_bin, + bin_size, + "Hey there\n", + 10, + "\xc6\x00\x00\x00\x0aHey there\n", + 15 + ); + test_format_with_length( + cmp_write_bin, cmp_read_bin, bin_size, "", 0, "\xc4\x00", 2 + ); + test_format_with_length( + cmp_write_bin, + cmp_read_bin, + bin_size, + "Hey there\n", + 10, + "\xc4\x0aHey there\n", + 12 + ); + + M_BufferSeek(&buf, 0); + + assert_true(cmp_write_bin_marker(&cmp, 100)); + for (size_t i = 0; i < 100; i++) { + M_BufferWrite(&buf, "C", 1); + } + + assert_true(cmp_write_bin_marker(&cmp, 300)); + for (size_t i = 0; i < 300; i++) { + M_BufferWrite(&buf, "C", 1); + } + + assert_true(cmp_write_bin_marker(&cmp, 70000)); + for(size_t i = 0; i < 70000; i++) { + M_BufferWrite(&buf, "C", 1); + } + + M_BufferSeek(&buf, 0); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_BIN8); + assert_int_equal(obj.as.bin_size, 100); + M_BufferSeekForward(&buf, 100); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_BIN16); + assert_int_equal(obj.as.bin_size, 300); + M_BufferSeekForward(&buf, 300); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_BIN32); + assert_int_equal(obj.as.bin_size, 70000); + M_BufferSeekForward(&buf, 70000); + + M_BufferSeek(&buf, 0); + + char *bin8 = malloc(200); + char *bin16 = malloc(300); + char *bin32 = malloc(70000); + + assert_true(cmp_write_bin(&cmp, bin8, 200)); + assert_true(cmp_write_bin(&cmp, bin16, 300)); + assert_true(cmp_write_bin(&cmp, bin32, 70000)); + + M_BufferClear(&buf); + assert_true(cmp_write_bin(&cmp, "Hello", 5)); + M_BufferSeek(&buf, 0); + size = 5; + assert_true(cmp_read_bin(&cmp, bin8, &size)); + M_BufferSeek(&buf, 0); + size = 4; + assert_false(cmp_read_bin(&cmp, bin8, &size)); + + reader_successes = 1; + M_BufferSeek(&buf, 0); + size = 5; + assert_false(cmp_read_bin(&cmp, bin8, &size)); + + reader_successes = 2; + M_BufferSeek(&buf, 0); + size = 5; + assert_false(cmp_read_bin(&cmp, bin8, &size)); + + reader_successes = 2; + M_BufferClear(&buf); + assert_true(cmp_write_bin(&cmp, "Hello", 5)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_false(cmp_object_to_bin(&cmp, &obj, bin8, 5)); + + reader_successes = -1; + M_BufferClear(&buf); + assert_true(cmp_write_bin(&cmp, "Hello", 5)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_false(cmp_object_to_bin(&cmp, &obj, bin8, 4)); + assert_true(cmp_object_to_bin(&cmp, &obj, bin8, 5)); + + free(bin8); + free(bin16); + free(bin32); + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_string(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + uint32_t size; + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + test_format_with_length( + cmp_write_fixstr, cmp_read_str, str_size, "", 0, "\xa0", 1 + ); + test_format_with_length( + cmp_write_fixstr, + cmp_read_str, + str_size, + "Hey there\n", + 10, + "\xaaHey there\n", + 11 + ); + test_format_with_length( + cmp_write_str8, cmp_read_str, str_size, "", 0, "\xd9\x00", 2 + ); + test_format_with_length( + cmp_write_str8, + cmp_read_str, + str_size, + "Hey there\n", + 10, + "\xd9\x0aHey there\n", + 12 + ); + test_format_with_length( + cmp_write_str16, + cmp_read_str, + str_size, + "", + 0, + "\xda\x00\x00", + 3 + ); + test_format_with_length( + cmp_write_str16, + cmp_read_str, + str_size, + "Hey there\n", + 10, + "\xda\x00\x0aHey there\n", + 13 + ); + test_format_with_length( + cmp_write_str32, + cmp_read_str, + str_size, + "", + 0, + "\xdb\x00\x00\x00\x00", + 5 + ); + test_format_with_length( + cmp_write_str32, + cmp_read_str, + str_size, + "Hey there\n", + 10, + "\xdb\x00\x00\x00\x0aHey there\n", + 15 + ); + test_format_with_length( + cmp_write_str, cmp_read_str, str_size, "", 0, "\xa0", 1 + ); + test_format_with_length( + cmp_write_str, + cmp_read_str, + str_size, + "Hey there\n", + 10, + "\xaaHey there\n", + 11 + ); + test_format_with_length( + cmp_write_str_v4, + cmp_read_str, + str_size, + "With your feet on the air and your head on the ground\n", + 54, + "\xda\x00\x36With your feet on the air and your head on the ground\n", + 57 + ); + + M_BufferSeek(&buf, 0); + + assert_true(cmp_write_str_marker(&cmp, 7)); + M_BufferWrite(&buf, "bananas", 7); + + assert_true(cmp_write_str_marker(&cmp, 100)); + for (size_t i = 0; i < 100; i++) { + M_BufferWrite(&buf, "C", 1); + } + + assert_true(cmp_write_str_marker(&cmp, 300)); + for (size_t i = 0; i < 300; i++) { + M_BufferWrite(&buf, "C", 1); + } + + assert_true(cmp_write_str_marker(&cmp, 70000)); + for(size_t i = 0; i < 70000; i++) { + M_BufferWrite(&buf, "C", 1); + } + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); + assert_int_equal(obj.as.str_size, 7); + M_BufferSeekForward(&buf, 7); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_STR8); + assert_int_equal(obj.as.str_size, 100); + M_BufferSeekForward(&buf, 100); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_STR16); + assert_int_equal(obj.as.str_size, 300); + M_BufferSeekForward(&buf, 300); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_STR32); + assert_int_equal(obj.as.str_size, 70000); + M_BufferSeekForward(&buf, 70000); + + M_BufferSeek(&buf, 0); + + assert_true(cmp_write_str_marker_v4(&cmp, 7)); + M_BufferWrite(&buf, "bananas", 7); + + assert_true(cmp_write_str_marker_v4(&cmp, 100)); + for (size_t i = 0; i < 100; i++) { + M_BufferWrite(&buf, "C", 1); + } + + assert_true(cmp_write_str_marker_v4(&cmp, 300)); + for (size_t i = 0; i < 300; i++) { + M_BufferWrite(&buf, "C", 1); + } + + assert_true(cmp_write_str_marker_v4(&cmp, 70000)); + for(size_t i = 0; i < 70000; i++) { + M_BufferWrite(&buf, "C", 1); + } + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); + assert_int_equal(obj.as.str_size, 7); + M_BufferSeekForward(&buf, 7); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_STR16); + assert_int_equal(obj.as.str_size, 100); + M_BufferSeekForward(&buf, 100); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_STR16); + assert_int_equal(obj.as.str_size, 300); + M_BufferSeekForward(&buf, 300); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_STR32); + assert_int_equal(obj.as.str_size, 70000); + M_BufferSeekForward(&buf, 70000); + + M_BufferSeek(&buf, 0); + + char *str8 = malloc(201); + char *str16 = malloc(301); + char *str32 = malloc(70001); + + *(str8 + 200) = '\0'; + *(str16 + 300) = '\0'; + *(str32 + 70000) = '\0'; + + assert_true(cmp_write_str(&cmp, str8, 200)); + assert_true(cmp_write_str(&cmp, str16, 300)); + assert_true(cmp_write_str(&cmp, str32, 70000)); + + assert_true(cmp_write_str_v4(&cmp, "C", 1)); + assert_true(cmp_write_str_v4(&cmp, str8, 200)); + assert_true(cmp_write_str_v4(&cmp, str16, 300)); + assert_true(cmp_write_str_v4(&cmp, str32, 70000)); + + free(str16); + free(str32); + + assert_false(cmp_write_fixstr_marker(&cmp, 200)); + + M_BufferClear(&buf); + assert_true(cmp_write_str(&cmp, "Hello", 5)); + M_BufferSeek(&buf, 0); + size = 6; + assert_true(cmp_read_str(&cmp, str8, &size)); + M_BufferSeek(&buf, 0); + size = 5; + assert_false(cmp_read_str(&cmp, str8, &size)); + + reader_successes = 1; + M_BufferSeek(&buf, 0); + size = 6; + assert_false(cmp_read_str(&cmp, str8, &size)); + + reader_successes = 1; + M_BufferClear(&buf); + assert_true(cmp_write_str_marker(&cmp, UINT32_MAX)); + M_BufferSeek(&buf, 0); + size = 1; + assert_false(cmp_read_str(&cmp, str8, &size)); + + reader_successes = 1; + M_BufferClear(&buf); + assert_true(cmp_write_str(&cmp, "Hello", 5)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_false(cmp_object_to_str(&cmp, &obj, str8, 6)); + + reader_successes = -1; + M_BufferClear(&buf); + assert_true(cmp_write_str(&cmp, "Hello", 5)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_false(cmp_object_to_str(&cmp, &obj, str8, 5)); + assert_true(cmp_object_to_str(&cmp, &obj, str8, 6)); + + free(str8); + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_array(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + test_format( + cmp_write_fixarray, cmp_read_array, array_size, uint32_t, 0, "\x90", 1 + ); + test_format( + cmp_write_fixarray, cmp_read_array, array_size, uint32_t, 10, "\x9a", 1 + ); + test_format( + cmp_write_array16, + cmp_read_array, + array_size, + uint32_t, + 0, + "\xdc\x00\x00", + 3 + ); + test_format( + cmp_write_array16, + cmp_read_array, + array_size, + uint32_t, + 10, + "\xdc\x00\x0a", + 3 + ); + test_format( + cmp_write_array32, + cmp_read_array, + array_size, + uint32_t, + 0, + "\xdd\x00\x00\x00\x00", + 5 + ); + test_format( + cmp_write_array32, + cmp_read_array, + array_size, + uint32_t, + 10, + "\xdd\x00\x00\x00\x0a", + 5 + ); + test_format( + cmp_write_array, cmp_read_array, array_size, uint32_t, 0, "\x90", 1 + ); + test_format( + cmp_write_array, cmp_read_array, array_size, uint32_t, 10, "\x9a", 1 + ); + + M_BufferSeek(&buf, 0); + + assert_string_equal(cmp_strerror(&cmp), ""); + + assert_false(cmp_write_fixarray(&cmp, 200)); + + assert_string_equal(cmp_strerror(&cmp), "Input value is too large"); + + assert_true(cmp_write_array(&cmp, 0xFFFE)); + for (size_t i = 0; i < 0xFFFE; i++) { + assert_true(cmp_write_uinteger(&cmp, 1)); + } + + assert_true(cmp_write_array(&cmp, 0x10000)); + for (size_t i = 0; i < 0x10000; i++) { + assert_true(cmp_write_uinteger(&cmp, 1)); + } + + M_BufferSeek(&buf, 0); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_ARRAY16); + assert_int_equal(obj.as.array_size, 0xFFFE); + for (size_t i = 0; i < 0xFFFE; i++) { + uint64_t n; + assert_true(cmp_read_uinteger(&cmp, &n)); + } + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_ARRAY32); + assert_int_equal(obj.as.array_size, 0x10000); + for (size_t i = 0; i < 0x10000; i++) { + uint64_t n; + assert_true(cmp_read_uinteger(&cmp, &n)); + } + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_map(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + test_format( + cmp_write_fixmap, cmp_read_map, map_size, uint32_t, 0, "\x80", 1 + ); + test_format( + cmp_write_fixmap, cmp_read_map, map_size, uint32_t, 10, "\x8a", 1 + ); + test_format( + cmp_write_map16, cmp_read_map, map_size, uint32_t, 0, "\xde\x00\x00", 3 + ); + test_format( + cmp_write_map16, cmp_read_map, map_size, uint32_t, 10, "\xde\x00\x0a", 3 + ); + test_format( + cmp_write_map32, + cmp_read_map, + map_size, + uint32_t, + 0, + "\xdf\x00\x00\x00\x00", + 5 + ); + test_format( + cmp_write_map32, + cmp_read_map, + map_size, + uint32_t, + 10, + "\xdf\x00\x00\x00\x0a", + 5 + ); + test_format( + cmp_write_map, cmp_read_map, map_size, uint32_t, 0, "\x80", 1 + ); + test_format( + cmp_write_map, cmp_read_map, map_size, uint32_t, 10, "\x8a", 1 + ); + + M_BufferSeek(&buf, 0); + + assert_true(cmp_write_map(&cmp, 3)); + assert_true(cmp_write_str(&cmp, "a", 1)); + assert_true(cmp_write_str(&cmp, "apple", 5)); + assert_true(cmp_write_str(&cmp, "b", 1)); + assert_true(cmp_write_str(&cmp, "banana", 6)); + assert_true(cmp_write_str(&cmp, "c", 1)); + assert_true(cmp_write_str(&cmp, "coconut", 7)); + + M_BufferSeek(&buf, 0); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXMAP); + assert_int_equal(obj.as.map_size, 3); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); + assert_int_equal(obj.as.str_size, 1); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), "a", 1); + M_BufferSeekForward(&buf, 1); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); + assert_int_equal(obj.as.str_size, 5); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), "apple", 5); + M_BufferSeekForward(&buf, 5); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); + assert_int_equal(obj.as.str_size, 1); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), "b", 1); + M_BufferSeekForward(&buf, 1); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); + assert_int_equal(obj.as.str_size, 6); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), "banana", 6); + M_BufferSeekForward(&buf, 6); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); + assert_int_equal(obj.as.str_size, 1); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), "c", 1); + M_BufferSeekForward(&buf, 1); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); + assert_int_equal(obj.as.str_size, 7); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), "coconut", 7); + M_BufferSeekForward(&buf, 7); + + M_BufferSeek(&buf, 0); + + assert_false(cmp_write_fixmap(&cmp, 200)); + + assert_string_equal(cmp_strerror(&cmp), "Input value is too large"); + + assert_true(cmp_write_map(&cmp, 0xFFFE)); + for (size_t i = 0; i < 0xFFFE; i++) { + assert_true(cmp_write_uinteger(&cmp, 1)); + assert_true(cmp_write_uinteger(&cmp, 1)); + } + + assert_true(cmp_write_map(&cmp, 0x10000)); + for (size_t i = 0; i < 0x10000; i++) { + assert_true(cmp_write_uinteger(&cmp, 1)); + assert_true(cmp_write_uinteger(&cmp, 1)); + } + + M_BufferSeek(&buf, 0); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_MAP16); + assert_int_equal(obj.as.map_size, 0xFFFE); + for (size_t i = 0; i < 0xFFFE; i++) { + uint64_t n; + assert_true(cmp_read_uinteger(&cmp, &n)); + assert_true(cmp_read_uinteger(&cmp, &n)); + } + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_MAP32); + assert_int_equal(obj.as.map_size, 0x10000); + for (size_t i = 0; i < 0x10000; i++) { + uint64_t n; + assert_true(cmp_read_uinteger(&cmp, &n)); + assert_true(cmp_read_uinteger(&cmp, &n)); + } + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_ext(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + int8_t etype; + uint8_t esize8; + uint16_t esize16; + uint32_t esize32; + char outfixedbuf1[1]; + char outfixedbuf2[2]; + char outfixedbuf4[4]; + char outfixedbuf8[8]; + char outfixedbuf16[16]; + char *buf8 = malloc(0x7F); + char *outbuf8 = malloc(0x7F); + char *buf16 = malloc(0x7FFF); + char *outbuf16 = malloc(0x7FFF); + char *buf32 = malloc(0x10000); + char *outbuf32 = malloc(0x10000); + + memset(buf8, 'C', 0x7F); + memset(buf16, 'C', 0x7FFF); + memset(buf32, 'C', 0x10000); + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + test_fixext_format(cmp_write_fixext1, 1, 1, "C", "\xd4\x01\x43", 3); + test_fixext_format(cmp_write_fixext2, 2, 2, "CC", "\xd5\x02\x43\x43", 4); + test_fixext_format( + cmp_write_fixext4, 3, 4, "CCCC", "\xd6\x03\x43\x43\x43\x43", 6 + ); + test_fixext_format( + cmp_write_fixext8, + 4, + 8, + "CCCCCCCC", + "\xd7\x04\x43\x43\x43\x43\x43\x43\x43\x43", + 10 + ); + test_fixext_format( + cmp_write_fixext16, + 5, + 16, + "CCCCCCCCCCCCCCCC", + "\xd8\x05\x43\x43\x43\x43\x43\x43\x43\x43\x43\x43\x43\x43\x43\x43\x43\x43", + 18 + ); + test_ext_format(cmp_write_ext8, 1, 1, "C", "\xc7\x01\x01\x43", 4); + test_ext_format( + cmp_write_ext8, 2, 3, "CCC", "\xc7\x03\x02\x43\x43\x43", 6 + ); + test_ext_format(cmp_write_ext16, 1, 1, "C", "\xc8\x00\x01\x01\x43", 5); + test_ext_format( + cmp_write_ext16, 2, 3, "CCC", "\xc8\x00\x03\x02\x43\x43\x43", 7 + ); + test_ext_format( + cmp_write_ext32, 1, 1, "C", "\xc9\x00\x00\x00\x01\x01\x43", 7 + ); + test_ext_format( + cmp_write_ext32, 2, 3, "CCC", "\xc9\x00\x00\x00\x03\x02\x43\x43\x43", 9 + ); + test_ext_format(cmp_write_ext, 1, 1, "C", "\xd4\x01\x43", 3); + test_ext_format( + cmp_write_ext, 2, 3, "CCC", "\xc7\x03\x02\x43\x43\x43", 6 + ); + + M_BufferSeek(&buf, 0); + writer_successes = 0; + assert_false(cmp_write_ext(&cmp, 7, 0x7F, buf8)); + + M_BufferSeek(&buf, 0); + writer_successes = 1; + assert_false(cmp_write_ext(&cmp, 7, 0x7F, buf8)); + + M_BufferSeek(&buf, 0); + writer_successes = 2; + assert_false(cmp_write_ext(&cmp, 7, 0x7F, buf8)); + + M_BufferSeek(&buf, 0); + writer_successes = 3; + assert_false(cmp_write_ext(&cmp, 7, 0x7F, buf8)); + + M_BufferSeek(&buf, 0); + writer_successes = 4; + assert_true(cmp_write_ext(&cmp, 7, 0x7F, buf8)); + + M_BufferSeek(&buf, 0); + writer_successes = 0; + assert_false(cmp_write_ext8(&cmp, 7, 0x7F, buf8)); + + M_BufferSeek(&buf, 0); + writer_successes = 1; + assert_false(cmp_write_ext8(&cmp, 7, 0x7F, buf8)); + + M_BufferSeek(&buf, 0); + writer_successes = 2; + assert_false(cmp_write_ext8(&cmp, 7, 0x7F, buf8)); + + M_BufferSeek(&buf, 0); + writer_successes = 3; + assert_false(cmp_write_ext8(&cmp, 7, 0x7F, buf8)); + + M_BufferSeek(&buf, 0); + writer_successes = 4; + assert_true(cmp_write_ext8(&cmp, 7, 0x7F, buf8)); + + M_BufferSeek(&buf, 0); + writer_successes = 0; + assert_false(cmp_write_ext16(&cmp, 7, 0x7FFF, buf16)); + + M_BufferSeek(&buf, 0); + writer_successes = 1; + assert_false(cmp_write_ext16(&cmp, 7, 0x7FFF, buf16)); + + M_BufferSeek(&buf, 0); + writer_successes = 2; + assert_false(cmp_write_ext16(&cmp, 7, 0x7FFF, buf16)); + + M_BufferSeek(&buf, 0); + writer_successes = 3; + assert_false(cmp_write_ext16(&cmp, 7, 0x7FFF, buf16)); + + M_BufferSeek(&buf, 0); + writer_successes = 4; + assert_true(cmp_write_ext16(&cmp, 7, 0x7FFF, buf16)); + + M_BufferSeek(&buf, 0); + writer_successes = 0; + assert_false(cmp_write_ext32(&cmp, 7, 0x10000, buf32)); + + M_BufferSeek(&buf, 0); + writer_successes = 1; + assert_false(cmp_write_ext32(&cmp, 7, 0x10000, buf32)); + + M_BufferSeek(&buf, 0); + writer_successes = 2; + assert_false(cmp_write_ext32(&cmp, 7, 0x10000, buf32)); + + M_BufferSeek(&buf, 0); + writer_successes = 3; + assert_false(cmp_write_ext32(&cmp, 7, 0x10000, buf32)); + + M_BufferSeek(&buf, 0); + writer_successes = 4; + assert_true(cmp_write_ext32(&cmp, 7, 0x10000, buf32)); + + writer_successes = -1; + + M_BufferSeek(&buf, 0); + + assert_true(cmp_write_ext(&cmp, 2, 1, "C")); + assert_true(cmp_write_ext(&cmp, 3, 2, "CC")); + assert_true(cmp_write_ext(&cmp, 4, 4, "CCCC")); + assert_true(cmp_write_ext(&cmp, 5, 8, "CCCCCCCC")); + assert_true(cmp_write_ext(&cmp, 6, 16, "CCCCCCCCCCCCCCCC")); + assert_true(cmp_write_ext(&cmp, 7, 0x7F, buf8)); + assert_true(cmp_write_ext(&cmp, 8, 0x7FFF, buf16)); + assert_true(cmp_write_ext(&cmp, 9, 0x10000, buf32)); + + M_BufferClear(&buf); + + assert_true(cmp_write_ext(&cmp, 2, 1, "C")); + assert_true(cmp_write_ext(&cmp, 3, 2, "CC")); + assert_true(cmp_write_ext(&cmp, 4, 4, "CCCC")); + assert_true(cmp_write_ext(&cmp, 5, 8, "CCCCCCCC")); + assert_true(cmp_write_ext(&cmp, 6, 16, "CCCCCCCCCCCCCCCC")); + assert_true(cmp_write_ext(&cmp, 7, 0x7F, buf8)); + assert_true(cmp_write_ext(&cmp, 8, 0x7FFF, buf16)); + assert_true(cmp_write_ext(&cmp, 9, 0x10000, buf32)); + assert_true(cmp_write_nil(&cmp)); + + M_BufferSeek(&buf, 0); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_true(cmp_object_is_ext(&obj)); + M_BufferSeekForward(&buf, obj.as.ext.size); + assert_true(cmp_read_object(&cmp, &obj)); + assert_true(cmp_object_is_ext(&obj)); + M_BufferSeekForward(&buf, obj.as.ext.size); + assert_true(cmp_read_object(&cmp, &obj)); + assert_true(cmp_object_is_ext(&obj)); + M_BufferSeekForward(&buf, obj.as.ext.size); + assert_true(cmp_read_object(&cmp, &obj)); + assert_true(cmp_object_is_ext(&obj)); + M_BufferSeekForward(&buf, obj.as.ext.size); + assert_true(cmp_read_object(&cmp, &obj)); + assert_true(cmp_object_is_ext(&obj)); + M_BufferSeekForward(&buf, obj.as.ext.size); + assert_true(cmp_read_object(&cmp, &obj)); + assert_true(cmp_object_is_ext(&obj)); + M_BufferSeekForward(&buf, obj.as.ext.size); + assert_true(cmp_read_object(&cmp, &obj)); + assert_true(cmp_object_is_ext(&obj)); + M_BufferSeekForward(&buf, obj.as.ext.size); + assert_true(cmp_read_object(&cmp, &obj)); + assert_true(cmp_object_is_ext(&obj)); + M_BufferSeekForward(&buf, obj.as.ext.size); + assert_true(cmp_read_object(&cmp, &obj)); + assert_false(cmp_object_is_ext(&obj)); + + M_BufferSeek(&buf, 0); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT1); + assert_int_equal(obj.as.ext.type, 2); + assert_int_equal(obj.as.ext.size, 1); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), "C", 1); + M_BufferSeekForward(&buf, 1); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT2); + assert_int_equal(obj.as.ext.type, 3); + assert_int_equal(obj.as.ext.size, 2); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), "CC", 2); + M_BufferSeekForward(&buf, 2); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT4); + assert_int_equal(obj.as.ext.type, 4); + assert_int_equal(obj.as.ext.size, 4); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), "CCCC", 4); + M_BufferSeekForward(&buf, 4); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT8); + assert_int_equal(obj.as.ext.type, 5); + assert_int_equal(obj.as.ext.size, 8); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), "CCCCCCCC", 8); + M_BufferSeekForward(&buf, 8); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT16); + assert_int_equal(obj.as.ext.type, 6); + assert_int_equal(obj.as.ext.size, 16); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), "CCCCCCCCCCCCCCCC", 16); + M_BufferSeekForward(&buf, 16); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_EXT8); + assert_int_equal(obj.as.ext.type, 7); + assert_int_equal(obj.as.ext.size, 0x7F); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), buf8, 0x7F); + M_BufferSeekForward(&buf, 0x7F); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_EXT16); + assert_int_equal(obj.as.ext.type, 8); + assert_int_equal(obj.as.ext.size, 0x7FFF); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), buf16, 0x7FFF); + M_BufferSeekForward(&buf, 0x7FFF); + + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_EXT32); + assert_int_equal(obj.as.ext.type, 9); + assert_int_equal(obj.as.ext.size, 0x10000); + assert_memory_equal(M_BufferGetDataAtCursor(&buf), buf32, 0x10000); + M_BufferSeekForward(&buf, 0x100000); + + M_BufferSeek(&buf, 0); + + assert_true(cmp_read_fixext1(&cmp, &etype, outfixedbuf1)); + assert_int_equal(etype, 2); + assert_memory_equal(outfixedbuf1, "C", 1); + + assert_true(cmp_read_fixext2(&cmp, &etype, outfixedbuf2)); + assert_int_equal(etype, 3); + assert_memory_equal(outfixedbuf2, "CC", 2); + + assert_true(cmp_read_fixext4(&cmp, &etype, outfixedbuf4)); + assert_int_equal(etype, 4); + assert_memory_equal(outfixedbuf4, "CCCC", 4); + + assert_true(cmp_read_fixext8(&cmp, &etype, outfixedbuf8)); + assert_int_equal(etype, 5); + assert_memory_equal(outfixedbuf8, "CCCCCCCC", 8); + + assert_true(cmp_read_fixext16(&cmp, &etype, outfixedbuf16)); + assert_int_equal(etype, 6); + assert_memory_equal(outfixedbuf16, "CCCCCCCCCCCCCCCC", 16); + + assert_true(cmp_read_ext8(&cmp, &etype, &esize8, outbuf8)); + assert_int_equal(etype, 7); + assert_int_equal(esize8, 0x7F); + assert_memory_equal(outbuf8, buf8, 0x7F); + + assert_true(cmp_read_ext16(&cmp, &etype, &esize16, outbuf16)); + assert_int_equal(etype, 8); + assert_int_equal(esize16, 0x7FFF); + assert_memory_equal(outbuf16, buf16, 0x7FFF); + + assert_true(cmp_read_ext32(&cmp, &etype, &esize32, outbuf32)); + assert_int_equal(etype, 9); + assert_int_equal(esize32, 0x10000); + assert_memory_equal(outbuf32, buf32, 16); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_true(cmp_object_as_ext(&obj, &etype, &esize32)); + + M_BufferSeek(&buf, 0); + + assert_true(cmp_write_ext_marker(&cmp, 2, 1)); + assert_true(cmp_write_ext_marker(&cmp, 3, 2)); + assert_true(cmp_write_ext_marker(&cmp, 4, 4)); + assert_true(cmp_write_ext_marker(&cmp, 5, 8)); + assert_true(cmp_write_ext_marker(&cmp, 6, 16)); + assert_true(cmp_write_ext_marker(&cmp, 7, 0x7F)); + assert_true(cmp_write_ext_marker(&cmp, 8, 0x7FFF)); + assert_true(cmp_write_ext_marker(&cmp, 9, 0x10000)); + + free(buf8); + free(outbuf8); + free(buf16); + free(outbuf16); + free(buf32); + free(outbuf32); + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_obj(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + obj_write(cmp_write_sint, -1); + obj_test(cmp_object_is_char, cmp_object_as_char, "char", int8_t, -1); + obj_test(cmp_object_is_short, cmp_object_as_short, "short", int16_t, -1); + obj_test(cmp_object_is_int, cmp_object_as_int, "int", int32_t, -1); + obj_test(cmp_object_is_long, cmp_object_as_long, "long", int64_t, -1); + obj_test( + cmp_object_is_sinteger, cmp_object_as_sinteger, "sinteger", int64_t, -1 + ); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_sint, -129); + obj_test(cmp_object_is_short, cmp_object_as_short, "short", int16_t, -129); + obj_test(cmp_object_is_int, cmp_object_as_int, "int", int32_t, -129); + obj_test(cmp_object_is_long, cmp_object_as_long, "long", int64_t, -129); + obj_test( + cmp_object_is_sinteger, cmp_object_as_sinteger, "sinteger", int64_t, -129 + ); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_sint, -32769); + obj_test(cmp_object_is_int, cmp_object_as_int, "int", int32_t, -32769); + obj_test(cmp_object_is_long, cmp_object_as_long, "long", int64_t, -32769); + obj_test( + cmp_object_is_sinteger, cmp_object_as_sinteger, "sinteger", int64_t, -32769 + ); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_sint, -2147483649); + obj_test( + cmp_object_is_long, cmp_object_as_long, "long", int64_t, -2147483649 + ); + obj_test( + cmp_object_is_sinteger, + cmp_object_as_sinteger, + "sinteger", + int64_t, -2147483649 + ); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_uint, 1); + obj_test(cmp_object_is_uchar, cmp_object_as_uchar, "uchar", uint8_t, 1); + obj_test(cmp_object_is_ushort, cmp_object_as_ushort, "ushort", uint16_t, 1); + obj_test(cmp_object_is_uint, cmp_object_as_uint, "uint", uint32_t, 1); + obj_test(cmp_object_is_ulong, cmp_object_as_ulong, "ulong", uint64_t, 1); + obj_test( + cmp_object_is_uinteger, cmp_object_as_uinteger, "uinteger", uint64_t, 1 + ); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_uint, 255); + obj_test(cmp_object_is_uchar, cmp_object_as_uchar, "uchar", uint8_t, 255); + obj_test( + cmp_object_is_ushort, cmp_object_as_ushort, "ushort", uint16_t, 255 + ); + obj_test(cmp_object_is_uint, cmp_object_as_uint, "uint", uint32_t, 255); + obj_test(cmp_object_is_ulong, cmp_object_as_ulong, "ulong", uint64_t, 255); + obj_test( + cmp_object_is_uinteger, cmp_object_as_uinteger, "uinteger", uint64_t, 255 + ); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_uint, 256); + obj_test( + cmp_object_is_ushort, cmp_object_as_ushort, "ushort", uint16_t, 256 + ); + obj_test(cmp_object_is_uint, cmp_object_as_uint, "uint", uint32_t, 256); + obj_test(cmp_object_is_ulong, cmp_object_as_ulong, "ulong", uint64_t, 256); + obj_test( + cmp_object_is_uinteger, cmp_object_as_uinteger, "uinteger", uint64_t, 256 + ); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_uint, 65535); + obj_test( + cmp_object_is_ushort, cmp_object_as_ushort, "ushort", uint16_t, 65535 + ); + obj_test(cmp_object_is_uint, cmp_object_as_uint, "uint", uint32_t, 65535); + obj_test(cmp_object_is_ulong, cmp_object_as_ulong, "ulong", uint64_t, 65535); + obj_test( + cmp_object_is_uinteger, + cmp_object_as_uinteger, + "uinteger", + uint64_t, + 65535 + ); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_uint, 65536); + obj_test(cmp_object_is_uint, cmp_object_as_uint, "uint", uint32_t, 65536); + obj_test(cmp_object_is_ulong, cmp_object_as_ulong, "ulong", uint64_t, 65536); + obj_test( + cmp_object_is_uinteger, + cmp_object_as_uinteger, + "uinteger", + uint64_t, + 65536 + ); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_uint, 4294967295); + obj_test( + cmp_object_is_uint, cmp_object_as_uint, "uint", uint32_t, 4294967295 + ); + obj_test( + cmp_object_is_ulong, cmp_object_as_ulong, "ulong", uint64_t, 4294967295 + ); + obj_test( + cmp_object_is_uinteger, + cmp_object_as_uinteger, + "uinteger", + uint64_t, + 4294967295 + ); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_uint, 4294967296); + obj_test( + cmp_object_is_ulong, cmp_object_as_ulong, "ulong", uint64_t, 4294967296 + ); + obj_test( + cmp_object_is_uinteger, + cmp_object_as_uinteger, + "uinteger", + uint64_t, + 4294967296 + ); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + +#ifndef CMP_NO_FLOAT + obj_write(cmp_write_float, 1.f); + obj_test(cmp_object_is_float, cmp_object_as_float, "float", float, 1.f); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); + obj_test_not(cmp_object_is_double, "double"); + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_double, 1.0); + obj_test(cmp_object_is_double, cmp_object_as_double, "double", double, 1.0); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); +#endif + + obj_write_no_val(cmp_write_nil); + obj_test_no_read(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write_no_val(cmp_write_true); + obj_test(cmp_object_is_bool, cmp_object_as_bool, "bool", bool, true); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write_no_val(cmp_write_false); + obj_test(cmp_object_is_bool, cmp_object_as_bool, "bool", bool, false); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_bool, true); + obj_test(cmp_object_is_bool, cmp_object_as_bool, "bool", bool, true); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write(cmp_write_bool, false); + obj_test(cmp_object_is_bool, cmp_object_as_bool, "bool", bool, false); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_str, "str"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write_len(cmp_write_str, "Hey there", 9); + obj_str_test("Hey there"); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + // Test new cmp_object_to_str + obj_write_len(cmp_write_str, "Hey there", 9); + obj_to_str_test("Hey there"); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + + obj_write_len(cmp_write_bin, "Hey there", 9); + obj_bin_test("Hey there", 9); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "string"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj_write_len(cmp_write_bin, "Hey there", 9); + obj_to_bin_test("Hey there", 9); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "string"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + M_BufferSeek(&buf, 0); + cmp_write_array(&cmp, 2); + cmp_write_uint(&cmp, 1); + cmp_write_uint(&cmp, 2); + M_BufferSeek(&buf, 0); + cmp_read_object(&cmp, &obj); + obj_array_test(1, 2); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "string"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_map, "map"); + obj_test_not(cmp_object_is_ext, "ext"); + + M_BufferSeek(&buf, 0); + cmp_write_map(&cmp, 1); + cmp_write_uint(&cmp, 1); + cmp_write_uint(&cmp, 2); + M_BufferSeek(&buf, 0); + cmp_read_object(&cmp, &obj); + obj_map_test(1, 2); + obj_test_not(cmp_object_is_char, "char"); + obj_test_not(cmp_object_is_short, "short"); + obj_test_not(cmp_object_is_int, "int"); + obj_test_not(cmp_object_is_long, "long"); + obj_test_not(cmp_object_is_sinteger, "sinteger"); + obj_test_not(cmp_object_is_uchar, "uchar"); + obj_test_not(cmp_object_is_ushort, "ushort"); + obj_test_not(cmp_object_is_uint, "uint"); + obj_test_not(cmp_object_is_ulong, "ulong"); + obj_test_not(cmp_object_is_uinteger, "uinteger"); +#ifndef CMP_NO_FLOAT + obj_test_not(cmp_object_is_float, "float"); + obj_test_not(cmp_object_is_double, "double"); +#endif + obj_test_not(cmp_object_is_nil, "nil"); + obj_test_not(cmp_object_is_bool, "bool"); + obj_test_not(cmp_object_is_str, "string"); + obj_test_not(cmp_object_is_bin, "bin"); + obj_test_not(cmp_object_is_array, "array"); + obj_test_not(cmp_object_is_ext, "ext"); + + obj.type = CMP_TYPE_NIL; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + + obj.type = CMP_TYPE_BOOLEAN; + obj.as.boolean = true; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + + obj.type = CMP_TYPE_BOOLEAN; + obj.as.boolean = false; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + + obj.type = CMP_TYPE_POSITIVE_FIXNUM; + obj.as.u8 = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + + obj.type = CMP_TYPE_POSITIVE_FIXNUM; + obj.as.u8 = 1; + assert_true(cmp_write_object(&cmp, &obj)); + + obj.type = CMP_TYPE_UINT8; + obj.as.u8 = 200; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_UINT16; + obj.as.u16 = 300; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_UINT32; + obj.as.u32 = 70000; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_UINT64; + obj.as.u64 = 0x100000002; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_NEGATIVE_FIXNUM; + obj.as.s8 = -1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_SINT8; + obj.as.s8 = -100; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_SINT16; + obj.as.s16 = -200; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_SINT32; + obj.as.s32 = -33000; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_SINT64; + obj.as.s64 = 0x100000002; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + +#ifndef CMP_NO_FLOAT + obj.type = CMP_TYPE_FLOAT; + obj.as.flt = 1.1f; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_DOUBLE; + obj.as.dbl = 1.1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); +#endif + + obj.type = CMP_TYPE_BIN8; + obj.as.bin_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + obj.type = CMP_TYPE_BIN16; + obj.as.bin_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + obj.type = CMP_TYPE_BIN32; + obj.as.bin_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + obj.type = CMP_TYPE_EXT8; + obj.as.ext.type = 2; + obj.as.ext.size = 2; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_EXT16; + obj.as.ext.type = 2; + obj.as.ext.size = 2; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_EXT32; + obj.as.ext.type = 2; + obj.as.ext.size = 2; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_FIXEXT1; + obj.as.ext.type = 2; + obj.as.ext.size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_FIXEXT2; + obj.as.ext.type = 2; + obj.as.ext.size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_FIXEXT4; + obj.as.ext.type = 2; + obj.as.ext.size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_FIXEXT8; + obj.as.ext.type = 2; + obj.as.ext.size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_FIXEXT16; + obj.as.ext.type = 2; + obj.as.ext.size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_FIXSTR; + obj.as.str_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_STR8; + obj.as.str_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + obj.type = CMP_TYPE_STR16; + obj.as.str_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_STR32; + obj.as.str_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_FIXARRAY; + obj.as.array_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_ARRAY16; + obj.as.array_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_ARRAY32; + obj.as.array_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_FIXMAP; + obj.as.map_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_MAP16; + obj.as.map_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + obj.type = CMP_TYPE_MAP32; + obj.as.map_size = 1; + assert_true(cmp_write_object(&cmp, &obj)); + assert_true(cmp_write_object_v4(&cmp, &obj)); + + obj.type = 100; + assert_false(cmp_write_object(&cmp, &obj)); + assert_false(cmp_write_object_v4(&cmp, &obj)); + + teardown_cmp_and_buf(&cmp, &buf); +} + +/* Thanks to andreyvps for this test */ +#ifndef CMP_NO_FLOAT +void test_float_flip(void **state) { + buf_t buf; + cmp_ctx_t cmp; + float in; + float out; + char init[4]; + char outnit[4]; + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + /* + * Writing and reading a float's bytes using cmp mangles one of the bytes for + * certain floats. This is one of them. + */ + + /* Specify the binary representation of a problematic float */ + init[0] = -1; + init[1] = -121; + init[2] = -95; + init[3] = -66; + + /* construct the float from the memory, should be -0.315490693 */ + memcpy(&in, init, sizeof(in)); + + assert_true(cmp_write_float(&cmp, in)); + + /* + * cmp writes the float header, then the bytes of the float in reversed order + * (endianness) + */ + + assert_int_equal(buf.data[1], init[3]); + assert_int_equal(buf.data[2], init[2]); + assert_int_equal(buf.data[3], init[1]); + assert_int_equal(buf.data[4], init[0]); + + M_BufferSeek(&buf, 0); + + /* read in the float using cmp. */ + assert_true(cmp_read_float(&cmp, &out)); + + memcpy(outnit, &out, sizeof(out)); + + /* The reader reads in exactly what was in the buffer */ + assert_int_equal(buf.data[1], outnit[3]); + assert_int_equal(buf.data[2], outnit[2]); + assert_int_equal(buf.data[3], outnit[1]); + assert_int_equal(buf.data[4], outnit[0]); + + /* + * The reader only seems ok. The issue happens when you fiddle with the + * float's bits in the first place. By the time you write, it's a "valid" + * float (though has the wrong contents), so when you read it, you're reading + * a seemingly ok float. When you fix the writer to write out the correct + * bytes, the reader then makes the same mistake. The fix is to write the + * flipped bytes to a buffer and then write that buffer out. On the read + * end, you read into a buffer, then write the flipped bytes into a float. + * This way, the float is never populated with invalid bytes. + */ + + assert_true(in == out); + + teardown_cmp_and_buf(&cmp, &buf); +} +#endif + +void test_skipping(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + cmp_skipper skip; + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + skip = cmp.skip; + + M_BufferEnsureCapacity(&buf, (66000 * 2) + 32); + + assert_true(cmp_write_true(&cmp)); + assert_true(cmp_write_nil(&cmp)); + assert_true(cmp_write_integer(&cmp, -8)); + assert_true(cmp_write_array(&cmp, 10)); + + for (uint32_t i = 0; i < 10; i++) { + assert_true(cmp_write_integer(&cmp, i)); + } + + assert_true(cmp_write_array(&cmp, 10)); + assert_true(cmp_write_uinteger(&cmp, 8)); + assert_true(cmp_write_integer(&cmp, -120)); + assert_true(cmp_write_uinteger(&cmp, 200)); + assert_true(cmp_write_integer(&cmp, -32000)); + assert_true(cmp_write_uinteger(&cmp, 64000)); + assert_true(cmp_write_integer(&cmp, -33000)); + assert_true(cmp_write_uinteger(&cmp, 66000)); + assert_true(cmp_write_integer(&cmp, -2150000000)); + assert_true(cmp_write_uinteger(&cmp, 4300000000)); + assert_true(cmp_write_map(&cmp, 3)); + assert_true(cmp_write_str(&cmp, "a", 1)); + assert_true(cmp_write_str(&cmp, "apple", 5)); + assert_true(cmp_write_str(&cmp, "b", 1)); + assert_true(cmp_write_array(&cmp, 2)); + assert_true(cmp_write_str(&cmp, "banana", 6)); + assert_true(cmp_write_str(&cmp, "blackberry", 10)); + assert_true(cmp_write_str(&cmp, "c", 1)); + assert_true(cmp_write_str(&cmp, "coconut", 7)); + assert_true(cmp_write_map(&cmp, 66000)); + + for (uint32_t i = 0; i < 66000; i++) { + assert_true(cmp_write_integer(&cmp, 1)); + assert_true(cmp_write_integer(&cmp, 1)); + } + + assert_true(cmp_write_nil(&cmp)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object(&cmp, &obj)); + + cmp.skip = NULL; + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); // true + assert_true(cmp_skip_object_no_limit(&cmp)); // nil + assert_true(cmp_skip_object_no_limit(&cmp)); // integer + assert_true(cmp_skip_object_no_limit(&cmp)); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + assert_true(cmp_skip_object_no_limit(&cmp)); // [..., {"a": "apple", + // "b": ["banana", "blackberry"], + // "c": "coconut"}] + assert_true(cmp_skip_object_no_limit(&cmp)); // {1: 1 (* 66000)} + assert_true(cmp_skip_object_no_limit(&cmp)); // nil + cmp.skip = skip; + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + + M_BufferSeek(&buf, 0); + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_false(cmp_skip_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXARRAY); + for (uint32_t i = 0; i < obj.as.array_size; i++) { + assert_true(cmp_skip_object(&cmp, &obj)); + } + assert_false(cmp_skip_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXARRAY); + for (uint32_t i = 0; i < 9; i++) { + assert_true(cmp_skip_object(&cmp, &obj)); + } + assert_false(cmp_skip_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXMAP); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_false(cmp_skip_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXARRAY); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_false(cmp_skip_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_MAP32); + for (uint32_t i = 0; i < obj.as.map_size; i++) { + assert_true(cmp_skip_object(&cmp, &obj)); + } + assert_true(cmp_skip_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_flat(&cmp, &obj)); + assert_false(cmp_skip_object_flat(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXMAP); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object_flat(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object_flat(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_flat(&cmp, &obj)); + assert_false(cmp_skip_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXARRAY); + for (uint32_t i = 0; i < 9; i++) { + assert_true(cmp_skip_object(&cmp, &obj)); + } + assert_false(cmp_skip_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXMAP); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object_flat(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_true(cmp_skip_object(&cmp, &obj)); + assert_false(cmp_skip_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_MAP32); + for (uint32_t i = 0; i < obj.as.map_size; i++) { + assert_true(cmp_skip_object(&cmp, &obj)); + } + assert_true(cmp_skip_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_BOOLEAN); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_NIL); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_NEGATIVE_FIXNUM); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXARRAY); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_POSITIVE_FIXNUM); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_POSITIVE_FIXNUM); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_POSITIVE_FIXNUM); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_POSITIVE_FIXNUM); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_POSITIVE_FIXNUM); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_POSITIVE_FIXNUM); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_POSITIVE_FIXNUM); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_POSITIVE_FIXNUM); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_POSITIVE_FIXNUM); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_POSITIVE_FIXNUM); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXARRAY); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_POSITIVE_FIXNUM); /* 8 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_SINT8); /* -120 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT8); /* 200 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_SINT16); /* -32000 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT16); /* 64000 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_SINT32); /* -33000 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT32); /* 66000 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_SINT64); /* -2150000000 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT64); /* 41300000000 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXMAP); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "a" */ + M_BufferSeekForward(&buf, 1); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "apple" */ + M_BufferSeekForward(&buf, 5); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "b" */ + M_BufferSeekForward(&buf, 1); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXARRAY); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "banana" */ + M_BufferSeekForward(&buf, 6); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "blackberry" */ + M_BufferSeekForward(&buf, 10); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "c" */ + M_BufferSeekForward(&buf, 1); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "coconut" */ + M_BufferSeekForward(&buf, 7); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_MAP32); + + M_BufferClear(&buf); +#ifndef CMP_NO_FLOAT + assert_true(cmp_write_float(&cmp, 1.1f)); + assert_true(cmp_write_double(&cmp, 1.1)); +#endif + assert_true(cmp_write_fixext1(&cmp, 1, "C")); + assert_true(cmp_write_fixext2(&cmp, 2, "CC")); + assert_true(cmp_write_fixext4(&cmp, 3, "CCCC")); + assert_true(cmp_write_fixext8(&cmp, 4, "CCCCCCCC")); + assert_true(cmp_write_fixext16(&cmp, 5, "CCCCCCCCCCCCCCCC")); + assert_true(cmp_write_ext8(&cmp, 6, 2, "CC")); + assert_true(cmp_write_ext16(&cmp, 7, 2, "CC")); + assert_true(cmp_write_ext32(&cmp, 8, 2, "CC")); + assert_true(cmp_write_nil(&cmp)); + assert_true(cmp_write_array32(&cmp, 4)); + +#ifndef CMP_NO_FLOAT + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FLOAT); + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_DOUBLE); +#endif + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT1); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT2); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT4); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT8); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT16); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT16); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_EXT8); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_EXT16); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_EXT32); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_NIL); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_ARRAY32); + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_deprecated_limited_skipping(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + cmp_skipper skip; + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + skip = cmp.skip; + + M_BufferEnsureCapacity(&buf, (66000 * 2) + 32); + + assert_true(cmp_write_true(&cmp)); + assert_true(cmp_write_nil(&cmp)); + assert_true(cmp_write_integer(&cmp, -8)); + assert_true(cmp_write_array(&cmp, 10)); + for (uint32_t i = 0; i < 10; i++) { + assert_true(cmp_write_array(&cmp, 0)); + } + assert_true(cmp_write_array(&cmp, 10)); + assert_true(cmp_write_uinteger(&cmp, 8)); + assert_true(cmp_write_integer(&cmp, -120)); + assert_true(cmp_write_uinteger(&cmp, 200)); + assert_true(cmp_write_integer(&cmp, -32000)); + assert_true(cmp_write_uinteger(&cmp, 64000)); + assert_true(cmp_write_integer(&cmp, -33000)); + assert_true(cmp_write_uinteger(&cmp, 66000)); + assert_true(cmp_write_integer(&cmp, -2150000000)); + assert_true(cmp_write_uinteger(&cmp, 4300000000)); + assert_true(cmp_write_map(&cmp, 3)); + assert_true(cmp_write_str(&cmp, "a", 1)); + assert_true(cmp_write_str(&cmp, "apple", 5)); + assert_true(cmp_write_str(&cmp, "b", 1)); + assert_true(cmp_write_array(&cmp, 2)); + assert_true(cmp_write_str(&cmp, "banana", 6)); + assert_true(cmp_write_str(&cmp, "blackberry", 10)); + assert_true(cmp_write_str(&cmp, "c", 1)); + assert_true(cmp_write_str(&cmp, "coconut", 7)); + assert_true(cmp_write_map(&cmp, 66000)); + + for (uint32_t i = 0; i < 66000; i++) { + assert_true(cmp_write_integer(&cmp, 1)); + assert_true(cmp_write_integer(&cmp, 1)); + } + + assert_true(cmp_write_nil(&cmp)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object(&cmp, &obj)); + + cmp.skip = NULL; + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + cmp.skip = skip; + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_false(cmp_skip_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_limit(&cmp, &obj, 11)); + assert_false(cmp_skip_object_limit(&cmp, &obj, 1)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_limit(&cmp, &obj, 11)); + assert_false(cmp_skip_object_limit(&cmp, &obj, 2)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_limit(&cmp, &obj, 11)); + assert_true(cmp_skip_object_limit(&cmp, &obj, 3)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_BOOLEAN); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_NIL); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_NEGATIVE_FIXNUM); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXARRAY); + for (uint32_t i = 0; i < 10; i++) { + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXARRAY); + } + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXARRAY); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_POSITIVE_FIXNUM); /* 8 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_SINT8); /* -120 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT8); /* 200 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_SINT16); /* -32000 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT16); /* 64000 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_SINT32); /* -33000 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT32); /* 66000 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_SINT64); /* -2150000000 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_UINT64); /* 41300000000 */ + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXMAP); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "a" */ + M_BufferSeekForward(&buf, 1); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "apple" */ + M_BufferSeekForward(&buf, 5); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "b" */ + M_BufferSeekForward(&buf, 1); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXARRAY); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "banana" */ + M_BufferSeekForward(&buf, 6); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "blackberry" */ + M_BufferSeekForward(&buf, 10); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "c" */ + M_BufferSeekForward(&buf, 1); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXSTR); /* "coconut" */ + M_BufferSeekForward(&buf, 7); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_MAP32); + + M_BufferClear(&buf); +#ifndef CMP_NO_FLOAT + assert_true(cmp_write_float(&cmp, 1.1f)); + assert_true(cmp_write_double(&cmp, 1.1)); +#endif + assert_true(cmp_write_fixext1(&cmp, 1, "C")); + assert_true(cmp_write_fixext2(&cmp, 2, "CC")); + assert_true(cmp_write_fixext4(&cmp, 3, "CCCC")); + assert_true(cmp_write_fixext8(&cmp, 4, "CCCCCCCC")); + assert_true(cmp_write_fixext16(&cmp, 5, "CCCCCCCCCCCCCCCC")); + assert_true(cmp_write_ext8(&cmp, 6, 2, "CC")); + assert_true(cmp_write_ext16(&cmp, 7, 2, "CC")); + assert_true(cmp_write_ext32(&cmp, 8, 2, "CC")); + assert_true(cmp_write_nil(&cmp)); + assert_true(cmp_write_array32(&cmp, 4)); + +#ifndef CMP_NO_FLOAT + M_BufferSeek(&buf, 0); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FLOAT); + + M_BufferSeek(&buf, 0); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_DOUBLE); +#endif + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT1); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT2); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT4); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT8); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT16); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_FIXEXT16); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_EXT8); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_EXT16); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_EXT32); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_NIL); + + M_BufferSeek(&buf, 0); +#ifndef CMP_NO_FLOAT + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); +#endif + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_skip_object_no_limit(&cmp)); + assert_true(cmp_read_object(&cmp, &obj)); + assert_int_equal(obj.type, CMP_TYPE_ARRAY32); + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_errors(void **state) { + buf_t buf; + cmp_ctx_t cmp; + cmp_object_t obj; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + int8_t s8; + int16_t s16; + int32_t s32; + int64_t s64; +#ifndef CMP_NO_FLOAT + float f; + double d; +#endif + uint32_t size; + int8_t type; + + char *bin8 = malloc(200); + char *bin16 = malloc(300); + char *bin32 = malloc(70000); + char *str8 = malloc(201); + char *str16 = malloc(301); + char *str32 = malloc(70001); + char *ext8 = malloc(0x7F); + char *ext16 = malloc(0x7FFF); + char *ext32 = malloc(0x10000); + + (void)state; + + setup_cmp_and_buf(&cmp, &buf); + + *(str8 + 200) = '\0'; + *(str16 + 300) = '\0'; + *(str32 + 70000) = '\0'; + + assert_true(cmp_write_nil(&cmp)); + assert_true(cmp_write_true(&cmp)); + assert_true(cmp_write_false(&cmp)); + assert_true(cmp_write_uinteger(&cmp, 1)); + assert_true(cmp_write_uinteger(&cmp, 200)); + assert_true(cmp_write_uinteger(&cmp, 300)); + assert_true(cmp_write_uinteger(&cmp, 70000)); + assert_true(cmp_write_uinteger(&cmp, 0x100000002)); + assert_true(cmp_write_integer(&cmp, -1)); + assert_true(cmp_write_integer(&cmp, -100)); + assert_true(cmp_write_integer(&cmp, -200)); + assert_true(cmp_write_integer(&cmp, -33000)); + assert_true(cmp_write_integer(&cmp, 0x80000002)); +#ifndef CMP_NO_FLOAT + assert_true(cmp_write_float(&cmp, 1.1f)); + assert_true(cmp_write_double(&cmp, 1.1)); +#endif + assert_true(cmp_write_map(&cmp, 1)); + assert_true(cmp_write_str(&cmp, "a", 1)); + assert_true(cmp_write_str(&cmp, "apple", 5)); + assert_true(cmp_write_map(&cmp, 0x100)); + for (size_t i = 0; i < 0x100; i++) { + assert_true(cmp_write_integer(&cmp, 1)); + assert_true(cmp_write_integer(&cmp, 1)); + } + assert_true(cmp_write_map(&cmp, 0x10000)); + for (size_t i = 0; i < 0x10000; i++) { + assert_true(cmp_write_integer(&cmp, 1)); + assert_true(cmp_write_integer(&cmp, 1)); + } + assert_true(cmp_write_array(&cmp, 2)); + assert_true(cmp_write_str(&cmp, "banana", 6)); + assert_true(cmp_write_str(&cmp, "blackberry", 10)); + assert_true(cmp_write_array(&cmp, 0x100)); + for (size_t i = 0; i < 0x100; i++) { + assert_true(cmp_write_integer(&cmp, 1)); + } + assert_true(cmp_write_array(&cmp, 0x10000)); + for (size_t i = 0; i < 0x10000; i++) { + assert_true(cmp_write_integer(&cmp, 1)); + } + assert_true(cmp_write_bin(&cmp, bin8, 200)); + assert_true(cmp_write_bin(&cmp, bin16, 300)); + assert_true(cmp_write_bin(&cmp, bin32, 70000)); + assert_true(cmp_write_str(&cmp, str8, 200)); + assert_true(cmp_write_str(&cmp, str16, 300)); + assert_true(cmp_write_str(&cmp, str32, 70000)); + assert_true(cmp_write_ext(&cmp, 2, 1, "C")); + assert_true(cmp_write_ext(&cmp, 3, 2, "CC")); + assert_true(cmp_write_ext(&cmp, 4, 4, "CCCC")); + assert_true(cmp_write_ext(&cmp, 5, 8, "CCCCCCCC")); + assert_true(cmp_write_ext(&cmp, 6, 16, "CCCCCCCCCCCCCCCC")); + assert_true(cmp_write_ext(&cmp, 7, 0x7F, ext8)); + assert_true(cmp_write_ext(&cmp, 8, 0x7FFF, ext16)); + assert_true(cmp_write_ext(&cmp, 9, 0x10000, ext32)); + + M_BufferClear(&buf); + + writer_successes = 0; + assert_false(cmp_write_nil(&cmp)); + assert_false(cmp_write_true(&cmp)); + assert_false(cmp_write_false(&cmp)); + assert_false(cmp_write_uinteger(&cmp, 1)); + assert_false(cmp_write_uinteger(&cmp, 200)); + assert_false(cmp_write_uinteger(&cmp, 300)); + assert_false(cmp_write_uinteger(&cmp, 70000)); + assert_false(cmp_write_uinteger(&cmp, 0x100000002)); + assert_false(cmp_write_integer(&cmp, -1)); + assert_false(cmp_write_integer(&cmp, -100)); + assert_false(cmp_write_integer(&cmp, -200)); + assert_false(cmp_write_integer(&cmp, -33000)); + assert_false(cmp_write_integer(&cmp, 0x80000002)); +#ifndef CMP_NO_FLOAT + assert_false(cmp_write_float(&cmp, 1.1f)); + assert_false(cmp_write_double(&cmp, 1.1)); +#endif + assert_false(cmp_write_map(&cmp, 1)); + assert_false(cmp_write_str(&cmp, "a", 1)); + assert_false(cmp_write_str(&cmp, "apple", 5)); + assert_false(cmp_write_map(&cmp, 0x100)); + for (size_t i = 0; i < 0x100; i++) { + assert_false(cmp_write_integer(&cmp, 1)); + assert_false(cmp_write_integer(&cmp, 1)); + } + assert_false(cmp_write_map(&cmp, 0x10000)); + for (size_t i = 0; i < 0x10000; i++) { + assert_false(cmp_write_integer(&cmp, 1)); + assert_false(cmp_write_integer(&cmp, 1)); + } + assert_false(cmp_write_array(&cmp, 2)); + assert_false(cmp_write_str(&cmp, "banana", 6)); + assert_false(cmp_write_str(&cmp, "blackberry", 10)); + assert_false(cmp_write_array(&cmp, 0x100)); + for (size_t i = 0; i < 0x100; i++) { + assert_false(cmp_write_integer(&cmp, 1)); + } + assert_false(cmp_write_array(&cmp, 0x10000)); + for (size_t i = 0; i < 0x10000; i++) { + assert_false(cmp_write_integer(&cmp, 1)); + } + assert_false(cmp_write_bin(&cmp, bin8, 200)); + assert_false(cmp_write_bin(&cmp, bin16, 300)); + assert_false(cmp_write_bin(&cmp, bin32, 70000)); + assert_false(cmp_write_str(&cmp, str8, 200)); + assert_false(cmp_write_str(&cmp, str16, 300)); + assert_false(cmp_write_str(&cmp, str32, 70000)); + assert_false(cmp_write_ext(&cmp, 2, 1, "C")); + assert_false(cmp_write_ext(&cmp, 3, 2, "CC")); + assert_false(cmp_write_ext(&cmp, 4, 4, "CCCC")); + assert_false(cmp_write_ext(&cmp, 5, 8, "CCCCCCCC")); + assert_false(cmp_write_ext(&cmp, 6, 16, "CCCCCCCCCCCCCCCC")); + assert_false(cmp_write_ext(&cmp, 7, 0x7F, ext8)); + assert_false(cmp_write_ext(&cmp, 8, 0x7FFF, ext16)); + assert_false(cmp_write_ext(&cmp, 9, 0x10000, ext32)); + + M_BufferClear(&buf); + + writer_successes = 1; + assert_false(cmp_write_uinteger(&cmp, 200)); + writer_successes = 1; + assert_false(cmp_write_uinteger(&cmp, 300)); + writer_successes = 1; + assert_false(cmp_write_uinteger(&cmp, 70000)); + writer_successes = 1; + assert_false(cmp_write_uinteger(&cmp, 0x100000002)); + writer_successes = 1; + assert_false(cmp_write_integer(&cmp, -100)); + writer_successes = 1; + assert_false(cmp_write_integer(&cmp, -200)); + writer_successes = 1; + assert_false(cmp_write_integer(&cmp, -33000)); + writer_successes = 1; + assert_false(cmp_write_integer(&cmp, 0xFFFFFFFF2)); +#ifndef CMP_NO_FLOAT + writer_successes = 1; + assert_false(cmp_write_float(&cmp, 1.1f)); + writer_successes = 1; + assert_false(cmp_write_double(&cmp, 1.1)); +#endif + writer_successes = 1; + assert_false(cmp_write_str(&cmp, "a", 1)); + writer_successes = 1; + assert_false(cmp_write_str(&cmp, "apple", 5)); + writer_successes = 1; + assert_false(cmp_write_map(&cmp, 0x100)); + writer_successes = 1; + assert_false(cmp_write_map(&cmp, 0x10000)); + writer_successes = 1; + assert_false(cmp_write_str(&cmp, "banana", 6)); + writer_successes = 1; + assert_false(cmp_write_str(&cmp, "blackberry", 10)); + writer_successes = 1; + assert_false(cmp_write_array(&cmp, 0x100)); + writer_successes = 1; + assert_false(cmp_write_array(&cmp, 0x10000)); + writer_successes = 1; + assert_false(cmp_write_bin(&cmp, bin8, 200)); + writer_successes = 1; + assert_false(cmp_write_bin(&cmp, bin16, 300)); + writer_successes = 1; + assert_false(cmp_write_bin(&cmp, bin32, 70000)); + writer_successes = 1; + assert_false(cmp_write_str(&cmp, str8, 200)); + writer_successes = 1; + assert_false(cmp_write_str(&cmp, str16, 300)); + writer_successes = 1; + assert_false(cmp_write_str(&cmp, str32, 70000)); + writer_successes = 1; + assert_false(cmp_write_ext(&cmp, 2, 1, "C")); + writer_successes = 1; + assert_false(cmp_write_ext(&cmp, 3, 2, "CC")); + writer_successes = 1; + assert_false(cmp_write_ext(&cmp, 4, 4, "CCCC")); + writer_successes = 1; + assert_false(cmp_write_ext(&cmp, 5, 8, "CCCCCCCC")); + writer_successes = 1; + assert_false(cmp_write_ext(&cmp, 6, 16, "CCCCCCCCCCCCCCCC")); + writer_successes = 1; + assert_false(cmp_write_ext(&cmp, 7, 0x7F, ext8)); + writer_successes = 1; + assert_false(cmp_write_ext(&cmp, 8, 0x7FFF, ext16)); + writer_successes = 1; + assert_false(cmp_write_ext(&cmp, 9, 0x10000, ext32)); + + M_BufferClear(&buf); + + writer_successes = 2; + assert_false(cmp_write_bin(&cmp, bin8, 200)); + writer_successes = 2; + assert_false(cmp_write_bin(&cmp, bin16, 300)); + writer_successes = 2; + assert_false(cmp_write_bin(&cmp, bin32, 70000)); + writer_successes = 2; + assert_false(cmp_write_str(&cmp, str8, 200)); + writer_successes = 2; + assert_false(cmp_write_str(&cmp, str16, 300)); + writer_successes = 2; + assert_false(cmp_write_str(&cmp, str32, 70000)); + writer_successes = 2; + assert_false(cmp_write_ext(&cmp, 2, 1, "C")); + writer_successes = 2; + assert_false(cmp_write_ext(&cmp, 3, 2, "CC")); + writer_successes = 2; + assert_false(cmp_write_ext(&cmp, 4, 4, "CCCC")); + writer_successes = 2; + assert_false(cmp_write_ext(&cmp, 5, 8, "CCCCCCCC")); + writer_successes = 2; + assert_false(cmp_write_ext(&cmp, 6, 16, "CCCCCCCCCCCCCCCC")); + writer_successes = 2; + assert_false(cmp_write_ext(&cmp, 7, 0x7F, ext8)); + writer_successes = 2; + assert_false(cmp_write_ext(&cmp, 8, 0x7FFF, ext16)); + writer_successes = 2; + assert_false(cmp_write_ext(&cmp, 9, 0x10000, ext32)); + + writer_successes = -1; + reader_successes = 0; + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_nil(&cmp)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_true(&cmp)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_false(&cmp)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_uinteger(&cmp, 1)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_uinteger(&cmp, 200)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_uinteger(&cmp, 300)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_uinteger(&cmp, 70000)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_uinteger(&cmp, 0x100000002)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_integer(&cmp, -1)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_integer(&cmp, -100)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_integer(&cmp, -200)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_integer(&cmp, -33000)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_integer(&cmp, 0x80000002)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + +#ifndef CMP_NO_FLOAT + M_BufferSeek(&buf, 0); + assert_true(cmp_write_float(&cmp, 1.1f)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_double(&cmp, 1.1)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); +#endif + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_map(&cmp, 1)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_str(&cmp, "a", 1)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_str(&cmp, "apple", 5)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_map(&cmp, 0x100)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_map(&cmp, 0x10000)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_array(&cmp, 2)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_str(&cmp, "banana", 6)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_str(&cmp, "blackberry", 10)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_array(&cmp, 0x100)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_bin(&cmp, bin8, 200)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_bin(&cmp, bin16, 300)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_bin(&cmp, bin32, 70000)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_str(&cmp, str8, 200)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_str(&cmp, str16, 300)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_str(&cmp, str32, 70000)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 2, 1, "C")); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 3, 2, "CC")); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 4, 4, "CCCC")); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 5, 8, "CCCCCCCC")); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 6, 16, "CCCCCCCCCCCCCCCC")); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 7, 0x7F, ext8)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 8, 0x7FFF, ext16)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 9, 0x10000, ext32)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_uinteger(&cmp, 200)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_uinteger(&cmp, 300)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_uinteger(&cmp, 70000)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_uinteger(&cmp, 0x100000002)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_integer(&cmp, -100)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_integer(&cmp, -200)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_integer(&cmp, -33000)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_integer(&cmp, 0x80000002)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + +#ifndef CMP_NO_FLOAT + M_BufferSeek(&buf, 0); + assert_true(cmp_write_float(&cmp, 1.1f)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_double(&cmp, 1.1)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); +#endif + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_map(&cmp, 0x100)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_map(&cmp, 0x10000)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_array(&cmp, 0x100)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_bin(&cmp, bin8, 200)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_bin(&cmp, bin16, 300)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_bin(&cmp, bin32, 70000)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_str(&cmp, str8, 200)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_str(&cmp, str16, 300)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_str(&cmp, str32, 70000)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 2, 1, "C")); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 3, 2, "CC")); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 4, 4, "CCCC")); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 5, 8, "CCCCCCCC")); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 6, 16, "CCCCCCCCCCCCCCCC")); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 7, 0x7F, ext8)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 8, 0x7FFF, ext16)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 9, 0x10000, ext32)); + M_BufferSeek(&buf, 0); + reader_successes = 1; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 7, 0x7F, ext8)); + M_BufferSeek(&buf, 0); + reader_successes = 2; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 8, 0x7FFF, ext16)); + M_BufferSeek(&buf, 0); + reader_successes = 2; + assert_false(cmp_read_object(&cmp, &obj)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_ext(&cmp, 9, 0x10000, ext32)); + M_BufferSeek(&buf, 0); + reader_successes = 2; + assert_false(cmp_read_object(&cmp, &obj)); + + writer_successes = -1; + reader_successes = 0; + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_sfix(&cmp, 1)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_sfix(&cmp, &s8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_sfix(&cmp, -1)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_sfix(&cmp, &s8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_pfix(&cmp, 1)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_pfix(&cmp, &u8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_u8(&cmp, 200)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_u8(&cmp, &u8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_u16(&cmp, 300)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_u16(&cmp, &u16)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_u32(&cmp, 70000)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_u32(&cmp, &u32)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_u64(&cmp, 0x100000002)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_u64(&cmp, &u64)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_nfix(&cmp, -1)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_nfix(&cmp, &s8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_s8(&cmp, -100)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_s8(&cmp, &s8)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_s16(&cmp, -200)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_s16(&cmp, &s16)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_s32(&cmp, -33000)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_s32(&cmp, &s32)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_s64(&cmp, 0x80000002)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_s64(&cmp, &s64)); + +#ifndef CMP_NO_FLOAT + M_BufferSeek(&buf, 0); + assert_true(cmp_write_float(&cmp, 1.1f)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_float(&cmp, &f)); + + M_BufferSeek(&buf, 0); + assert_true(cmp_write_double(&cmp, 1.1)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_double(&cmp, &d)); +#endif + + writer_successes = 1; + + M_BufferSeek(&buf, 0); + assert_false(cmp_write_u8(&cmp, 200)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_write_u16(&cmp, 300)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_write_u32(&cmp, 70000)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_write_u64(&cmp, 0x100000002)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_write_nfix(&cmp, -1)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_write_s8(&cmp, -100)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_write_s16(&cmp, -200)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_write_s32(&cmp, -33000)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_write_s64(&cmp, 0x80000002)); + +#ifndef CMP_NO_FLOAT + M_BufferSeek(&buf, 0); + assert_false(cmp_write_float(&cmp, 1.1f)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_write_double(&cmp, 1.1)); +#endif + + reader_successes = -1; + + M_BufferClear(&buf); + writer_successes = 1; + assert_false(cmp_write_u8(&cmp, 200)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_u8(&cmp, &u8)); + + M_BufferClear(&buf); + writer_successes = 1; + assert_false(cmp_write_u16(&cmp, 300)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_u16(&cmp, &u16)); + + M_BufferClear(&buf); + writer_successes = 1; + assert_false(cmp_write_u32(&cmp, 70000)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_u32(&cmp, &u32)); + + M_BufferClear(&buf); + writer_successes = 1; + assert_false(cmp_write_u64(&cmp, 0x100000002)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_u64(&cmp, &u64)); + + M_BufferClear(&buf); + writer_successes = 1; + assert_false(cmp_write_s8(&cmp, -100)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_s8(&cmp, &s8)); + + M_BufferClear(&buf); + writer_successes = 1; + assert_false(cmp_write_s16(&cmp, -200)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_s16(&cmp, &s16)); + + M_BufferClear(&buf); + writer_successes = 1; + assert_false(cmp_write_s32(&cmp, -33000)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_s32(&cmp, &s32)); + + M_BufferClear(&buf); + writer_successes = 1; + assert_false(cmp_write_s64(&cmp, 0x80000002)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_s64(&cmp, &s64)); + +#ifndef CMP_NO_FLOAT + M_BufferClear(&buf); + writer_successes = 1; + assert_false(cmp_write_float(&cmp, 1.1f)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_float(&cmp, &f)); + + M_BufferClear(&buf); + writer_successes = 1; + assert_false(cmp_write_double(&cmp, 1.1)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_double(&cmp, &d)); +#endif + + writer_successes = -1; + reader_successes = -1; + + M_BufferClear(&buf); + assert_true(cmp_write_u16(&cmp, 300)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_sfix(&cmp, &s8)); + + M_BufferClear(&buf); + assert_true(cmp_write_pfix(&cmp, 1)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_pfix(&cmp, &u8)); + + M_BufferSeek(&buf, 0); + assert_false(cmp_read_u8(&cmp, &u8)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_u16(&cmp, &u16)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_u32(&cmp, &u32)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_u64(&cmp, &u64)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_nfix(&cmp, &s8)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_s8(&cmp, &s8)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_s16(&cmp, &s16)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_s32(&cmp, &s32)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_s64(&cmp, &s64)); +#ifndef CMP_NO_FLOAT + M_BufferSeek(&buf, 0); + assert_false(cmp_read_float(&cmp, &f)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_double(&cmp, &d)); +#endif + M_BufferSeek(&buf, 0); + assert_false(cmp_read_str_size(&cmp, &size)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_str(&cmp, NULL, &size)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_bin_size(&cmp, &size)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_bin(&cmp, NULL, &size)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_array(&cmp, &size)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_map(&cmp, &size)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_ext_marker(&cmp, &type, &size)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_ext(&cmp, &type, &size, NULL)); + + M_BufferClear(&buf); + assert_true(cmp_write_s8(&cmp, -1)); + assert_true(cmp_write_s8(&cmp, -100)); + assert_true(cmp_write_s8(&cmp, 100)); + assert_true(cmp_write_s16(&cmp, -200)); + assert_true(cmp_write_s32(&cmp, -33000)); + assert_true(cmp_write_s64(&cmp, 0xFFFFFFFFF)); + assert_true(cmp_write_u8(&cmp, 1)); + assert_true(cmp_write_u8(&cmp, 200)); + assert_true(cmp_write_u16(&cmp, 300)); + assert_true(cmp_write_u32(&cmp, 70000)); + assert_true(cmp_write_u64(&cmp, 0xFFFFFFFFF)); +#ifndef CMP_NO_FLOAT + assert_true(cmp_write_decimal(&cmp, 1.1f)); + assert_true(cmp_write_decimal(&cmp, 1.1)); +#endif + + M_BufferSeek(&buf, 0); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_char(&cmp, &s8)); + assert_true(cmp_read_short(&cmp, &s16)); + assert_true(cmp_read_int(&cmp, &s32)); + assert_true(cmp_read_long(&cmp, &s64)); + assert_true(cmp_read_uchar(&cmp, &u8)); + assert_true(cmp_read_uchar(&cmp, &u8)); + assert_true(cmp_read_ushort(&cmp, &u16)); + assert_true(cmp_read_uint(&cmp, &u32)); + assert_true(cmp_read_ulong(&cmp, &u64)); + +#ifndef CMP_NO_FLOAT + assert_true(cmp_read_decimal(&cmp, &d)); + assert_true(cmp_read_decimal(&cmp, &d)); +#endif + + M_BufferClear(&buf); + assert_true(cmp_write_nfix(&cmp, -1)); + M_BufferSeek(&buf, 0); + assert_true(cmp_read_nfix(&cmp, &s8)); + M_BufferSeek(&buf, 0); + assert_false(cmp_read_pfix(&cmp, &u8)); + + free(bin8); + free(bin16); + free(bin32); + free(str8); + free(str16); + free(str32); + free(ext8); + free(ext16); + free(ext32); + + teardown_cmp_and_buf(&cmp, &buf); +} + +void test_version(void **state) { + uint32_t version = cmp_version(); + uint32_t mp_version = cmp_mp_version(); + + (void)state; + (void)version; + (void)mp_version; +} + +/* vi: set et ts=2 sw=2: */ diff --git a/local_pod_repo/cmp/cmp/test/tests.h b/local_pod_repo/cmp/cmp/test/tests.h new file mode 100644 index 0000000..57f458c --- /dev/null +++ b/local_pod_repo/cmp/cmp/test/tests.h @@ -0,0 +1,45 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +void test_msgpack(void **state); +void test_fixedint(void **state); +void test_numbers(void **state); +void test_conversions(void **state); +void test_nil(void **state); +void test_boolean(void **state); +void test_bin(void **state); +void test_string(void **state); +void test_array(void **state); +void test_map(void **state); +void test_ext(void **state); +void test_obj(void **state); +#ifndef CMP_NO_FLOAT +void test_float_flip(void **state); +#endif +void test_skipping(void **state); +void test_deprecated_limited_skipping(void **state); +void test_errors(void **state); +void test_version(void **state); + +/* vi: set et ts=2 sw=2: */ diff --git a/local_pod_repo/cmp/cmp/test/utils.c b/local_pod_repo/cmp/cmp/test/utils.c new file mode 100644 index 0000000..08e9b01 --- /dev/null +++ b/local_pod_repo/cmp/cmp/test/utils.c @@ -0,0 +1,58 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +void error_and_exit(const char *msg) { + fprintf(stderr, "%s\n", msg); + exit(EXIT_FAILURE); +} + +void errorf_and_exit(const char *msg, ...) { + va_list args; + + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); + + exit(EXIT_FAILURE); +} + +char* _strdup(const char *s) { + char *out = calloc(strlen(s) + 1, sizeof(char)); + + strcpy(out, s); + + return out; +} + +/* vi: set et ts=2 sw=2: */ + diff --git a/local_pod_repo/cmp/cmp/test/utils.h b/local_pod_repo/cmp/cmp/test/utils.h new file mode 100644 index 0000000..c0582b5 --- /dev/null +++ b/local_pod_repo/cmp/cmp/test/utils.h @@ -0,0 +1,52 @@ +/* +The MIT License (MIT) + +Copyright (c) 2020 Charles Gunyon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#ifndef UTILS_H__ +#define UTILS_H__ + +void error_and_exit(const char *msg); + +#ifdef __GNUC__ +void errorf_and_exit(const char *msg, ...) + __attribute__ ((format (printf, 1, 2))); +#else +void errorf_and_exit(const char *msg, ...); +#endif + +char* _strdup(const char *s); + +#ifndef strdup +#define strdup _strdup +#endif + +#ifndef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) +#endif + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif + +#endif + diff --git a/local_pod_repo/msgpack-c/.gitignore b/local_pod_repo/msgpack-c/.gitignore new file mode 100644 index 0000000..d2dcf7f --- /dev/null +++ b/local_pod_repo/msgpack-c/.gitignore @@ -0,0 +1,32 @@ +# OS X +.DS_Store + +# Xcode +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +profile +*.moved-aside +DerivedData +*.hmap +*.ipa + +# Bundler +.bundle + +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control +# +# Note: if you ignore the Pods directory, make sure to uncomment +# `pod install` in .travis.yml +# +# Pods/ diff --git a/local_pod_repo/msgpack-c/LICENSE b/local_pod_repo/msgpack-c/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/local_pod_repo/msgpack-c/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/local_pod_repo/msgpack-c/msgpack-c.podspec b/local_pod_repo/msgpack-c/msgpack-c.podspec new file mode 100644 index 0000000..0d37921 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c.podspec @@ -0,0 +1,35 @@ +# +# Be sure to run `pod lib lint toxcore.podspec' to ensure this is a +# valid spec and remove all comments before submitting the spec. +# +# Any lines starting with a # are optional, but encouraged +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = "msgpack-c" + s.version = "4.0.0" + s.summary = "Cocoapods wrapper for msgpack-c" + s.homepage = "https://github.com/Zoxcore/msgpack-c" + s.license = 'GPLv3' + s.author = "Zoff" + s.source = { + :git => "https://github.com/Zoxcore/msgpack-c.git", + :tag => s.version.to_s, + :submodules => true + } + + s.pod_target_xcconfig = { 'ENABLE_BITCODE' => 'NO', 'OTHER_LDFLAGS' => '-read_only_relocs suppress' } + + s.ios.deployment_target = '8.0' + s.requires_arc = true + + # Preserve the layout of headers in the msgpack-c directory + #s.header_mappings_dir = 'msgpack-c/include' + + s.source_files = 'msgpack-c/include/*h', 'msgpack-c/include/msgpack/*h', 'msgpack-c/src/*.c' + #s.public_header_files = 'msgpack-c/include/*.h', 'msgpack-c/include/msgpack/*.h' + #s.xcconfig = { 'FRAMEWORK_SEARCH_PATHS' => '"${PODS_ROOT}"'} + +end diff --git a/local_pod_repo/msgpack-c/msgpack-c/.gitignore b/local_pod_repo/msgpack-c/msgpack-c/.gitignore new file mode 100644 index 0000000..40f5e0a --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/.gitignore @@ -0,0 +1,51 @@ +# Files generated by the bootstrap script. +/INSTALL +/AUTHORS +/ChangeLog +/NEWS +/README +/ac/ +/aclocal.m4 +/autom4te.cache/ +/config.h.in +/configure +/msgpack_vc2008.sln +/msgpack_vc2008.vcproj +Makefile.in + +# Files generated by the configure script. + +/config.h +/config.log +/config.status +/libtool +/msgpack.pc +/src/msgpack/version.h +/src/msgpack/version.hpp +/stamp-h1 +Makefile +.deps +.libs + +# Files generated by make. +*.o +*.so +*.lo +*.la + +# Files generated by make check. +# TODO: Replace these with something like /test/*_test +/test/buffer +/test/cases +/test/convert +/test/fixint +/test/fixint_c +/test/msgpack_test +/test/msgpackc_test +/test/object +/test/pack_unpack +/test/pack_unpack_c +/test/streaming +/test/streaming_c +/test/version +/test/zone diff --git a/local_pod_repo/msgpack-c/msgpack-c/CHANGELOG.md b/local_pod_repo/msgpack-c/msgpack-c/CHANGELOG.md new file mode 100644 index 0000000..cd1c499 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/CHANGELOG.md @@ -0,0 +1,424 @@ +# 2021-08-01 version 4.0.0 + * Fix and improve alignment logic (#962) + * Fix iovec name conflict (#953) + * Fix empty string print (#942) + * Fix buffer ptr size (#899) + * Fix UB. Check null pointer before using memcpy() (#890) + * Improve CI environment (#885, #899) + +## << breaking changes >> +* Separate C part of the msgpack-c from C/C++ mixed msgpack-c (#876, #878) + + +# 2020-06-05 version 3.3.0 + * Add json example for C (#870) + * Add both header and body packing functions for C (#870) + * Set default ref_size and chunk_size to vrefbuffer (#865) + * Add examples (#861) + * Improve build system (#839, #842) + * Improve tests (#829) + * Improve documents (#828) + * Remove some warnings (#827, #851, #871) + * Improve CI environment (#824, #831, #833, #834, #846, #860, 874) + +# 2019-12-10 version 3.2.1 + * Fix snprintf return value checking (#821) + * Remove some warnings (#819) + * Fix fbuffer result checking (#812) + * Fix temporary object handling (#807) + * Improve cmake support (#804) + * Fix invalid `int main` parameter (#800) + * Improve supporting platform (#797, #817) + * Fix ZLIB error handling (#795) + * Remove unused variable (#793) + * Improve integer overflow checking (#792) + +# 2019-05-27 version 3.2.0 + + * Fix invalid include (#783) + * Add timespec support (#781) + * Fix unchecked fnprintf on C (#780) + * Improve integer overflow checking on C (#776) + * Fix warnings on `-Wconversion` (#770, #777, #784) + * Fix invalid passed by value on aligned_zone_size_visitor (#764) + * Improve windows support (#757, #779) + * Fix msgpack::object size caluclation error (#754) + * Fix memory error on example code (#753) + * Fix redundant memory allocation on C (#747) + * Fix msgpack::type::tuple base class conversion (#743) + +# 2018-09-09 version 3.1.1 + + * Add force endian set functionality (#736) + * Fix vrefbuffer memory management problem (#733) + * Fix msvc specific problem (#731, #732) + * Update boost from 1.61.0 to 1.68.0 (#730) + * Fix msgpack_timestamp type mismatch bug (#726) + +# 2018-08-10 version 3.1.0 + + * Improve documents (#687, #718) + * Add fuzzer support (#689) + * Fix msgpack::object union member access bug (#694) + * Improve cross platform configuration (#704) + * Fix out of range dereference bug of EXT (#705) + * Add timestamp support. std::chrono::system_clock::time_point is mapped to TIMESTAMP (#706) + * Add minimal timestamp support for C. The type `msgpack_timestamp` and the function `msgpack_object_to_timestamp()` are introduced (#707) + * Improve MSGPACK_DEFINE family name confliction probability (#710) + * Add no static-library build option (BUILD_SHARED_LIBS=ON) (#713, #717, #722) + * Add header only cmake target (#721) + * Add `std::byte` adaptor (#719) + * Remove some warnings (#720) + +# 2018-05-12 version 3.0.1 + + * Add fuzz directory to release tar ball (#686) + * Add include file checking for X-Code (#683) + +# 2018-05-09 version 3.0.0 + +## << breaking changes >> + + * Change offset parameter updating rule. If parse error happens, offset is updated to the error position. (#639, #666) + +## << other updates >> + + * Improve cross platform configuration (#655, #677) + * Improve build system (#647) + * Improve user class adaptor (#645, #673) + * Improve msgpack::object visitation logic (#676) + * Remove some warnings (#641, 659) + * Add `->` and `*` operators to object_handle (#635) + * Improve CI environment (#631, #634, #643, #657, #662, #668) + * Improve documents (#630, #661) + * Refactoring (#670) + * Add OSS-Fuzz support (#672, #674, #675, #678) + +# 2017-08-04 version 2.1.5 + * Improve cross platform configuration (#624) + * Add boost asio examples (including zlib) (#610) + * Remove some warnings (#611) + * Fix unpack visitor to treat float32/64 correctly (#613) + * Improve documents (#616) + * Fix alignment problem on some platform (#617, #518) + * Fix conflict std::tuple, std::pair, and boost::fusion::sequence problem (#619) + +# 2017-08-03 version 2.1.4 (Invalid) + * See https://github.com/msgpack/msgpack-c/issues/623 + +# 2017-06-15 version 2.1.3 + * Improve build system (#603) + * Add C++17 adaptors `std::optional` and `std::string_view`. (#607, #608) + * Improve cross platform configuration (#601) + * Remove some warnings (#599, #602, #605) + +# 2017-06-07 version 2.1.2 + +* Improve documents (#565) + * Fix empty map parse bug (#568) + * Improve build system (#569, #570, #572, #579, #591, #592) + * Remove some warnings (#574, #578, #586, #588) + * Improve cross platform configuration (#577, #582) + * Add cmake package config support (#580) + * Fix streaming unpack bug (#585) + +# 2017-02-04 version 2.1.1 + + * Fix unpacker's buffer management bug (#561) + * Add boost string_view adaptor (#558) + * Remove some warnings (#557, #559) + * Improve coding style (#556) + +# 2017-01-10 version 2.1.0 + +## << breaking changes >> + + * Fix object internal data type is float if msgpack format is float32 (#531) + +## << recommended changes >> + + * Add `FLOAT64` type. Please use it instead of `DOUBLE` (#531) + * Add `FLOAT32` type. Please use it instead of `FLOAT` (#531) + +## << other updates >> + + * Add iterator based parse/unpack function(experimental) (#553) + * Add `[[deprecated]]` attribute for C++14 (#552) + * Fix `msgpack_unpack()` return code (#548) + * Fix integer overflow (#547, #549, #550) + * Add example codes (#542) + * Add MSGPACK_NVP. You can use not only variable name but also any strings (#535) + * Fix and Improve build system (#532, #545) + * Fix `gcc_atomic.hpp` include path (#529, #530) + * Improve CI environment (#526) + * Improve documents (#524) + * Add msgpack_unpacker_next_with_size() function (#515) + * Fix `as()` applying condition (#511) + * Fix fbuffer write (#504) + * Add gcc bug workaround (#499) + * Improve object print (#497, #500, #505, #533) + * Remove some warnings (#495, #506, #508, #513, #528, #538, #545) + +# 2016-06-25 version 2.0.0 + +## << breaking changes >> + + * Removed autotools support. Use cmake instead (#476, #479) + * Removed pointer version of msgpack::unpack APIs. Use reference version instead (#453) + * Removed MSGPACK_DISABLE_LEGACY_CONVERT. msgpack::object::convert(T*) is removed by default. Use msgpack::object::convert(T&) instead (#451) + * Removed msgpacl::type::nil. Use nil_t or define MSGPACK_USE_LECACY_NIL (#444) + * Removed std::string to msgpack::object conversion (#434) + +## << recommended changes >> + + * Replaced msgpack::unpacked with msgpack::object_handle. msgpack::unpacked is kept as a typedef of msgpack::object_handle. (#448) + +## << other updates >> + + * Add strict size checking adaptor. Relaxed tuple conversion (#489) + * Fix and Improve example codes (#487) + * Add C++/CLI support for nullptr (#481) + * Update the boost libraries that are contained by msgpack-c (#475) + * Fix gcc_atomic.hpp location (#474) + * Add C-Style array support (#466, #488) + * Fix include file dependency (#464) + * Add a visitor version of unpack API (#461) + * Fix JSON string conversion from "nil" to "null" (#458) + * Fix and Improve build system (#455, #471, #473, #486, #491) + * Fix comments (#452) + * Fix unintentional msgpack::zone moving problem (#447) + * Fix operator>> and << for msgpack::object (#443) + * Fix C++03 msgpack::zone::clear() memory access violation bug (#441) + * Fix TARGET_OS_IPHONE checking (#436) + * Fix invalid front() call for empty container (#435) + * Fix compile error on g++6 (C++11 only) (#426, #430) + * Fix zone size expansion logic (#423) + * Fix wrong hader file dependency (#421) + * Fix msvc specific problem (#420) + * Add v2 API support (#415) + +# 2016-01-22 version 1.4.0 + +## << recommended changes >> + + * Define [MSGPACK_DISABLE_LEGACY_NIL](https://github.com/msgpack/msgpack-c/wiki/v1_1_cpp_configure#msgpack_disable_legacy_nil-since-140), then `msgpack::type::nil` is replaced by with `msgpack::type::nil_t` (#408, #411, #412). + Replace `msgpack::type::nil` with `msgpack::type::nil_t` in client codes. + `msgpack::type::nil` will be removed on the version 2.0.0. + * Define [MSGPACK_DISABLE_LEGACY_CONVERT](https://github.com/msgpack/msgpack-c/wiki/v1_1_cpp_configure#msgpack_disable_legacy_convert-since-140), then `msgpack::object::convert(T*)` is removed (#410). + Replace calling `msgpack::bojectconvert(T*)` with `msgpack::bojectconvert(T&)` in client codes as follows: + + ```C++ + int i; + obj.convert(&i); // before + ``` + + ```C++ + int i; + obj.convert(i); // after + ``` + + `msgpack::object::convert(T*)` will be removed on the version 2.0.0. + +Define the macros above as follows when you compile C++ codes that use msgpack-c: + +``` +g++ -Ipath_to_msgpack/include -DMSGPACK_DISABLE_LEGACY_NIL -DMSGPACK_DISABLE_LEGACY_CONVERT your_code.cpp + +``` + +You can compile existing codes without defining macros above but I recommend defining them and updating your codes to fix the issues #408, #411, #412, #399, and #410. It is also a good preparation for the version 2.0.0. + +## << other updates >> + + * Improve documents (#387, #407) + * Remove C++ version library (#394, #402) + * Add Doxyfile and ChangeLog to the distribution package (#397) + * Add signed/unsigned char test to travis-ci (#398) + * Remove some warnings (#400, #401, #409) + * Fix endian checking. (#404) + +# 2015-11-21 version 1.3.0 + + * Change the license from the Apache License Version 2.0 to the + Boost Software License, Version 1.0.(#386) + * Remove some warnings (#365) + * Add std::reference_wrapper support(#373, #384) + * Improve tests (#375, #378, #379, #380) + * Fix msvc specific problem (#376, #383) + * Fix typos (#381) + +# 2015-09-04 version 1.2.0 + +## << breaking changes >> + + * Change std::vector and std::array + mapped to BIN instead of ARRAY (#243) + * Remove redundant copy (#285) + + +## << other updates >> + + * Add array_ref to map to ARRAY (#243) + * Add variant type and adaptor (#349) + * Add object::convert_if_not_nil() (#357) + * Fix invalid offset update (#354) + * Add C++11 support on MSVC2015(#339, #347) + * Fix and Improve build system (#346, #350, #361, #363) + * Import Boost.Preprocessor as a part of msgpack-c (#312) + * Fix OSX with libc++ specific errors (#334, #362) + * Add customized containers support (#330) + * Add std::unique_ptr and std::shared_ptr support (#329) + * Add missing install files (#328) + * Add shared/static library switching option (#316) + * Improve no throw description on C++11 (#313) + * Import Boost.Predef as a part of msgpack-c (#312) + * Add map based serialize support (#306) + * Add Boost.Fusion support (#305) + * Add v4 format RAW support (#304) + * Fix zbuffer with empty string problem (#303) + * Add non default constructible class support (#302, #324, #327, #331, #332, #345) + * Add inline keyword to function (template) (#299) + * Add EXT type supporting classes (#292, #308) + * Fix raw_ref != comparison (#290) + * Add object deep copy (#288) + * Remove some warnings (#284, #322, #323, #335) + * Improve compiler version checking (#283) + * Add return value to object::convert() (#282) + * Improve move semantic support in C++11 (#279, #353) + * Add Boost.StringRef support (#278) + * Improve CI environment (#276, #294, #338) + * Add converting to JSON (#274, #301) + * Fix iOS specific problem (#270) + * Improve doxtgen document generation (#269) + * Add Boost.Optional support (#268) + * Fix msvc specific problem (#267, #295) + * Add base class serialization. (#265, #277) + * Add and improve examples. (#264, #310, #311, #341, #342, #344) + * Fix wiki URL. (#263) + +# 2015-04-03 version 1.1.0 + +## << breaking changes >> + + * Remove msgpack_fwd.hpp + * Improve user types adaptation mechanism (#262) + Since version 1.0.0, users need to obey the correct include order. + However, it is very difficult to maintain the correct order in big + projects. version 1.1.0 removed this order. Users don't need to + care about include order. Migration guide from 1.0.x to 1.1.0 has + been written. See https://github.com/msgpack/msgpack-c/wiki + + +## << other updates >> + + * Fix vector size check (#251) + * Fix inttypes.h inclusion on MSVC (#257) + * Support documents generation by Doxygen (#259) + * Remove C99 style variable declaration (#253) + * Improve documents (https://github.com/msgpack/msgpack-c/wiki) + +# 2015-03-22 version 1.0.1: + + * Fix compilation error on Mac 10.9 (#244) + * Fix typos in documents (#240) + * Update CHANGELOG.md for version 1.0.0 (#242) + * Fix erb templates for the next code generation (#239) + +# 2015-03-10 version 1.0.0: + + * Support msgpack v5 format (str, bin, and ext) https://github.com/msgpack/msgpack/blob/master/spec.md (#142) + * Support std::tuple, std::forward_list, std::array, std::unordered_set, std::unordered_map on C++11. tr1 unordered containers are still supported (#53, #130, #137, #154, #169) + * Update msgpack-c as a header-only library on C++ (#142) + * Move include directory (#142) + * Update the name of float format family on msgpack::object from 'dec' to 'f64' (#194) + * Remove existing elements on associative containers when unpacking (#127) + * Add an API versioning functionality https://github.com/msgpack/msgpack-c/wiki/cpp_versioning (#139) + * Add C++11 enum class support (#205) + * Map std::vector and std::array to BIN (#100) + * Map '\0' teminated char* and char const* to STR (#206) + * Add the new parameter on unpacking functions and classes to limit msgpack's bytestream size (https://github.com/msgpack/msgpack-c/wiki/cpp_unpacker#limit-size-of-elements) (#175) + * Add the copy or reference choosing function on unpack() and unpacker (https://github.com/msgpack/msgpack-c/wiki/cpp_unpacker#memory-management) + * Add the new unpack() overloads for C++11 https://github.com/msgpack/msgpack-c/wiki/cpp_unpacker (#128) + * Add a msgpack::object::with_zone (deep) copying function (#133, #163) + * Remove the compile-time defined limit of msgpack nest level on C++ (#218) + * Add the new unpack() overloads that use an existing zone (#201) + * Add the range-based for loop support on msgpack object array and map (#203) + * Add msgpack revision getter function for 'revision' (#237) + * Support EXT for C (#118, #129) + * Fix unpacking buffer allocation problem when malformed data is given (#160, #185) + * Add dll exporting function on MSVC (#162) + * Fix msgpack::zone::allocate_no_align(). Now it allocates the memory that is not aligned as expected (#171) + * Improve documents (https://github.com/msgpack/msgpack-c/wiki) + * Other bug fixes and refactoring: #62, #91, #95, #97, #107, #109, #113, #117, #119, #121, #122, #123, #126, #131, #136, #138, #140, #143, #145, #146, #150, #151, #152, #156, #157, #158, #161, #165, #170, #172, #179, #180, #181, #182, #183, #192, #195, #199, #200, #207, #211, #212, #219, #222, #224, #230, #231, #232, #233, #234, #235 + +# 2014-07-02 version 0.5.9: + + * Support std::tr1 unordered containers by default (#51, #63, #68, #69) + * Remove some warnings (#56) + * Fix segmentation fault after malloc failures (#58, #59) + * Fix alloc/dealloc mismatch (#52, #61) + * Fix sample codes (#60, #64) + * Support implicit conversion from integer to float/double (#54) + * Improve documents (#45, #75, #82, #83) + * Support CMake (#20, #87) + * Remove Ruby dependencies in bootstrap (#86, #87) + * Add FILE* buffer (#40) + * Other bug fixes and refactoring: #39, #73, #77, #79, #80, #81, #84, #90 + +# 2013-12-23 version 0.5.8: + + * Move to the new github repository msgpack/msgpack-c + * Support the new deserialization specification + * fixes the problem of unpack helpers for array and map with 32bit compilers (#37, #38) + * Other bug fixes and refactoring: #46, #41, #36, #35, #33, #32, #30, #29, #28, #27, #26, #25, #8, #3 + * Update of documents: #23, #18, #17 + +# 2011-08-08 version 0.5.7: + + * fixes compile error problem with llvm-gcc and Mac OS X Lion + +# 2011-04-24 version 0.5.6: + + * #42 fixes double-free problem on msgpack_unpacker_release_zone + +# 2011-02-24 version 0.5.5: + + * eliminates dependency of winsock2.h header + * fixes msgpack_vc.postbuild.bat file + * fixes some implicit cast warnings + +# 2010-08-29 version 0.5.4: + + * includes msgpack_vc2008.vcproj file in source package + * fixes type::fix_int types + +# 2010-08-27 version 0.5.3: + + * adds type::fix_{u,}int{8,16,32,64} types + * adds msgpack_pack_fix_{u,}int{8,16,32,64} functions + * adds packer::pack_fix_{u,}int{8,16,32,64} functions + * fixes include paths + +# 2010-07-14 version 0.5.2: + + * type::raw::str(), operator==, operator!=, operator< and operator> are now const + * generates version.h using AC_OUTPUT macro in ./configure + +# 2010-07-06 version 0.5.1: + + * Add msgpack_vrefbuffer_new and msgpack_vrefbuffer_free + * Add msgpack_sbuffer_new and msgpack_sbuffer_free + * Add msgpack_unpacker_next and msgpack_unpack_next + * msgpack::unpack returns void + * Add MSGPACK_VERSION{,_MAJOR,_MINOR} macros to check header version + * Add msgpack_version{,_major,_minor} functions to check library version + * ./configure supports --disable-cxx option not to build C++ API + +# 2010-04-29 version 0.5.0: + + * msgpack_object_type is changed. MSGPACK_OBJECT_NIL is now 0x00. + * New safe streaming deserializer API. + * Add object::object(const T&) and object::operator=(const T&) + * Add operator==(object, const T&) + * MSGPACK_DEFINE macro defines msgpack_object(object* obj, zone* z) + * C++ programs doesn't need to link "msgpackc" library. diff --git a/local_pod_repo/msgpack-c/msgpack-c/CMakeLists.txt b/local_pod_repo/msgpack-c/msgpack-c/CMakeLists.txt new file mode 100644 index 0000000..85a1e5a --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/CMakeLists.txt @@ -0,0 +1,333 @@ +CMAKE_MINIMUM_REQUIRED (VERSION 2.8.12) + +IF ((CMAKE_VERSION VERSION_GREATER 3.1) OR + (CMAKE_VERSION VERSION_EQUAL 3.1)) + CMAKE_POLICY(SET CMP0054 NEW) +ENDIF () + +PROJECT (msgpack) + +FILE (READ ${CMAKE_CURRENT_SOURCE_DIR}/include/msgpack/version_master.h contents) +STRING (REGEX MATCH "#define MSGPACK_VERSION_MAJOR *([0-9a-zA-Z_]*)" NULL_OUT ${contents}) +SET (VERSION_MAJOR ${CMAKE_MATCH_1}) +STRING (REGEX MATCH "#define MSGPACK_VERSION_MINOR *([0-9a-zA-Z_]*)" NULL_OUT ${contents}) +SET (VERSION_MINOR ${CMAKE_MATCH_1}) +STRING (REGEX MATCH "#define MSGPACK_VERSION_REVISION *([0-9a-zA-Z_]*)" NULL_OUT ${contents}) +SET (VERSION_REVISION ${CMAKE_MATCH_1}) +SET (VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REVISION}) + +LIST (APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") +SET (prefix ${CMAKE_INSTALL_PREFIX}) +SET (exec_prefix "\${prefix}") +SET (libdir "\${exec_prefix}/lib") +SET (includedir "\${prefix}/include") + +OPTION (MSGPACK_32BIT "32bit compile" OFF) + +INCLUDE(TestBigEndian) +TEST_BIG_ENDIAN(BIGENDIAN) +IF (BIGENDIAN) + SET(MSGPACK_ENDIAN_BIG_BYTE 1) + SET(MSGPACK_ENDIAN_LITTLE_BYTE 0) +ELSE () + SET(MSGPACK_ENDIAN_BIG_BYTE 0) + SET(MSGPACK_ENDIAN_LITTLE_BYTE 1) +ENDIF () + +CONFIGURE_FILE ( + cmake/sysdep.h.in + include/msgpack/sysdep.h + @ONLY +) + +CONFIGURE_FILE ( + cmake/pack_template.h.in + include/msgpack/pack_template.h + @ONLY +) + +IF (APPLE) + SET(CMAKE_MACOSX_RPATH ON) + SET(CMAKE_SKIP_BUILD_RPATH FALSE) + SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir) + IF ("${isSystemDir}" STREQUAL "-1") + SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + ENDIF () +ENDIF () + +IF (MSGPACK_32BIT) + IF ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + SET (CMAKE_C_FLAGS "-m32 ${CMAKE_C_FLAGS}") + SET (CMAKE_EXE_LINKER_FLAGS "-m32 ${CMAKE_EXE_LINKER_FLAGS}") + ELSEIF ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + SET (CMAKE_C_FLAGS "-m32 ${CMAKE_C_FLAGS}") + SET (CMAKE_EXE_LINKER_FLAGS "-m32 ${CMAKE_EXE_LINKER_FLAGS}") + ENDIF () +ENDIF () + +OPTION (MSGPACK_BUILD_EXAMPLES "Build msgpack examples." ON) + +IF (MSGPACK_CHAR_SIGN) + SET (CMAKE_C_FLAGS "-f${MSGPACK_CHAR_SIGN}-char ${CMAKE_C_FLAGS}") +ENDIF () + +FIND_PACKAGE (GTest) +FIND_PACKAGE (ZLIB) +FIND_PACKAGE (Threads) +IF (GTEST_FOUND AND ZLIB_FOUND AND THREADS_FOUND) + OPTION (MSGPACK_BUILD_TESTS "Build msgpack tests." ON) + OPTION (MSGPACK_GEN_COVERAGE "Enable running gcov to get a test coverage report." OFF) +ENDIF () + +IF (DEFINED BUILD_SHARED_LIBS) + IF (BUILD_SHARED_LIBS) + IF (DEFINED MSGPACK_ENABLE_SHARED AND NOT MSGPACK_ENABLE_SHARED) + MESSAGE(WARNING "MSGPACK_ENABLE_SHARED is overridden to ON by BUILD_SHARED_LIBS") + ENDIF () + SET (MSGPACK_ENABLE_SHARED ON) + IF (DEFINED MSGPACK_ENABLE_STATIC AND MSGPACK_ENABLE_STATIC) + MESSAGE(WARNING "MSGPACK_ENABLE_STATIC is overridden to OFF by BUILD_SHARED_LIBS") + ENDIF () + SET (MSGPACK_ENABLE_STATIC OFF) + ELSE () + IF (DEFINED MSGPACK_ENABLE_SHARED AND MSGPACK_ENABLE_SHARED) + MESSAGE(WARNING "MSGPACK_ENABLE_SHARED is overridden to OFF by BUILD_SHARED_LIBS") + ENDIF () + SET (MSGPACK_ENABLE_SHARED OFF) + IF (DEFINED MSGPACK_ENABLE_STATIC AND NOT MSGPACK_ENABLE_STATIC) + MESSAGE(WARNING "MSGPACK_ENABLE_STATIC is overridden to ON by BUILD_SHARED_LIBS") + ENDIF () + SET (MSGPACK_ENABLE_STATIC ON) + ENDIF () +ELSE () + IF (NOT DEFINED MSGPACK_ENABLE_SHARED) + SET (MSGPACK_ENABLE_SHARED ON) + ENDIF () + IF (NOT DEFINED MSGPACK_ENABLE_STATIC) + SET (MSGPACK_ENABLE_STATIC ON) + ENDIF () + SET (BUILD_SHARED_LIBS ${MSGPACK_ENABLE_SHARED}) +ENDIF () + +INCLUDE (Files.cmake) + +CONFIGURE_FILE ( + msgpack.pc.in + msgpack.pc + @ONLY +) + +IF (MSGPACK_ENABLE_SHARED OR MSGPACK_ENABLE_STATIC) + ADD_LIBRARY (msgpackc + ${msgpackc_SOURCES} + ${msgpackc_HEADERS} + ) + + SET_TARGET_PROPERTIES (msgpackc PROPERTIES SOVERSION 2 VERSION 2.0.0) + + TARGET_INCLUDE_DIRECTORIES (msgpackc + PUBLIC + $ + $ + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ) +ENDIF () + + +IF (MSGPACK_ENABLE_SHARED AND MSGPACK_ENABLE_STATIC) + ADD_LIBRARY (msgpackc-static STATIC + ${msgpackc_SOURCES} + ${msgpackc_HEADERS} + ) + + TARGET_INCLUDE_DIRECTORIES (msgpackc-static + PUBLIC + $ + $ + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ) + + SET_TARGET_PROPERTIES (msgpackc-static PROPERTIES OUTPUT_NAME "msgpackc") + + IF (MSGPACK_ENABLE_SHARED) + IF (MSVC) + SET_TARGET_PROPERTIES (msgpackc PROPERTIES IMPORT_SUFFIX "_import.lib") + ELSEIF (MINGW) + SET_TARGET_PROPERTIES (msgpackc PROPERTIES IMPORT_SUFFIX ".dll.a") + ENDIF () + ENDIF () +ENDIF () + +IF (MSGPACK_GEN_COVERAGE) + IF (NOT MSGPACK_BUILD_TESTS) + MESSAGE(FATAL_ERROR "Coverage requires -DMSGPACK_BUILD_TESTS=ON") + ENDIF () + STRING(TOUPPER "${CMAKE_BUILD_TYPE}" UPPER_CMAKE_BUILD_TYPE) + IF (NOT "${UPPER_CMAKE_BUILD_TYPE}" STREQUAL "DEBUG") + MESSAGE(FATAL_ERROR "Coverage requires -DCMAKE_BUILD_TYPE=Debug") + ENDIF () + + INCLUDE(CodeCoverage) + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_FLAGS}") + + SETUP_TARGET_FOR_COVERAGE(coverage make coverage test) +ENDIF () + +IF (MSGPACK_BUILD_TESTS) + ENABLE_TESTING () + list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") + # MEMORYCHECK_COMMAND_OPTIONS needs to place prior to CTEST_MEMORYCHECK_COMMAND + SET (MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --show-leak-kinds=definite,possible --error-exitcode=1") + FIND_PROGRAM(CTEST_MEMORYCHECK_COMMAND NAMES valgrind) + INCLUDE(Dart) + ADD_SUBDIRECTORY (test) +ENDIF () + +IF ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + IF (MSGPACK_ENABLE_SHARED OR MSGPACK_ENABLE_STATIC) + SET_PROPERTY (TARGET msgpackc APPEND_STRING PROPERTY COMPILE_FLAGS " -Wall -Wextra -DPIC") + ENDIF () + IF (MSGPACK_ENABLE_SHARED AND MSGPACK_ENABLE_STATIC) + SET_PROPERTY (TARGET msgpackc-static APPEND_STRING PROPERTY COMPILE_FLAGS " -Wall -Wextra" ) + ENDIF () +ENDIF () + +IF ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + IF (MSGPACK_ENABLE_SHARED OR MSGPACK_ENABLE_STATIC) + SET_PROPERTY (TARGET msgpackc APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-mismatched-tags") + ENDIF () + IF (MSGPACK_ENABLE_SHARED AND MSGPACK_ENABLE_STATIC) + SET_PROPERTY (TARGET msgpackc-static APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-mismatched-tags") + ENDIF () +ENDIF () + +IF ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") + IF (CMAKE_C_FLAGS MATCHES "/W[0-4]") + STRING(REGEX REPLACE "/W[0-4]" "/W3 /WX" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + ELSE () + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W3 /WX") + ENDIF () +ENDIF () + +IF ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") + IF (CMAKE_C_FLAGS MATCHES "/W[0-4]") + STRING(REGEX REPLACE "/W[0-4]" "/W3 /WX" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + ELSE () + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W3 /WX") + ENDIF () +ENDIF () + +IF ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC90" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC10") + SET_SOURCE_FILES_PROPERTIES(${msgpackc_SOURCES} PROPERTIES LANGUAGE C) +ENDIF () + +IF ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "sparc") + SET (CMAKE_C_FLAGS "-DMSGPACK_ZONE_ALIGN=8 ${CMAKE_C_FLAGS}") +ENDIF () + +IF (NOT DEFINED CMAKE_INSTALL_BINDIR) + SET(CMAKE_INSTALL_BINDIR bin) +ENDIF () + +IF (NOT DEFINED CMAKE_INSTALL_LIBDIR) + SET(CMAKE_INSTALL_LIBDIR lib) +ENDIF () + +IF (MSGPACK_BUILD_EXAMPLES) + ADD_SUBDIRECTORY (example) +ENDIF () + +IF (MSGPACK_ENABLE_SHARED OR MSGPACK_ENABLE_STATIC) + SET (MSGPACK_INSTALLTARGETS msgpackc) +ENDIF () + +IF (MSGPACK_ENABLE_SHARED AND MSGPACK_ENABLE_STATIC) + LIST (APPEND MSGPACK_INSTALLTARGETS msgpackc-static) +ENDIF () + +INSTALL (TARGETS ${MSGPACK_INSTALLTARGETS} EXPORT msgpack-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) +FOREACH (file ${msgpackc_common_HEADERS}) + GET_FILENAME_COMPONENT (dir ${file} PATH) + INSTALL (FILES ${file} DESTINATION ${CMAKE_INSTALL_PREFIX}/${dir}) +ENDFOREACH () +FOREACH (file ${msgpackc_configured_HEADERS}) + GET_FILENAME_COMPONENT (dir ${file} PATH) + INSTALL (FILES ${CMAKE_CURRENT_BINARY_DIR}/${file} DESTINATION ${CMAKE_INSTALL_PREFIX}/${dir}) +ENDFOREACH () +IF (NOT MSVC) + INSTALL (FILES ${CMAKE_CURRENT_BINARY_DIR}/msgpack.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +ENDIF () + +# Doxygen +FIND_PACKAGE (Doxygen) +IF (DOXYGEN_FOUND) + LIST (APPEND Doxyfile_c_CONTENT + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile_c + COMMAND ${CMAKE_COMMAND} -E echo "FILE_PATTERNS = *.h" >> ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile_c + COMMAND ${CMAKE_COMMAND} -E echo "OUTPUT_DIRECTORY = doc_c" >> ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile_c + COMMAND ${CMAKE_COMMAND} -E echo "INPUT = ${CMAKE_CURRENT_SOURCE_DIR}/include" >> ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile_c + COMMAND ${CMAKE_COMMAND} -E echo "EXTRACT_ALL = YES" >> ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile_c + COMMAND ${CMAKE_COMMAND} -E echo "PROJECT_NAME = \"MessagePack for C\"" >> ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile_c + COMMAND ${CMAKE_COMMAND} -E echo "STRIP_FROM_PATH = ${CMAKE_CURRENT_SOURCE_DIR}/include" >> ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile_c + ) + IF (DOXYGEN_DOT_FOUND) + LIST (APPEND Doxyfile_c_CONTENT + COMMAND ${CMAKE_COMMAND} -E echo "HAVE_DOT = YES" >> ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile_c + ) + ENDIF () + ADD_CUSTOM_TARGET ( + doxygen + ${Doxyfile_c_CONTENT} + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile_c + VERBATIM + ) +ENDIF () + +INCLUDE (CMakePackageConfigHelpers) + +SET (CMAKE_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/msgpack") + +WRITE_BASIC_PACKAGE_VERSION_FILE ( + msgpack-config-version.cmake + VERSION ${VERSION} + COMPATIBILITY SameMajorVersion +) + +IF (NOT CMAKE_VERSION VERSION_LESS 3.0) + EXPORT (EXPORT msgpack-targets + FILE "${CMAKE_CURRENT_BINARY_DIR}/msgpack-targets.cmake" + ) +ENDIF () + +CONFIGURE_PACKAGE_CONFIG_FILE (msgpack-config.cmake.in + msgpack-config.cmake + INSTALL_DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" +) + +INSTALL (EXPORT msgpack-targets + FILE + msgpack-targets.cmake + DESTINATION + "${CMAKE_INSTALL_CMAKEDIR}" +) + +INSTALL ( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/msgpack-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/msgpack-config-version.cmake" + DESTINATION + "${CMAKE_INSTALL_CMAKEDIR}" +) diff --git a/local_pod_repo/msgpack-c/msgpack-c/COPYING b/local_pod_repo/msgpack-c/msgpack-c/COPYING new file mode 100644 index 0000000..61e4acc --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/COPYING @@ -0,0 +1,5 @@ +Copyright (C) 2008-2015 FURUHASHI Sadayuki + + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) diff --git a/local_pod_repo/msgpack-c/msgpack-c/Doxyfile b/local_pod_repo/msgpack-c/msgpack-c/Doxyfile new file mode 100644 index 0000000..227d6e2 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/Doxyfile @@ -0,0 +1,1552 @@ +# Doxyfile 1.6.2 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = "MessagePack" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.hpp *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +#RECURSIVE = NO +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = NO + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be implemented using a PHP enabled web server instead of at the web client using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server based approach is that it scales better to large projects and allows full text search. The disadvances is that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/local_pod_repo/msgpack-c/msgpack-c/Files.cmake b/local_pod_repo/msgpack-c/msgpack-c/Files.cmake new file mode 100644 index 0000000..5d2730b --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/Files.cmake @@ -0,0 +1,41 @@ +# Source files +SET (msgpackc_SOURCES + src/objectc.c + src/unpack.c + src/version.c + src/vrefbuffer.c + src/zone.c +) + +# Header files +SET (msgpackc_common_HEADERS + include/msgpack.h + include/msgpack/fbuffer.h + include/msgpack/gcc_atomic.h + include/msgpack/object.h + include/msgpack/pack.h + include/msgpack/pack_define.h + include/msgpack/sbuffer.h + include/msgpack/timestamp.h + include/msgpack/unpack.h + include/msgpack/unpack_define.h + include/msgpack/unpack_template.h + include/msgpack/util.h + include/msgpack/version.h + include/msgpack/version_master.h + include/msgpack/vrefbuffer.h + include/msgpack/zbuffer.h + include/msgpack/zone.h +) + +# Header files will configured +SET (msgpackc_configured_HEADERS + include/msgpack/pack_template.h + include/msgpack/sysdep.h +) + +# All header files +LIST (APPEND msgpackc_HEADERS + ${msgpackc_common_HEADERS} + ${msgpackc_configured_HEADERS} +) diff --git a/local_pod_repo/msgpack-c/msgpack-c/LICENSE_1_0.txt b/local_pod_repo/msgpack-c/msgpack-c/LICENSE_1_0.txt new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/local_pod_repo/msgpack-c/msgpack-c/NOTICE b/local_pod_repo/msgpack-c/msgpack-c/NOTICE new file mode 100644 index 0000000..55106ba --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/NOTICE @@ -0,0 +1,14 @@ +This product bundles Boost Predef and Boost Preprocessor. +They are distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at +http://www.boost.org/LICENSE_1_0.txt) + +For details, see the following files: + +external/boost/predef +include/msgpack/predef.h +include/msgpack/predef/* + +external/boost/preprocessor +include/msgpack/preprocessor.hpp +include/msgpack/preprocessor/* diff --git a/local_pod_repo/msgpack-c/msgpack-c/QUICKSTART-C.md b/local_pod_repo/msgpack-c/msgpack-c/QUICKSTART-C.md new file mode 100644 index 0000000..852718f --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/QUICKSTART-C.md @@ -0,0 +1,193 @@ +# Implementation Status + +The serialization library is production-ready. + +Currently, RPC implementation is not available. + +# Install + +## Install with package manager + +### MacOS with MacPorts + +On MacOS, you can install MessagePack for C using MacPorts. + +``` +$ sudo port install msgpack +``` + +You might need to run `sudo port selfupdate` before installing to update the package repository. + +You can also install via Homebrew. + +``` +$ brew install msgpack +``` + +### FreeBSD with Ports Collection + +On FreeBSD, you can use Ports Collection. Install [net/msgpack](http://www.freebsd.org/cgi/cvsweb.cgi/ports/devel/msgpack/) package. + +### Gentoo Linux with Portage + +On Gentoo Linux, you can use emerge. Install [dev-libs/msgpack](http://gentoo-portage.com/dev-libs/msgpack) package. + +### Windows with vcpkg + +There are several package managers available, and vcpkg is typical. + +``` +$ vcpkg install msgpack:x64-windows +``` +## Install with source code + +### Build with cmake + +You need to install cmake (2.8.12 or higher) first. + +``` +$ git clone https://github.com/msgpack/msgpack-c.git +$ cd msgpack-c +$ mkdir build +$ cd build +$ cmake .. +$ cmake --build . +$ cmake --build . --target install +``` +### Build with autotools + +In versions 1.4.2 and below, you can use `autotools` to build on UNIX-like platforms. +``` +$ wget https://github.com/msgpack/msgpack-c/archive/cpp-1.3.0.tar.gz +$ tar zxvf msgpack-1.3.0.tar.gz +$ cd msgpack-1.3.0 +$ ./bootstrap # If the 'configure' script already exists, you can omit this step. +$ ./configure +$ make +$ sudo make install +``` + +# Serialization QuickStart for C + +## First program + +Include `msgpack.h` header and link `msgpack` library to use MessagePack on your program. + +```c +#include +#include + +int main(void) { + + /* creates buffer and serializer instance. */ + msgpack_sbuffer* buffer = msgpack_sbuffer_new(); + msgpack_packer* pk = msgpack_packer_new(buffer, msgpack_sbuffer_write); + + /* serializes ["Hello", "MessagePack"]. */ + msgpack_pack_array(pk, 2); + msgpack_pack_bin(pk, 5); + msgpack_pack_bin_body(pk, "Hello", 5); + msgpack_pack_bin(pk, 11); + msgpack_pack_bin_body(pk, "MessagePack", 11); + + /* deserializes it. */ + msgpack_unpacked msg; + msgpack_unpacked_init(&msg); + msgpack_unpack_return ret = msgpack_unpack_next(&msg, buffer->data, buffer->size, NULL); + + /* prints the deserialized object. */ + msgpack_object obj = msg.data; + msgpack_object_print(stdout, obj); /*=> ["Hello", "MessagePack"] */ + + /* cleaning */ + msgpack_unpacked_destroy(&msg); + msgpack_sbuffer_free(buffer); + msgpack_packer_free(pk); +} +``` + +## Simple program with a loop + +```c +#include +#include + +int main(void) { + + /* creates buffer and serializer instance. */ + msgpack_sbuffer* buffer = msgpack_sbuffer_new(); + msgpack_packer* pk = msgpack_packer_new(buffer, msgpack_sbuffer_write); + + int j; + + for(j = 0; j<23; j++) { + /* NB: the buffer needs to be cleared on each iteration */ + msgpack_sbuffer_clear(buffer); + + /* serializes ["Hello", "MessagePack"]. */ + msgpack_pack_array(pk, 3); + msgpack_pack_bin(pk, 5); + msgpack_pack_bin_body(pk, "Hello", 5); + msgpack_pack_bin(pk, 11); + msgpack_pack_bin_body(pk, "MessagePack", 11); + msgpack_pack_int(pk, j); + + /* deserializes it. */ + msgpack_unpacked msg; + msgpack_unpacked_init(&msg); + msgpack_unpack_return ret = msgpack_unpack_next(&msg, buffer->data, buffer->size, NULL); + + /* prints the deserialized object. */ + msgpack_object obj = msg.data; + msgpack_object_print(stdout, obj); /*=> ["Hello", "MessagePack"] */ + msgpack_unpacked_destroy(&msg); + puts(""); + } + + /* cleaning */ + msgpack_sbuffer_free(buffer); + msgpack_packer_free(pk); +} +``` + +## Streaming feature + +```c +#include +#include + +int main(void) { + /* serializes multiple objects using msgpack_packer. */ + msgpack_sbuffer* buffer = msgpack_sbuffer_new(); + msgpack_packer* pk = msgpack_packer_new(buffer, msgpack_sbuffer_write); + msgpack_pack_int(pk, 1); + msgpack_pack_int(pk, 2); + msgpack_pack_int(pk, 3); + + /* deserializes these objects using msgpack_unpacker. */ + msgpack_unpacker pac; + msgpack_unpacker_init(&pac, MSGPACK_UNPACKER_INIT_BUFFER_SIZE); + + /* feeds the buffer. */ + msgpack_unpacker_reserve_buffer(&pac, buffer->size); + memcpy(msgpack_unpacker_buffer(&pac), buffer->data, buffer->size); + msgpack_unpacker_buffer_consumed(&pac, buffer->size); + + /* now starts streaming deserialization. */ + msgpack_unpacked result; + msgpack_unpacked_init(&result); + + while(msgpack_unpacker_next(&pac, &result)) { + msgpack_object_print(stdout, result.data); + puts(""); + } + + /* results: + * $ gcc stream.cc -lmsgpackc -o stream + * $ ./stream + * 1 + * 2 + * 3 + */ +} +``` diff --git a/local_pod_repo/msgpack-c/msgpack-c/README.md b/local_pod_repo/msgpack-c/msgpack-c/README.md new file mode 100644 index 0000000..e803596 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/README.md @@ -0,0 +1,139 @@ +`msgpack` for C +=================== + +Version 4.0.0 [![Build Status](https://github.com/msgpack/msgpack-c/workflows/CI/badge.svg?branch=c_master)](https://github.com/msgpack/msgpack-c/actions) [![Build status](https://ci.appveyor.com/api/projects/status/8kstcgt79qj123mw/branch/c_master?svg=true)](https://ci.appveyor.com/project/redboltz/msgpack-c/branch/c_master) +[![codecov](https://codecov.io/gh/msgpack/msgpack-c/branch/c_master/graph/badge.svg)](https://codecov.io/gh/msgpack/msgpack-c/branch/c_master) + +It's like JSON but smaller and faster. + +Overview +-------- + +[MessagePack](http://msgpack.org/) is an efficient binary serialization +format, which lets you exchange data among multiple languages like JSON, +except that it's faster and smaller. Small integers are encoded into a +single byte and short strings require only one extra byte in +addition to the strings themselves. + +Example +------- + +```c +#include +#include + +int main(void) +{ + /* msgpack::sbuffer is a simple buffer implementation. */ + msgpack_sbuffer sbuf; + msgpack_sbuffer_init(&sbuf); + + /* serialize values into the buffer using msgpack_sbuffer_write callback function. */ + msgpack_packer pk; + msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write); + + msgpack_pack_array(&pk, 3); + msgpack_pack_int(&pk, 1); + msgpack_pack_true(&pk); + msgpack_pack_str(&pk, 7); + msgpack_pack_str_body(&pk, "example", 7); + + /* deserialize the buffer into msgpack_object instance. */ + /* deserialized object is valid during the msgpack_zone instance alive. */ + msgpack_zone mempool; + msgpack_zone_init(&mempool, 2048); + + msgpack_object deserialized; + msgpack_unpack(sbuf.data, sbuf.size, NULL, &mempool, &deserialized); + + /* print the deserialized object. */ + msgpack_object_print(stdout, deserialized); + puts(""); + + msgpack_zone_destroy(&mempool); + msgpack_sbuffer_destroy(&sbuf); + + return 0; +} +``` + +See [`QUICKSTART-C.md`](./QUICKSTART-C.md) for more details. + +Usage +----- + +### Building and Installing + +#### Install from git repository + +##### Using the Terminal (CLI) + +You will need: + + - `gcc >= 4.1.0` + - `cmake >= 2.8.0` + +How to build: + + $ git clone https://github.com/msgpack/msgpack-c.git + $ cd msgpack-c + $ git checkout c_master + $ cmake . + $ make + $ sudo make install + +How to run tests: + +In order to run tests you must have the [GoogleTest](https://github.com/google/googletest) framework installed. If you do not currently have it, install it and re-run `cmake`. +Then: + + $ make test + +When you use the C part of `msgpack-c`, you need to build and link the library. By default, both static/shared libraries are built. If you want to build only static library, set `BUILD_SHARED_LIBS=OFF` to cmake. If you want to build only shared library, set `BUILD_SHARED_LIBS=ON`. + +#### GUI on Windows + +Clone msgpack-c git repository. + + $ git clone https://github.com/msgpack/msgpack-c.git + +or using GUI git client. + +e.g.) tortoise git https://code.google.com/p/tortoisegit/ + +1. Checkout to c_master branch + +2. Launch [cmake GUI client](http://www.cmake.org/cmake/resources/software.html). + +3. Set 'Where is the source code:' text box and 'Where to build +the binaries:' text box. + +4. Click 'Configure' button. + +5. Choose your Visual Studio version. + +6. Click 'Generate' button. + +7. Open the created msgpack.sln on Visual Studio. + +8. Build all. + +### Documentation + +You can get additional information including the tutorial on the +[wiki](https://github.com/msgpack/msgpack-c/wiki). + +Contributing +------------ + +`msgpack-c` is developed on GitHub at [msgpack/msgpack-c](https://github.com/msgpack/msgpack-c). +To report an issue or send a pull request, use the +[issue tracker](https://github.com/msgpack/msgpack-c/issues). + +Here's the list of [great contributors](https://github.com/msgpack/msgpack-c/graphs/contributors). + +License +------- + +`msgpack-c` is licensed under the Boost Software License, Version 1.0. See +the [`LICENSE_1_0.txt`](./LICENSE_1_0.txt) file for details. diff --git a/local_pod_repo/msgpack-c/msgpack-c/appveyor.yml b/local_pod_repo/msgpack-c/msgpack-c/appveyor.yml new file mode 100644 index 0000000..ba1c221 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/appveyor.yml @@ -0,0 +1,43 @@ +version: 4.0.0.{build} + +branches: + only: + - c_master + +image: +- Visual Studio 2015 +environment: + matrix: + - msvc: '"Visual Studio 10 2010"' + - msvc: '"Visual Studio 11 2012"' + - msvc: '"Visual Studio 12 2013"' + - msvc: '"Visual Studio 14 2015"' +build_script: +- appveyor DownloadFile https://github.com/google/googletest/archive/release-1.7.0.zip -FileName googletest-release-1.7.0.zip +- 7z x googletest-release-1.7.0.zip > NUL +- cd googletest-release-1.7.0 +- md build +- cd build +- cmake -G %msvc% -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_FLAGS=/D_VARIADIC_MAX=10 .. +- cmake --build . --config Release +- cd .. +- cd .. +- appveyor DownloadFile http://zlib.net/zlib-1.2.11.tar.gz -FileName zlib-1.2.11.tar.gz +- 7z x zlib-1.2.11.tar.gz > NUL +- 7z x zlib-1.2.11.tar > NUL +- cd zlib-1.2.11 +- md build +- cd build +- cmake -G %msvc% .. +- cmake --build . --config Release +- copy zconf.h .. +- cd .. +- cd .. +- md build +- cd build +- cmake -G %msvc% -DGTEST_LIBRARY=%APPVEYOR_BUILD_FOLDER%\googletest-release-1.7.0\build\Release\gtest.lib -DGTEST_MAIN_LIBRARY=%APPVEYOR_BUILD_FOLDER%\googletest-release-1.7.0\build\Release\gtest_main.lib -DGTEST_INCLUDE_DIR=%APPVEYOR_BUILD_FOLDER%\googletest-release-1.7.0\include -DZLIB_LIBRARY=%APPVEYOR_BUILD_FOLDER%\zlib-1.2.11\build\Release\zlib.lib -DZLIB_INCLUDE_DIR=%APPVEYOR_BUILD_FOLDER%\zlib-1.2.11 -DCMAKE_CXX_FLAGS='"/D_VARIADIC_MAX=10 /EHsc"' .. +- cmake --build . --config Release -v + +test_script: +- set PATH=%PATH%;%APPVEYOR_BUILD_FOLDER%\googletest-release-1.7.0\build\Release;%APPVEYOR_BUILD_FOLDER%\zlib-1.2.11\build\Release;%APPVEYOR_BUILD_FOLDER%\build\release +- ctest -V diff --git a/local_pod_repo/msgpack-c/msgpack-c/codecov.yml b/local_pod_repo/msgpack-c/msgpack-c/codecov.yml new file mode 100644 index 0000000..caa3005 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/codecov.yml @@ -0,0 +1,36 @@ +codecov: + notify: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "70...100" + + status: + project: yes + patch: yes + changes: no + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "header, diff" + behavior: default + require_changes: no + +ignore: + - "test" + - "fuzz" + - "erb" + - "ci" + - "cmake" + - "examle" + - "external" + - "usr" diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack.h new file mode 100644 index 0000000..9b75f95 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack.h @@ -0,0 +1,24 @@ +/* + * MessagePack for C + * + * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +/** + * @defgroup msgpack MessagePack C + * @{ + * @} + */ + +#include "msgpack/util.h" +#include "msgpack/object.h" +#include "msgpack/zone.h" +#include "msgpack/pack.h" +#include "msgpack/unpack.h" +#include "msgpack/sbuffer.h" +#include "msgpack/vrefbuffer.h" +#include "msgpack/msgpack_version.h" + diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/fbuffer.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/fbuffer.h new file mode 100644 index 0000000..5c847dd --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/fbuffer.h @@ -0,0 +1,42 @@ +/* + * MessagePack for C FILE* buffer adaptor + * + * Copyright (C) 2013 Vladimir Volodko + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_FBUFFER_H +#define MSGPACK_FBUFFER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup msgpack_fbuffer FILE* buffer + * @ingroup msgpack_buffer + * @{ + */ + +static inline int msgpack_fbuffer_write(void* data, const char* buf, size_t len) +{ + assert(buf || len == 0); + if(!buf) return 0; + + return (1 == fwrite(buf, len, 1, (FILE *)data)) ? 0 : -1; +} + +/** @} */ + + +#ifdef __cplusplus +} +#endif + +#endif /* msgpack/fbuffer.h */ diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/gcc_atomic.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/gcc_atomic.h new file mode 100644 index 0000000..6b1b1a7 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/gcc_atomic.h @@ -0,0 +1,25 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ + +#ifndef MSGPACK_GCC_ATOMIC_H +#define MSGPACK_GCC_ATOMIC_H + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef int _msgpack_atomic_counter_t; + +int _msgpack_sync_decr_and_fetch(volatile _msgpack_atomic_counter_t* ptr); +int _msgpack_sync_incr_and_fetch(volatile _msgpack_atomic_counter_t* ptr); + + +#if defined(__cplusplus) +} +#endif + + +#endif // MSGPACK_GCC_ATOMIC_H diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/msgpack_version.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/msgpack_version.h new file mode 100644 index 0000000..bd6605b --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/msgpack_version.h @@ -0,0 +1,38 @@ +/* + * MessagePack for C version information + * + * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_VERSION_H +#define MSGPACK_VERSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +MSGPACK_DLLEXPORT +const char* msgpack_version(void); +MSGPACK_DLLEXPORT +int msgpack_version_major(void); +MSGPACK_DLLEXPORT +int msgpack_version_minor(void); +MSGPACK_DLLEXPORT +int msgpack_version_revision(void); + +#include "version_master.h" + +#define MSGPACK_STR(v) #v +#define MSGPACK_VERSION_I(maj, min, rev) MSGPACK_STR(maj) "." MSGPACK_STR(min) "." MSGPACK_STR(rev) + +#define MSGPACK_VERSION MSGPACK_VERSION_I(MSGPACK_VERSION_MAJOR, MSGPACK_VERSION_MINOR, MSGPACK_VERSION_REVISION) + +#ifdef __cplusplus +} +#endif + +#endif /* msgpack/version.h */ + diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/object.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/object.h new file mode 100644 index 0000000..e943174 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/object.h @@ -0,0 +1,118 @@ +/* + * MessagePack for C dynamic typing routine + * + * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_OBJECT_H +#define MSGPACK_OBJECT_H + +#include "zone.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup msgpack_object Dynamically typed object + * @ingroup msgpack + * @{ + */ + +typedef enum { + MSGPACK_OBJECT_NIL = 0x00, + MSGPACK_OBJECT_BOOLEAN = 0x01, + MSGPACK_OBJECT_POSITIVE_INTEGER = 0x02, + MSGPACK_OBJECT_NEGATIVE_INTEGER = 0x03, + MSGPACK_OBJECT_FLOAT32 = 0x0a, + MSGPACK_OBJECT_FLOAT64 = 0x04, + MSGPACK_OBJECT_FLOAT = 0x04, +#if defined(MSGPACK_USE_LEGACY_NAME_AS_FLOAT) + MSGPACK_OBJECT_DOUBLE = MSGPACK_OBJECT_FLOAT, /* obsolete */ +#endif /* MSGPACK_USE_LEGACY_NAME_AS_FLOAT */ + MSGPACK_OBJECT_STR = 0x05, + MSGPACK_OBJECT_ARRAY = 0x06, + MSGPACK_OBJECT_MAP = 0x07, + MSGPACK_OBJECT_BIN = 0x08, + MSGPACK_OBJECT_EXT = 0x09 +} msgpack_object_type; + + +struct msgpack_object; +struct msgpack_object_kv; + +typedef struct { + uint32_t size; + struct msgpack_object* ptr; +} msgpack_object_array; + +typedef struct { + uint32_t size; + struct msgpack_object_kv* ptr; +} msgpack_object_map; + +typedef struct { + uint32_t size; + const char* ptr; +} msgpack_object_str; + +typedef struct { + uint32_t size; + const char* ptr; +} msgpack_object_bin; + +typedef struct { + int8_t type; + uint32_t size; + const char* ptr; +} msgpack_object_ext; + +typedef union { + bool boolean; + uint64_t u64; + int64_t i64; +#if defined(MSGPACK_USE_LEGACY_NAME_AS_FLOAT) + double dec; /* obsolete*/ +#endif /* MSGPACK_USE_LEGACY_NAME_AS_FLOAT */ + double f64; + msgpack_object_array array; + msgpack_object_map map; + msgpack_object_str str; + msgpack_object_bin bin; + msgpack_object_ext ext; +} msgpack_object_union; + +typedef struct msgpack_object { + msgpack_object_type type; + msgpack_object_union via; +} msgpack_object; + +typedef struct msgpack_object_kv { + msgpack_object key; + msgpack_object val; +} msgpack_object_kv; + +#if !defined(_KERNEL_MODE) +MSGPACK_DLLEXPORT +void msgpack_object_print(FILE* out, msgpack_object o); +#endif + +MSGPACK_DLLEXPORT +int msgpack_object_print_buffer(char *buffer, size_t buffer_size, msgpack_object o); + +MSGPACK_DLLEXPORT +bool msgpack_object_equal(const msgpack_object x, const msgpack_object y); + +/** @} */ + + +#ifdef __cplusplus +} +#endif + +#endif /* msgpack/object.h */ diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/pack.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/pack.h new file mode 100644 index 0000000..08ab84b --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/pack.h @@ -0,0 +1,174 @@ +/* + * MessagePack for C packing routine + * + * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_PACK_H +#define MSGPACK_PACK_H + +#include "pack_define.h" +#include "object.h" +#include "timestamp.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup msgpack_buffer Buffers + * @ingroup msgpack + * @{ + * @} + */ + +/** + * @defgroup msgpack_pack Serializer + * @ingroup msgpack + * @{ + */ + +typedef int (*msgpack_packer_write)(void* data, const char* buf, size_t len); + +typedef struct msgpack_packer { + void* data; + msgpack_packer_write callback; +} msgpack_packer; + +static void msgpack_packer_init(msgpack_packer* pk, void* data, msgpack_packer_write callback); + +static msgpack_packer* msgpack_packer_new(void* data, msgpack_packer_write callback); +static void msgpack_packer_free(msgpack_packer* pk); + +static int msgpack_pack_char(msgpack_packer* pk, char d); + +static int msgpack_pack_signed_char(msgpack_packer* pk, signed char d); +static int msgpack_pack_short(msgpack_packer* pk, short d); +static int msgpack_pack_int(msgpack_packer* pk, int d); +static int msgpack_pack_long(msgpack_packer* pk, long d); +static int msgpack_pack_long_long(msgpack_packer* pk, long long d); +static int msgpack_pack_unsigned_char(msgpack_packer* pk, unsigned char d); +static int msgpack_pack_unsigned_short(msgpack_packer* pk, unsigned short d); +static int msgpack_pack_unsigned_int(msgpack_packer* pk, unsigned int d); +static int msgpack_pack_unsigned_long(msgpack_packer* pk, unsigned long d); +static int msgpack_pack_unsigned_long_long(msgpack_packer* pk, unsigned long long d); + +static int msgpack_pack_uint8(msgpack_packer* pk, uint8_t d); +static int msgpack_pack_uint16(msgpack_packer* pk, uint16_t d); +static int msgpack_pack_uint32(msgpack_packer* pk, uint32_t d); +static int msgpack_pack_uint64(msgpack_packer* pk, uint64_t d); +static int msgpack_pack_int8(msgpack_packer* pk, int8_t d); +static int msgpack_pack_int16(msgpack_packer* pk, int16_t d); +static int msgpack_pack_int32(msgpack_packer* pk, int32_t d); +static int msgpack_pack_int64(msgpack_packer* pk, int64_t d); + +static int msgpack_pack_fix_uint8(msgpack_packer* pk, uint8_t d); +static int msgpack_pack_fix_uint16(msgpack_packer* pk, uint16_t d); +static int msgpack_pack_fix_uint32(msgpack_packer* pk, uint32_t d); +static int msgpack_pack_fix_uint64(msgpack_packer* pk, uint64_t d); +static int msgpack_pack_fix_int8(msgpack_packer* pk, int8_t d); +static int msgpack_pack_fix_int16(msgpack_packer* pk, int16_t d); +static int msgpack_pack_fix_int32(msgpack_packer* pk, int32_t d); +static int msgpack_pack_fix_int64(msgpack_packer* pk, int64_t d); + +static int msgpack_pack_float(msgpack_packer* pk, float d); +static int msgpack_pack_double(msgpack_packer* pk, double d); + +static int msgpack_pack_nil(msgpack_packer* pk); +static int msgpack_pack_true(msgpack_packer* pk); +static int msgpack_pack_false(msgpack_packer* pk); + +static int msgpack_pack_array(msgpack_packer* pk, size_t n); + +static int msgpack_pack_map(msgpack_packer* pk, size_t n); + +static int msgpack_pack_str(msgpack_packer* pk, size_t l); +static int msgpack_pack_str_body(msgpack_packer* pk, const void* b, size_t l); +static int msgpack_pack_str_with_body(msgpack_packer* pk, const void* b, size_t l); + +static int msgpack_pack_v4raw(msgpack_packer* pk, size_t l); +static int msgpack_pack_v4raw_body(msgpack_packer* pk, const void* b, size_t l); + +static int msgpack_pack_bin(msgpack_packer* pk, size_t l); +static int msgpack_pack_bin_body(msgpack_packer* pk, const void* b, size_t l); +static int msgpack_pack_bin_with_body(msgpack_packer* pk, const void* b, size_t l); + +static int msgpack_pack_ext(msgpack_packer* pk, size_t l, int8_t type); +static int msgpack_pack_ext_body(msgpack_packer* pk, const void* b, size_t l); +static int msgpack_pack_ext_with_body(msgpack_packer* pk, const void* b, size_t l, int8_t type); + +static int msgpack_pack_timestamp(msgpack_packer* pk, const msgpack_timestamp* d); + +MSGPACK_DLLEXPORT +int msgpack_pack_object(msgpack_packer* pk, msgpack_object d); + + +/** @} */ + + +#define msgpack_pack_inline_func(name) \ + inline int msgpack_pack ## name + +#define msgpack_pack_inline_func_cint(name) \ + inline int msgpack_pack ## name + +#define msgpack_pack_inline_func_fixint(name) \ + inline int msgpack_pack_fix ## name + +#define msgpack_pack_user msgpack_packer* + +#define msgpack_pack_append_buffer(user, buf, len) \ + return (*(user)->callback)((user)->data, (const char*)buf, len) + +#include "pack_template.h" + +inline void msgpack_packer_init(msgpack_packer* pk, void* data, msgpack_packer_write callback) +{ + pk->data = data; + pk->callback = callback; +} + +inline msgpack_packer* msgpack_packer_new(void* data, msgpack_packer_write callback) +{ + msgpack_packer* pk = (msgpack_packer*)calloc(1, sizeof(msgpack_packer)); + if(!pk) { return NULL; } + msgpack_packer_init(pk, data, callback); + return pk; +} + +inline void msgpack_packer_free(msgpack_packer* pk) +{ + free(pk); +} + +inline int msgpack_pack_str_with_body(msgpack_packer* pk, const void* b, size_t l) + { + int ret = msgpack_pack_str(pk, l); + if (ret != 0) { return ret; } + return msgpack_pack_str_body(pk, b, l); + } + + inline int msgpack_pack_bin_with_body(msgpack_packer* pk, const void* b, size_t l) + { + int ret = msgpack_pack_bin(pk, l); + if (ret != 0) { return ret; } + return msgpack_pack_bin_body(pk, b, l); + } + + inline int msgpack_pack_ext_with_body(msgpack_packer* pk, const void* b, size_t l, int8_t type) + { + int ret = msgpack_pack_ext(pk, l, type); + if (ret != 0) { return ret; } + return msgpack_pack_ext_body(pk, b, l); + } + +#ifdef __cplusplus +} +#endif + +#endif /* msgpack/pack.h */ diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/pack_define.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/pack_define.h new file mode 100644 index 0000000..542c2c6 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/pack_define.h @@ -0,0 +1,18 @@ +/* + * MessagePack unpacking routine template + * + * Copyright (C) 2008-2010 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_PACK_DEFINE_H +#define MSGPACK_PACK_DEFINE_H + +#include "sysdep.h" +#include +#include + +#endif /* msgpack/pack_define.h */ + diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/pack_template.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/pack_template.h new file mode 100644 index 0000000..53953fa --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/pack_template.h @@ -0,0 +1,952 @@ +/* + * MessagePack packing routine template + * + * Copyright (C) 2008-2010 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ + +#ifndef MSGPACK_ENDIAN_BIG_BYTE +#define MSGPACK_ENDIAN_BIG_BYTE 0 +#endif +#ifndef MSGPACK_ENDIAN_LITTLE_BYTE +#define MSGPACK_ENDIAN_LITTLE_BYTE 1 +#endif + +#if MSGPACK_ENDIAN_LITTLE_BYTE +#define TAKE8_8(d) ((uint8_t*)&d)[0] +#define TAKE8_16(d) ((uint8_t*)&d)[0] +#define TAKE8_32(d) ((uint8_t*)&d)[0] +#define TAKE8_64(d) ((uint8_t*)&d)[0] +#elif MSGPACK_ENDIAN_BIG_BYTE +#define TAKE8_8(d) ((uint8_t*)&d)[0] +#define TAKE8_16(d) ((uint8_t*)&d)[1] +#define TAKE8_32(d) ((uint8_t*)&d)[3] +#define TAKE8_64(d) ((uint8_t*)&d)[7] +#else +#error msgpack-c supports only big endian and little endian +#endif + +#ifndef msgpack_pack_inline_func +#error msgpack_pack_inline_func template is not defined +#endif + +#ifndef msgpack_pack_user +#error msgpack_pack_user type is not defined +#endif + +#ifndef msgpack_pack_append_buffer +#error msgpack_pack_append_buffer callback is not defined +#endif + +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4204) /* nonstandard extension used: non-constant aggregate initializer */ +#endif + +/* + * Integer + */ + +#define msgpack_pack_real_uint8(x, d) \ +do { \ + if(d < (1<<7)) { \ + /* fixnum */ \ + msgpack_pack_append_buffer(x, &TAKE8_8(d), 1); \ + } else { \ + /* unsigned 8 */ \ + unsigned char buf[2] = {0xcc, TAKE8_8(d)}; \ + msgpack_pack_append_buffer(x, buf, 2); \ + } \ +} while(0) + +#define msgpack_pack_real_uint16(x, d) \ +do { \ + if(d < (1<<7)) { \ + /* fixnum */ \ + msgpack_pack_append_buffer(x, &TAKE8_16(d), 1); \ + } else if(d < (1<<8)) { \ + /* unsigned 8 */ \ + unsigned char buf[2] = {0xcc, TAKE8_16(d)}; \ + msgpack_pack_append_buffer(x, buf, 2); \ + } else { \ + /* unsigned 16 */ \ + unsigned char buf[3]; \ + buf[0] = 0xcd; _msgpack_store16(&buf[1], (uint16_t)d); \ + msgpack_pack_append_buffer(x, buf, 3); \ + } \ +} while(0) + +#define msgpack_pack_real_uint32(x, d) \ +do { \ + if(d < (1<<8)) { \ + if(d < (1<<7)) { \ + /* fixnum */ \ + msgpack_pack_append_buffer(x, &TAKE8_32(d), 1); \ + } else { \ + /* unsigned 8 */ \ + unsigned char buf[2] = {0xcc, TAKE8_32(d)}; \ + msgpack_pack_append_buffer(x, buf, 2); \ + } \ + } else { \ + if(d < (1<<16)) { \ + /* unsigned 16 */ \ + unsigned char buf[3]; \ + buf[0] = 0xcd; _msgpack_store16(&buf[1], (uint16_t)d); \ + msgpack_pack_append_buffer(x, buf, 3); \ + } else { \ + /* unsigned 32 */ \ + unsigned char buf[5]; \ + buf[0] = 0xce; _msgpack_store32(&buf[1], (uint32_t)d); \ + msgpack_pack_append_buffer(x, buf, 5); \ + } \ + } \ +} while(0) + +#define msgpack_pack_real_uint64(x, d) \ +do { \ + if(d < (1ULL<<8)) { \ + if(d < (1ULL<<7)) { \ + /* fixnum */ \ + msgpack_pack_append_buffer(x, &TAKE8_64(d), 1); \ + } else { \ + /* unsigned 8 */ \ + unsigned char buf[2] = {0xcc, TAKE8_64(d)}; \ + msgpack_pack_append_buffer(x, buf, 2); \ + } \ + } else { \ + if(d < (1ULL<<16)) { \ + /* unsigned 16 */ \ + unsigned char buf[3]; \ + buf[0] = 0xcd; _msgpack_store16(&buf[1], (uint16_t)d); \ + msgpack_pack_append_buffer(x, buf, 3); \ + } else if(d < (1ULL<<32)) { \ + /* unsigned 32 */ \ + unsigned char buf[5]; \ + buf[0] = 0xce; _msgpack_store32(&buf[1], (uint32_t)d); \ + msgpack_pack_append_buffer(x, buf, 5); \ + } else { \ + /* unsigned 64 */ \ + unsigned char buf[9]; \ + buf[0] = 0xcf; _msgpack_store64(&buf[1], d); \ + msgpack_pack_append_buffer(x, buf, 9); \ + } \ + } \ +} while(0) + +#define msgpack_pack_real_int8(x, d) \ +do { \ + if(d < -(1<<5)) { \ + /* signed 8 */ \ + unsigned char buf[2] = {0xd0, TAKE8_8(d)}; \ + msgpack_pack_append_buffer(x, buf, 2); \ + } else { \ + /* fixnum */ \ + msgpack_pack_append_buffer(x, &TAKE8_8(d), 1); \ + } \ +} while(0) + +#define msgpack_pack_real_int16(x, d) \ +do { \ + if(d < -(1<<5)) { \ + if(d < -(1<<7)) { \ + /* signed 16 */ \ + unsigned char buf[3]; \ + buf[0] = 0xd1; _msgpack_store16(&buf[1], (int16_t)d); \ + msgpack_pack_append_buffer(x, buf, 3); \ + } else { \ + /* signed 8 */ \ + unsigned char buf[2] = {0xd0, TAKE8_16(d)}; \ + msgpack_pack_append_buffer(x, buf, 2); \ + } \ + } else if(d < (1<<7)) { \ + /* fixnum */ \ + msgpack_pack_append_buffer(x, &TAKE8_16(d), 1); \ + } else { \ + if(d < (1<<8)) { \ + /* unsigned 8 */ \ + unsigned char buf[2] = {0xcc, TAKE8_16(d)}; \ + msgpack_pack_append_buffer(x, buf, 2); \ + } else { \ + /* unsigned 16 */ \ + unsigned char buf[3]; \ + buf[0] = 0xcd; _msgpack_store16(&buf[1], (uint16_t)d); \ + msgpack_pack_append_buffer(x, buf, 3); \ + } \ + } \ +} while(0) + +#define msgpack_pack_real_int32(x, d) \ +do { \ + if(d < -(1<<5)) { \ + if(d < -(1<<15)) { \ + /* signed 32 */ \ + unsigned char buf[5]; \ + buf[0] = 0xd2; _msgpack_store32(&buf[1], (int32_t)d); \ + msgpack_pack_append_buffer(x, buf, 5); \ + } else if(d < -(1<<7)) { \ + /* signed 16 */ \ + unsigned char buf[3]; \ + buf[0] = 0xd1; _msgpack_store16(&buf[1], (int16_t)d); \ + msgpack_pack_append_buffer(x, buf, 3); \ + } else { \ + /* signed 8 */ \ + unsigned char buf[2] = {0xd0, TAKE8_32(d)}; \ + msgpack_pack_append_buffer(x, buf, 2); \ + } \ + } else if(d < (1<<7)) { \ + /* fixnum */ \ + msgpack_pack_append_buffer(x, &TAKE8_32(d), 1); \ + } else { \ + if(d < (1<<8)) { \ + /* unsigned 8 */ \ + unsigned char buf[2] = {0xcc, TAKE8_32(d)}; \ + msgpack_pack_append_buffer(x, buf, 2); \ + } else if(d < (1<<16)) { \ + /* unsigned 16 */ \ + unsigned char buf[3]; \ + buf[0] = 0xcd; _msgpack_store16(&buf[1], (uint16_t)d); \ + msgpack_pack_append_buffer(x, buf, 3); \ + } else { \ + /* unsigned 32 */ \ + unsigned char buf[5]; \ + buf[0] = 0xce; _msgpack_store32(&buf[1], (uint32_t)d); \ + msgpack_pack_append_buffer(x, buf, 5); \ + } \ + } \ +} while(0) + +#define msgpack_pack_real_int64(x, d) \ +do { \ + if(d < -(1LL<<5)) { \ + if(d < -(1LL<<15)) { \ + if(d < -(1LL<<31)) { \ + /* signed 64 */ \ + unsigned char buf[9]; \ + buf[0] = 0xd3; _msgpack_store64(&buf[1], d); \ + msgpack_pack_append_buffer(x, buf, 9); \ + } else { \ + /* signed 32 */ \ + unsigned char buf[5]; \ + buf[0] = 0xd2; _msgpack_store32(&buf[1], (int32_t)d); \ + msgpack_pack_append_buffer(x, buf, 5); \ + } \ + } else { \ + if(d < -(1<<7)) { \ + /* signed 16 */ \ + unsigned char buf[3]; \ + buf[0] = 0xd1; _msgpack_store16(&buf[1], (int16_t)d); \ + msgpack_pack_append_buffer(x, buf, 3); \ + } else { \ + /* signed 8 */ \ + unsigned char buf[2] = {0xd0, TAKE8_64(d)}; \ + msgpack_pack_append_buffer(x, buf, 2); \ + } \ + } \ + } else if(d < (1<<7)) { \ + /* fixnum */ \ + msgpack_pack_append_buffer(x, &TAKE8_64(d), 1); \ + } else { \ + if(d < (1LL<<16)) { \ + if(d < (1<<8)) { \ + /* unsigned 8 */ \ + unsigned char buf[2] = {0xcc, TAKE8_64(d)}; \ + msgpack_pack_append_buffer(x, buf, 2); \ + } else { \ + /* unsigned 16 */ \ + unsigned char buf[3]; \ + buf[0] = 0xcd; _msgpack_store16(&buf[1], (uint16_t)d); \ + msgpack_pack_append_buffer(x, buf, 3); \ + } \ + } else { \ + if(d < (1LL<<32)) { \ + /* unsigned 32 */ \ + unsigned char buf[5]; \ + buf[0] = 0xce; _msgpack_store32(&buf[1], (uint32_t)d); \ + msgpack_pack_append_buffer(x, buf, 5); \ + } else { \ + /* unsigned 64 */ \ + unsigned char buf[9]; \ + buf[0] = 0xcf; _msgpack_store64(&buf[1], d); \ + msgpack_pack_append_buffer(x, buf, 9); \ + } \ + } \ + } \ +} while(0) + + +#ifdef msgpack_pack_inline_func_fixint + +msgpack_pack_inline_func_fixint(_uint8)(msgpack_pack_user x, uint8_t d) +{ + unsigned char buf[2] = {0xcc, TAKE8_8(d)}; + msgpack_pack_append_buffer(x, buf, 2); +} + +msgpack_pack_inline_func_fixint(_uint16)(msgpack_pack_user x, uint16_t d) +{ + unsigned char buf[3]; + buf[0] = 0xcd; _msgpack_store16(&buf[1], d); + msgpack_pack_append_buffer(x, buf, 3); +} + +msgpack_pack_inline_func_fixint(_uint32)(msgpack_pack_user x, uint32_t d) +{ + unsigned char buf[5]; + buf[0] = 0xce; _msgpack_store32(&buf[1], d); + msgpack_pack_append_buffer(x, buf, 5); +} + +msgpack_pack_inline_func_fixint(_uint64)(msgpack_pack_user x, uint64_t d) +{ + unsigned char buf[9]; + buf[0] = 0xcf; _msgpack_store64(&buf[1], d); + msgpack_pack_append_buffer(x, buf, 9); +} + +msgpack_pack_inline_func_fixint(_int8)(msgpack_pack_user x, int8_t d) +{ + unsigned char buf[2] = {0xd0, TAKE8_8(d)}; + msgpack_pack_append_buffer(x, buf, 2); +} + +msgpack_pack_inline_func_fixint(_int16)(msgpack_pack_user x, int16_t d) +{ + unsigned char buf[3]; + buf[0] = 0xd1; _msgpack_store16(&buf[1], d); + msgpack_pack_append_buffer(x, buf, 3); +} + +msgpack_pack_inline_func_fixint(_int32)(msgpack_pack_user x, int32_t d) +{ + unsigned char buf[5]; + buf[0] = 0xd2; _msgpack_store32(&buf[1], d); + msgpack_pack_append_buffer(x, buf, 5); +} + +msgpack_pack_inline_func_fixint(_int64)(msgpack_pack_user x, int64_t d) +{ + unsigned char buf[9]; + buf[0] = 0xd3; _msgpack_store64(&buf[1], d); + msgpack_pack_append_buffer(x, buf, 9); +} + +#undef msgpack_pack_inline_func_fixint +#endif + + +msgpack_pack_inline_func(_uint8)(msgpack_pack_user x, uint8_t d) +{ + msgpack_pack_real_uint8(x, d); +} + +msgpack_pack_inline_func(_uint16)(msgpack_pack_user x, uint16_t d) +{ + msgpack_pack_real_uint16(x, d); +} + +msgpack_pack_inline_func(_uint32)(msgpack_pack_user x, uint32_t d) +{ + msgpack_pack_real_uint32(x, d); +} + +msgpack_pack_inline_func(_uint64)(msgpack_pack_user x, uint64_t d) +{ + msgpack_pack_real_uint64(x, d); +} + +msgpack_pack_inline_func(_int8)(msgpack_pack_user x, int8_t d) +{ + msgpack_pack_real_int8(x, d); +} + +msgpack_pack_inline_func(_int16)(msgpack_pack_user x, int16_t d) +{ + msgpack_pack_real_int16(x, d); +} + +msgpack_pack_inline_func(_int32)(msgpack_pack_user x, int32_t d) +{ + msgpack_pack_real_int32(x, d); +} + +msgpack_pack_inline_func(_int64)(msgpack_pack_user x, int64_t d) +{ + msgpack_pack_real_int64(x, d); +} + +msgpack_pack_inline_func(_char)(msgpack_pack_user x, char d) +{ +#if defined(CHAR_MIN) +#if CHAR_MIN < 0 + msgpack_pack_real_int8(x, d); +#else + msgpack_pack_real_uint8(x, d); +#endif +#else +#error CHAR_MIN is not defined +#endif +} + +msgpack_pack_inline_func(_signed_char)(msgpack_pack_user x, signed char d) +{ + msgpack_pack_real_int8(x, d); +} + +msgpack_pack_inline_func(_unsigned_char)(msgpack_pack_user x, unsigned char d) +{ + msgpack_pack_real_uint8(x, d); +} + +#ifdef msgpack_pack_inline_func_cint + +msgpack_pack_inline_func_cint(_short)(msgpack_pack_user x, short d) +{ +#if defined(SIZEOF_SHORT) +#if SIZEOF_SHORT == 2 + msgpack_pack_real_int16(x, d); +#elif SIZEOF_SHORT == 4 + msgpack_pack_real_int32(x, d); +#else + msgpack_pack_real_int64(x, d); +#endif + +#elif defined(SHRT_MAX) +#if SHRT_MAX == 0x7fff + msgpack_pack_real_int16(x, d); +#elif SHRT_MAX == 0x7fffffff + msgpack_pack_real_int32(x, d); +#else + msgpack_pack_real_int64(x, d); +#endif + +#else +if(sizeof(short) == 2) { + msgpack_pack_real_int16(x, d); +} else if(sizeof(short) == 4) { + msgpack_pack_real_int32(x, d); +} else { + msgpack_pack_real_int64(x, d); +} +#endif +} + +msgpack_pack_inline_func_cint(_int)(msgpack_pack_user x, int d) +{ +#if defined(SIZEOF_INT) +#if SIZEOF_INT == 2 + msgpack_pack_real_int16(x, d); +#elif SIZEOF_INT == 4 + msgpack_pack_real_int32(x, d); +#else + msgpack_pack_real_int64(x, d); +#endif + +#elif defined(INT_MAX) +#if INT_MAX == 0x7fff + msgpack_pack_real_int16(x, d); +#elif INT_MAX == 0x7fffffff + msgpack_pack_real_int32(x, d); +#else + msgpack_pack_real_int64(x, d); +#endif + +#else +if(sizeof(int) == 2) { + msgpack_pack_real_int16(x, d); +} else if(sizeof(int) == 4) { + msgpack_pack_real_int32(x, d); +} else { + msgpack_pack_real_int64(x, d); +} +#endif +} + +msgpack_pack_inline_func_cint(_long)(msgpack_pack_user x, long d) +{ +#if defined(SIZEOF_LONG) +#if SIZEOF_LONG == 2 + msgpack_pack_real_int16(x, d); +#elif SIZEOF_LONG == 4 + msgpack_pack_real_int32(x, d); +#else + msgpack_pack_real_int64(x, d); +#endif + +#elif defined(LONG_MAX) +#if LONG_MAX == 0x7fffL + msgpack_pack_real_int16(x, d); +#elif LONG_MAX == 0x7fffffffL + msgpack_pack_real_int32(x, d); +#else + msgpack_pack_real_int64(x, d); +#endif + +#else +if(sizeof(long) == 2) { + msgpack_pack_real_int16(x, d); +} else if(sizeof(long) == 4) { + msgpack_pack_real_int32(x, d); +} else { + msgpack_pack_real_int64(x, d); +} +#endif +} + +msgpack_pack_inline_func_cint(_long_long)(msgpack_pack_user x, long long d) +{ +#if defined(SIZEOF_LONG_LONG) +#if SIZEOF_LONG_LONG == 2 + msgpack_pack_real_int16(x, d); +#elif SIZEOF_LONG_LONG == 4 + msgpack_pack_real_int32(x, d); +#else + msgpack_pack_real_int64(x, d); +#endif + +#elif defined(LLONG_MAX) +#if LLONG_MAX == 0x7fffL + msgpack_pack_real_int16(x, d); +#elif LLONG_MAX == 0x7fffffffL + msgpack_pack_real_int32(x, d); +#else + msgpack_pack_real_int64(x, d); +#endif + +#else +if(sizeof(long long) == 2) { + msgpack_pack_real_int16(x, d); +} else if(sizeof(long long) == 4) { + msgpack_pack_real_int32(x, d); +} else { + msgpack_pack_real_int64(x, d); +} +#endif +} + +msgpack_pack_inline_func_cint(_unsigned_short)(msgpack_pack_user x, unsigned short d) +{ +#if defined(SIZEOF_SHORT) +#if SIZEOF_SHORT == 2 + msgpack_pack_real_uint16(x, d); +#elif SIZEOF_SHORT == 4 + msgpack_pack_real_uint32(x, d); +#else + msgpack_pack_real_uint64(x, d); +#endif + +#elif defined(USHRT_MAX) +#if USHRT_MAX == 0xffffU + msgpack_pack_real_uint16(x, d); +#elif USHRT_MAX == 0xffffffffU + msgpack_pack_real_uint32(x, d); +#else + msgpack_pack_real_uint64(x, d); +#endif + +#else +if(sizeof(unsigned short) == 2) { + msgpack_pack_real_uint16(x, d); +} else if(sizeof(unsigned short) == 4) { + msgpack_pack_real_uint32(x, d); +} else { + msgpack_pack_real_uint64(x, d); +} +#endif +} + +msgpack_pack_inline_func_cint(_unsigned_int)(msgpack_pack_user x, unsigned int d) +{ +#if defined(SIZEOF_INT) +#if SIZEOF_INT == 2 + msgpack_pack_real_uint16(x, d); +#elif SIZEOF_INT == 4 + msgpack_pack_real_uint32(x, d); +#else + msgpack_pack_real_uint64(x, d); +#endif + +#elif defined(UINT_MAX) +#if UINT_MAX == 0xffffU + msgpack_pack_real_uint16(x, d); +#elif UINT_MAX == 0xffffffffU + msgpack_pack_real_uint32(x, d); +#else + msgpack_pack_real_uint64(x, d); +#endif + +#else +if(sizeof(unsigned int) == 2) { + msgpack_pack_real_uint16(x, d); +} else if(sizeof(unsigned int) == 4) { + msgpack_pack_real_uint32(x, d); +} else { + msgpack_pack_real_uint64(x, d); +} +#endif +} + +msgpack_pack_inline_func_cint(_unsigned_long)(msgpack_pack_user x, unsigned long d) +{ +#if defined(SIZEOF_LONG) +#if SIZEOF_LONG == 2 + msgpack_pack_real_uint16(x, d); +#elif SIZEOF_LONG == 4 + msgpack_pack_real_uint32(x, d); +#else + msgpack_pack_real_uint64(x, d); +#endif + +#elif defined(ULONG_MAX) +#if ULONG_MAX == 0xffffUL + msgpack_pack_real_uint16(x, d); +#elif ULONG_MAX == 0xffffffffUL + msgpack_pack_real_uint32(x, d); +#else + msgpack_pack_real_uint64(x, d); +#endif + +#else +if(sizeof(unsigned long) == 2) { + msgpack_pack_real_uint16(x, d); +} else if(sizeof(unsigned long) == 4) { + msgpack_pack_real_uint32(x, d); +} else { + msgpack_pack_real_uint64(x, d); +} +#endif +} + +msgpack_pack_inline_func_cint(_unsigned_long_long)(msgpack_pack_user x, unsigned long long d) +{ +#if defined(SIZEOF_LONG_LONG) +#if SIZEOF_LONG_LONG == 2 + msgpack_pack_real_uint16(x, d); +#elif SIZEOF_LONG_LONG == 4 + msgpack_pack_real_uint32(x, d); +#else + msgpack_pack_real_uint64(x, d); +#endif + +#elif defined(ULLONG_MAX) +#if ULLONG_MAX == 0xffffUL + msgpack_pack_real_uint16(x, d); +#elif ULLONG_MAX == 0xffffffffUL + msgpack_pack_real_uint32(x, d); +#else + msgpack_pack_real_uint64(x, d); +#endif + +#else +if(sizeof(unsigned long long) == 2) { + msgpack_pack_real_uint16(x, d); +} else if(sizeof(unsigned long long) == 4) { + msgpack_pack_real_uint32(x, d); +} else { + msgpack_pack_real_uint64(x, d); +} +#endif +} + +#undef msgpack_pack_inline_func_cint +#endif + + + +/* + * Float + */ + +msgpack_pack_inline_func(_float)(msgpack_pack_user x, float d) +{ + unsigned char buf[5]; + union { float f; uint32_t i; } mem; + mem.f = d; + buf[0] = 0xca; _msgpack_store32(&buf[1], mem.i); + msgpack_pack_append_buffer(x, buf, 5); +} + +msgpack_pack_inline_func(_double)(msgpack_pack_user x, double d) +{ + unsigned char buf[9]; + union { double f; uint64_t i; } mem; + mem.f = d; + buf[0] = 0xcb; +#if defined(TARGET_OS_IPHONE) + // ok +#elif defined(__arm__) && !(__ARM_EABI__) // arm-oabi + // https://github.com/msgpack/msgpack-perl/pull/1 + mem.i = (mem.i & 0xFFFFFFFFUL) << 32UL | (mem.i >> 32UL); +#endif + _msgpack_store64(&buf[1], mem.i); + msgpack_pack_append_buffer(x, buf, 9); +} + + +/* + * Nil + */ + +msgpack_pack_inline_func(_nil)(msgpack_pack_user x) +{ + static const unsigned char d = 0xc0; + msgpack_pack_append_buffer(x, &d, 1); +} + + +/* + * Boolean + */ + +msgpack_pack_inline_func(_true)(msgpack_pack_user x) +{ + static const unsigned char d = 0xc3; + msgpack_pack_append_buffer(x, &d, 1); +} + +msgpack_pack_inline_func(_false)(msgpack_pack_user x) +{ + static const unsigned char d = 0xc2; + msgpack_pack_append_buffer(x, &d, 1); +} + + +/* + * Array + */ + +msgpack_pack_inline_func(_array)(msgpack_pack_user x, size_t n) +{ + if(n < 16) { + unsigned char d = 0x90 | (uint8_t)n; + msgpack_pack_append_buffer(x, &d, 1); + } else if(n < 65536) { + unsigned char buf[3]; + buf[0] = 0xdc; _msgpack_store16(&buf[1], (uint16_t)n); + msgpack_pack_append_buffer(x, buf, 3); + } else { + unsigned char buf[5]; + buf[0] = 0xdd; _msgpack_store32(&buf[1], (uint32_t)n); + msgpack_pack_append_buffer(x, buf, 5); + } +} + + +/* + * Map + */ + +msgpack_pack_inline_func(_map)(msgpack_pack_user x, size_t n) +{ + if(n < 16) { + unsigned char d = 0x80 | (uint8_t)n; + msgpack_pack_append_buffer(x, &TAKE8_8(d), 1); + } else if(n < 65536) { + unsigned char buf[3]; + buf[0] = 0xde; _msgpack_store16(&buf[1], (uint16_t)n); + msgpack_pack_append_buffer(x, buf, 3); + } else { + unsigned char buf[5]; + buf[0] = 0xdf; _msgpack_store32(&buf[1], (uint32_t)n); + msgpack_pack_append_buffer(x, buf, 5); + } +} + + +/* + * Str + */ + +msgpack_pack_inline_func(_str)(msgpack_pack_user x, size_t l) +{ + if(l < 32) { + unsigned char d = 0xa0 | (uint8_t)l; + msgpack_pack_append_buffer(x, &TAKE8_8(d), 1); + } else if(l < 256) { + unsigned char buf[2]; + buf[0] = 0xd9; buf[1] = (uint8_t)l; + msgpack_pack_append_buffer(x, buf, 2); + } else if(l < 65536) { + unsigned char buf[3]; + buf[0] = 0xda; _msgpack_store16(&buf[1], (uint16_t)l); + msgpack_pack_append_buffer(x, buf, 3); + } else { + unsigned char buf[5]; + buf[0] = 0xdb; _msgpack_store32(&buf[1], (uint32_t)l); + msgpack_pack_append_buffer(x, buf, 5); + } +} + +msgpack_pack_inline_func(_str_body)(msgpack_pack_user x, const void* b, size_t l) +{ + msgpack_pack_append_buffer(x, (const unsigned char*)b, l); +} + +/* + * Raw (V4) + */ + +msgpack_pack_inline_func(_v4raw)(msgpack_pack_user x, size_t l) +{ + if(l < 32) { + unsigned char d = 0xa0 | (uint8_t)l; + msgpack_pack_append_buffer(x, &TAKE8_8(d), 1); + } else if(l < 65536) { + unsigned char buf[3]; + buf[0] = 0xda; _msgpack_store16(&buf[1], (uint16_t)l); + msgpack_pack_append_buffer(x, buf, 3); + } else { + unsigned char buf[5]; + buf[0] = 0xdb; _msgpack_store32(&buf[1], (uint32_t)l); + msgpack_pack_append_buffer(x, buf, 5); + } +} + +msgpack_pack_inline_func(_v4raw_body)(msgpack_pack_user x, const void* b, size_t l) +{ + msgpack_pack_append_buffer(x, (const unsigned char*)b, l); +} + +/* + * Bin + */ + +msgpack_pack_inline_func(_bin)(msgpack_pack_user x, size_t l) +{ + if(l < 256) { + unsigned char buf[2]; + buf[0] = 0xc4; buf[1] = (uint8_t)l; + msgpack_pack_append_buffer(x, buf, 2); + } else if(l < 65536) { + unsigned char buf[3]; + buf[0] = 0xc5; _msgpack_store16(&buf[1], (uint16_t)l); + msgpack_pack_append_buffer(x, buf, 3); + } else { + unsigned char buf[5]; + buf[0] = 0xc6; _msgpack_store32(&buf[1], (uint32_t)l); + msgpack_pack_append_buffer(x, buf, 5); + } +} + +msgpack_pack_inline_func(_bin_body)(msgpack_pack_user x, const void* b, size_t l) +{ + msgpack_pack_append_buffer(x, (const unsigned char*)b, l); +} + +/* + * Ext + */ + +msgpack_pack_inline_func(_ext)(msgpack_pack_user x, size_t l, int8_t type) +{ + switch(l) { + case 1: { + unsigned char buf[2]; + buf[0] = 0xd4; + buf[1] = (unsigned char)type; + msgpack_pack_append_buffer(x, buf, 2); + } break; + case 2: { + unsigned char buf[2]; + buf[0] = 0xd5; + buf[1] = (unsigned char)type; + msgpack_pack_append_buffer(x, buf, 2); + } break; + case 4: { + unsigned char buf[2]; + buf[0] = 0xd6; + buf[1] = (unsigned char)type; + msgpack_pack_append_buffer(x, buf, 2); + } break; + case 8: { + unsigned char buf[2]; + buf[0] = 0xd7; + buf[1] = (unsigned char)type; + msgpack_pack_append_buffer(x, buf, 2); + } break; + case 16: { + unsigned char buf[2]; + buf[0] = 0xd8; + buf[1] = (unsigned char)type; + msgpack_pack_append_buffer(x, buf, 2); + } break; + default: + if(l < 256) { + unsigned char buf[3]; + buf[0] = 0xc7; + buf[1] = (unsigned char)l; + buf[2] = (unsigned char)type; + msgpack_pack_append_buffer(x, buf, 3); + } else if(l < 65536) { + unsigned char buf[4]; + buf[0] = 0xc8; + _msgpack_store16(&buf[1], l); + buf[3] = (unsigned char)type; + msgpack_pack_append_buffer(x, buf, 4); + } else { + unsigned char buf[6]; + buf[0] = 0xc9; + _msgpack_store32(&buf[1], l); + buf[5] = (unsigned char)type; + msgpack_pack_append_buffer(x, buf, 6); + } + break; + } +} + +msgpack_pack_inline_func(_ext_body)(msgpack_pack_user x, const void* b, size_t l) +{ + msgpack_pack_append_buffer(x, (const unsigned char*)b, l); +} + +msgpack_pack_inline_func(_timestamp)(msgpack_pack_user x, const msgpack_timestamp* d) +{ + if ((((int64_t)d->tv_sec) >> 34) == 0) { + uint64_t data64 = ((uint64_t) d->tv_nsec << 34) | (uint64_t)d->tv_sec; + if ((data64 & 0xffffffff00000000L) == 0) { + // timestamp 32 + char buf[4]; + uint32_t data32 = (uint32_t)data64; + msgpack_pack_ext(x, 4, -1); + _msgpack_store32(buf, data32); + msgpack_pack_append_buffer(x, buf, 4); + } else { + // timestamp 64 + char buf[8]; + msgpack_pack_ext(x, 8, -1); + _msgpack_store64(buf, data64); + msgpack_pack_append_buffer(x, buf, 8); + } + } else { + // timestamp 96 + char buf[12]; + _msgpack_store32(&buf[0], d->tv_nsec); + _msgpack_store64(&buf[4], d->tv_sec); + msgpack_pack_ext(x, 12, -1); + msgpack_pack_append_buffer(x, buf, 12); + } +} + +#undef msgpack_pack_inline_func +#undef msgpack_pack_user +#undef msgpack_pack_append_buffer + +#undef TAKE8_8 +#undef TAKE8_16 +#undef TAKE8_32 +#undef TAKE8_64 + +#undef msgpack_pack_real_uint8 +#undef msgpack_pack_real_uint16 +#undef msgpack_pack_real_uint32 +#undef msgpack_pack_real_uint64 +#undef msgpack_pack_real_int8 +#undef msgpack_pack_real_int16 +#undef msgpack_pack_real_int32 +#undef msgpack_pack_real_int64 + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/sbuffer.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/sbuffer.h new file mode 100644 index 0000000..572d8f2 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/sbuffer.h @@ -0,0 +1,115 @@ +/* + * MessagePack for C simple buffer implementation + * + * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_SBUFFER_H +#define MSGPACK_SBUFFER_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup msgpack_sbuffer Simple buffer + * @ingroup msgpack_buffer + * @{ + */ + +typedef struct msgpack_sbuffer { + size_t size; + char* data; + size_t alloc; +} msgpack_sbuffer; + +static inline void msgpack_sbuffer_init(msgpack_sbuffer* sbuf) +{ + memset(sbuf, 0, sizeof(msgpack_sbuffer)); +} + +static inline void msgpack_sbuffer_destroy(msgpack_sbuffer* sbuf) +{ + free(sbuf->data); +} + +static inline msgpack_sbuffer* msgpack_sbuffer_new(void) +{ + return (msgpack_sbuffer*)calloc(1, sizeof(msgpack_sbuffer)); +} + +static inline void msgpack_sbuffer_free(msgpack_sbuffer* sbuf) +{ + if(sbuf == NULL) { return; } + msgpack_sbuffer_destroy(sbuf); + free(sbuf); +} + +#ifndef MSGPACK_SBUFFER_INIT_SIZE +#define MSGPACK_SBUFFER_INIT_SIZE 8192 +#endif + +static inline int msgpack_sbuffer_write(void* data, const char* buf, size_t len) +{ + msgpack_sbuffer* sbuf = (msgpack_sbuffer*)data; + + assert(buf || len == 0); + if(!buf) return 0; + + if(sbuf->alloc - sbuf->size < len) { + void* tmp; + size_t nsize = (sbuf->alloc) ? + sbuf->alloc * 2 : MSGPACK_SBUFFER_INIT_SIZE; + + while(nsize < sbuf->size + len) { + size_t tmp_nsize = nsize * 2; + if (tmp_nsize <= nsize) { + nsize = sbuf->size + len; + break; + } + nsize = tmp_nsize; + } + + tmp = realloc(sbuf->data, nsize); + if(!tmp) { return -1; } + + sbuf->data = (char*)tmp; + sbuf->alloc = nsize; + } + + memcpy(sbuf->data + sbuf->size, buf, len); + sbuf->size += len; + + return 0; +} + +static inline char* msgpack_sbuffer_release(msgpack_sbuffer* sbuf) +{ + char* tmp = sbuf->data; + sbuf->size = 0; + sbuf->data = NULL; + sbuf->alloc = 0; + return tmp; +} + +static inline void msgpack_sbuffer_clear(msgpack_sbuffer* sbuf) +{ + sbuf->size = 0; +} + +/** @} */ + + +#ifdef __cplusplus +} +#endif + +#endif /* msgpack/sbuffer.h */ diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/sysdep.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/sysdep.h new file mode 100644 index 0000000..8f6f461 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/sysdep.h @@ -0,0 +1,228 @@ +/* + * MessagePack system dependencies + * + * Copyright (C) 2008-2010 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_SYSDEP_H +#define MSGPACK_SYSDEP_H + +#include +#include + +#ifndef MSGPACK_ENDIAN_BIG_BYTE +#define MSGPACK_ENDIAN_BIG_BYTE 0 +#endif +#ifndef MSGPACK_ENDIAN_LITTLE_BYTE +#define MSGPACK_ENDIAN_LITTLE_BYTE 1 +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +# define snprintf(buf, len, format,...) _snprintf_s(buf, len, _TRUNCATE, format, __VA_ARGS__) +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1600 + typedef signed __int8 int8_t; + typedef unsigned __int8 uint8_t; + typedef signed __int16 int16_t; + typedef unsigned __int16 uint16_t; + typedef signed __int32 int32_t; + typedef unsigned __int32 uint32_t; + typedef signed __int64 int64_t; + typedef unsigned __int64 uint64_t; +# if defined(_WIN64) + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +# else + typedef signed __int32 intptr_t; + typedef unsigned __int32 uintptr_t; +# endif +#elif defined(_MSC_VER) // && _MSC_VER >= 1600 +# include +#else +# include +# include +#endif + +#if !defined(MSGPACK_DLLEXPORT) +#if defined(_MSC_VER) +# define MSGPACK_DLLEXPORT __declspec(dllexport) +#else /* _MSC_VER */ +# define MSGPACK_DLLEXPORT +#endif /* _MSC_VER */ +#endif + +#ifdef _WIN32 +# if defined(_KERNEL_MODE) +# define _msgpack_atomic_counter_header +# else +# define _msgpack_atomic_counter_header +# if !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +# endif /* WIN32_LEAN_AND_MEAN */ +# endif + typedef long _msgpack_atomic_counter_t; +#if defined(_AMD64_) || defined(_M_X64) || defined(_M_ARM64) +# define _msgpack_sync_decr_and_fetch(ptr) _InterlockedDecrement(ptr) +# define _msgpack_sync_incr_and_fetch(ptr) _InterlockedIncrement(ptr) +#else +# define _msgpack_sync_decr_and_fetch(ptr) InterlockedDecrement(ptr) +# define _msgpack_sync_incr_and_fetch(ptr) InterlockedIncrement(ptr) +#endif +#elif defined(__GNUC__) && ((__GNUC__*10 + __GNUC_MINOR__) < 41) + +# if defined(__cplusplus) +# define _msgpack_atomic_counter_header "msgpack/gcc_atomic.hpp" +# else +# define _msgpack_atomic_counter_header "msgpack/gcc_atomic.h" +# endif + +#else + typedef unsigned int _msgpack_atomic_counter_t; +# define _msgpack_sync_decr_and_fetch(ptr) __sync_sub_and_fetch(ptr, 1) +# define _msgpack_sync_incr_and_fetch(ptr) __sync_add_and_fetch(ptr, 1) +#endif + +#ifdef _WIN32 + +# ifdef __cplusplus + /* numeric_limits::min,max */ +# ifdef max +# undef max +# endif +# ifdef min +# undef min +# endif +# endif + +#elif defined(unix) || defined(__unix) || defined(__APPLE__) || defined(__OpenBSD__) + +#include /* __BYTE_ORDER */ +# if defined(linux) +# include +# endif + +#endif + +#if !defined(MSGPACK_ENDIAN_LITTLE_BYTE) && !defined(MSGPACK_ENDIAN_BIG_BYTE) +#include +#endif // !defined(MSGPACK_ENDIAN_LITTLE_BYTE) && !defined(MSGPACK_ENDIAN_BIG_BYTE) + +#if MSGPACK_ENDIAN_LITTLE_BYTE + +# if defined(unix) || defined(__unix) || defined(__APPLE__) || defined(__OpenBSD__) +# define _msgpack_be16(x) ntohs((uint16_t)x) +# else +# if defined(ntohs) +# define _msgpack_be16(x) ntohs(x) +# elif defined(_byteswap_ushort) || (defined(_MSC_VER) && _MSC_VER >= 1400) +# define _msgpack_be16(x) ((uint16_t)_byteswap_ushort((unsigned short)x)) +# else +# define _msgpack_be16(x) ( \ + ((((uint16_t)x) << 8) ) | \ + ((((uint16_t)x) >> 8) ) ) +# endif +# endif + +# if defined(unix) || defined(__unix) || defined(__APPLE__) || defined(__OpenBSD__) +# define _msgpack_be32(x) ntohl((uint32_t)x) +# else +# if defined(ntohl) +# define _msgpack_be32(x) ntohl(x) +# elif defined(_byteswap_ulong) || (defined(_MSC_VER) && _MSC_VER >= 1400) +# define _msgpack_be32(x) ((uint32_t)_byteswap_ulong((unsigned long)x)) +# else +# define _msgpack_be32(x) \ + ( ((((uint32_t)x) << 24) ) | \ + ((((uint32_t)x) << 8) & 0x00ff0000U ) | \ + ((((uint32_t)x) >> 8) & 0x0000ff00U ) | \ + ((((uint32_t)x) >> 24) ) ) +# endif +# endif + +# if defined(_byteswap_uint64) || (defined(_MSC_VER) && _MSC_VER >= 1400) +# define _msgpack_be64(x) (_byteswap_uint64(x)) +# elif defined(bswap_64) +# define _msgpack_be64(x) bswap_64(x) +# elif defined(__DARWIN_OSSwapInt64) +# define _msgpack_be64(x) __DARWIN_OSSwapInt64(x) +# else +# define _msgpack_be64(x) \ + ( ((((uint64_t)x) << 56) ) | \ + ((((uint64_t)x) << 40) & 0x00ff000000000000ULL ) | \ + ((((uint64_t)x) << 24) & 0x0000ff0000000000ULL ) | \ + ((((uint64_t)x) << 8) & 0x000000ff00000000ULL ) | \ + ((((uint64_t)x) >> 8) & 0x00000000ff000000ULL ) | \ + ((((uint64_t)x) >> 24) & 0x0000000000ff0000ULL ) | \ + ((((uint64_t)x) >> 40) & 0x000000000000ff00ULL ) | \ + ((((uint64_t)x) >> 56) ) ) +# endif + +#elif MSGPACK_ENDIAN_BIG_BYTE + +# define _msgpack_be16(x) (x) +# define _msgpack_be32(x) (x) +# define _msgpack_be64(x) (x) + +#else +# error msgpack-c supports only big endian and little endian +#endif /* MSGPACK_ENDIAN_LITTLE_BYTE */ + +#define _msgpack_load16(cast, from, to) do { \ + memcpy((cast*)(to), (from), sizeof(cast)); \ + *(to) = (cast)_msgpack_be16(*(to)); \ + } while (0); + +#define _msgpack_load32(cast, from, to) do { \ + memcpy((cast*)(to), (from), sizeof(cast)); \ + *(to) = (cast)_msgpack_be32(*(to)); \ + } while (0); +#define _msgpack_load64(cast, from, to) do { \ + memcpy((cast*)(to), (from), sizeof(cast)); \ + *(to) = (cast)_msgpack_be64(*(to)); \ + } while (0); + +#define _msgpack_store16(to, num) \ + do { uint16_t val = _msgpack_be16(num); memcpy(to, &val, 2); } while(0) +#define _msgpack_store32(to, num) \ + do { uint32_t val = _msgpack_be32(num); memcpy(to, &val, 4); } while(0) +#define _msgpack_store64(to, num) \ + do { uint64_t val = _msgpack_be64(num); memcpy(to, &val, 8); } while(0) + +/* +#define _msgpack_load16(cast, from) \ + ({ cast val; memcpy(&val, (char*)from, 2); _msgpack_be16(val); }) +#define _msgpack_load32(cast, from) \ + ({ cast val; memcpy(&val, (char*)from, 4); _msgpack_be32(val); }) +#define _msgpack_load64(cast, from) \ + ({ cast val; memcpy(&val, (char*)from, 8); _msgpack_be64(val); }) +*/ + + +#if !defined(__cplusplus) && defined(_MSC_VER) +# if !defined(_KERNEL_MODE) +# if !defined(FALSE) +# define FALSE (0) +# endif +# if !defined(TRUE) +# define TRUE (!FALSE) +# endif +# endif +# if _MSC_VER >= 1800 +# include +# else +# define bool int +# define true TRUE +# define false FALSE +# endif +# define inline __inline +#endif + +#ifdef __APPLE__ +# include +#endif + +#endif /* msgpack/sysdep.h */ diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/timestamp.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/timestamp.h new file mode 100644 index 0000000..ac1bb7c --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/timestamp.h @@ -0,0 +1,58 @@ +/* + * MessagePack for C TimeStamp + * + * Copyright (C) 2018 KONDO Takatoshi + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_TIMESTAMP_H +#define MSGPACK_TIMESTAMP_H + +#include "object.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct msgpack_timestamp { + int64_t tv_sec; + uint32_t tv_nsec; +} msgpack_timestamp; + +static inline bool msgpack_object_to_timestamp(const msgpack_object* obj, msgpack_timestamp* ts) { + if (obj->type != MSGPACK_OBJECT_EXT) return false; + if (obj->via.ext.type != -1) return false; + switch (obj->via.ext.size) { + case 4: + ts->tv_nsec = 0; + { + uint32_t v; + _msgpack_load32(uint32_t, obj->via.ext.ptr, &v); + ts->tv_sec = v; + } + return true; + case 8: { + uint64_t value; + _msgpack_load64(uint64_t, obj->via.ext.ptr, &value); + ts->tv_nsec = (uint32_t)(value >> 34); + ts->tv_sec = value & 0x00000003ffffffffLL; + return true; + } + case 12: + _msgpack_load32(uint32_t, obj->via.ext.ptr, &ts->tv_nsec); + _msgpack_load64(int64_t, obj->via.ext.ptr + 4, &ts->tv_sec); + return true; + default: + return false; + } +} + + +#ifdef __cplusplus +} +#endif + +#endif /* msgpack/timestamp.h */ diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/unpack.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/unpack.h new file mode 100644 index 0000000..036d575 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/unpack.h @@ -0,0 +1,281 @@ +/* + * MessagePack for C unpacking routine + * + * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_UNPACKER_H +#define MSGPACK_UNPACKER_H + +#include "zone.h" +#include "object.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup msgpack_unpack Deserializer + * @ingroup msgpack + * @{ + */ + +typedef struct msgpack_unpacked { + msgpack_zone* zone; + msgpack_object data; +} msgpack_unpacked; + +typedef enum { + MSGPACK_UNPACK_SUCCESS = 2, + MSGPACK_UNPACK_EXTRA_BYTES = 1, + MSGPACK_UNPACK_CONTINUE = 0, + MSGPACK_UNPACK_PARSE_ERROR = -1, + MSGPACK_UNPACK_NOMEM_ERROR = -2 +} msgpack_unpack_return; + + +MSGPACK_DLLEXPORT +msgpack_unpack_return +msgpack_unpack_next(msgpack_unpacked* result, + const char* data, size_t len, size_t* off); + +/** @} */ + + +/** + * @defgroup msgpack_unpacker Streaming deserializer + * @ingroup msgpack + * @{ + */ + +typedef struct msgpack_unpacker { + char* buffer; + size_t used; + size_t free; + size_t off; + size_t parsed; + msgpack_zone* z; + size_t initial_buffer_size; + void* ctx; +} msgpack_unpacker; + + +#ifndef MSGPACK_UNPACKER_INIT_BUFFER_SIZE +#define MSGPACK_UNPACKER_INIT_BUFFER_SIZE (64*1024) +#endif + +/** + * Initializes a streaming deserializer. + * The initialized deserializer must be destroyed by msgpack_unpacker_destroy(msgpack_unpacker*). + */ +MSGPACK_DLLEXPORT +bool msgpack_unpacker_init(msgpack_unpacker* mpac, size_t initial_buffer_size); + +/** + * Destroys a streaming deserializer initialized by msgpack_unpacker_init(msgpack_unpacker*, size_t). + */ +MSGPACK_DLLEXPORT +void msgpack_unpacker_destroy(msgpack_unpacker* mpac); + + +/** + * Creates a streaming deserializer. + * The created deserializer must be destroyed by msgpack_unpacker_free(msgpack_unpacker*). + */ +MSGPACK_DLLEXPORT +msgpack_unpacker* msgpack_unpacker_new(size_t initial_buffer_size); + +/** + * Frees a streaming deserializer created by msgpack_unpacker_new(size_t). + */ +MSGPACK_DLLEXPORT +void msgpack_unpacker_free(msgpack_unpacker* mpac); + + +#ifndef MSGPACK_UNPACKER_RESERVE_SIZE +#define MSGPACK_UNPACKER_RESERVE_SIZE (32*1024) +#endif + +/** + * Reserves free space of the internal buffer. + * Use this function to fill the internal buffer with + * msgpack_unpacker_buffer(msgpack_unpacker*), + * msgpack_unpacker_buffer_capacity(const msgpack_unpacker*) and + * msgpack_unpacker_buffer_consumed(msgpack_unpacker*). + */ +static inline bool msgpack_unpacker_reserve_buffer(msgpack_unpacker* mpac, size_t size); + +/** + * Gets pointer to the free space of the internal buffer. + * Use this function to fill the internal buffer with + * msgpack_unpacker_reserve_buffer(msgpack_unpacker*, size_t), + * msgpack_unpacker_buffer_capacity(const msgpack_unpacker*) and + * msgpack_unpacker_buffer_consumed(msgpack_unpacker*). + */ +static inline char* msgpack_unpacker_buffer(msgpack_unpacker* mpac); + +/** + * Gets size of the free space of the internal buffer. + * Use this function to fill the internal buffer with + * msgpack_unpacker_reserve_buffer(msgpack_unpacker*, size_t), + * msgpack_unpacker_buffer(const msgpack_unpacker*) and + * msgpack_unpacker_buffer_consumed(msgpack_unpacker*). + */ +static inline size_t msgpack_unpacker_buffer_capacity(const msgpack_unpacker* mpac); + +/** + * Notifies the deserializer that the internal buffer filled. + * Use this function to fill the internal buffer with + * msgpack_unpacker_reserve_buffer(msgpack_unpacker*, size_t), + * msgpack_unpacker_buffer(msgpack_unpacker*) and + * msgpack_unpacker_buffer_capacity(const msgpack_unpacker*). + */ +static inline void msgpack_unpacker_buffer_consumed(msgpack_unpacker* mpac, size_t size); + + +/** + * Deserializes one object. + * Returns true if it successes. Otherwise false is returned. + * @param pac pointer to an initialized msgpack_unpacked object. + */ +MSGPACK_DLLEXPORT +msgpack_unpack_return msgpack_unpacker_next(msgpack_unpacker* mpac, msgpack_unpacked* pac); + +/** + * Deserializes one object and set the number of parsed bytes involved. + * Returns true if it successes. Otherwise false is returned. + * @param mpac pointer to an initialized msgpack_unpacker object. + * @param result pointer to an initialized msgpack_unpacked object. + * @param p_bytes pointer to variable that will be set with the number of parsed bytes. + */ +MSGPACK_DLLEXPORT +msgpack_unpack_return msgpack_unpacker_next_with_size(msgpack_unpacker* mpac, + msgpack_unpacked* result, + size_t *p_bytes); + +/** + * Initializes a msgpack_unpacked object. + * The initialized object must be destroyed by msgpack_unpacked_destroy(msgpack_unpacker*). + * Use the object with msgpack_unpacker_next(msgpack_unpacker*, msgpack_unpacked*) or + * msgpack_unpack_next(msgpack_unpacked*, const char*, size_t, size_t*). + */ +static inline void msgpack_unpacked_init(msgpack_unpacked* result); + +/** + * Destroys a streaming deserializer initialized by msgpack_unpacked(). + */ +static inline void msgpack_unpacked_destroy(msgpack_unpacked* result); + +/** + * Releases the memory zone from msgpack_unpacked object. + * The released zone must be freed by msgpack_zone_free(msgpack_zone*). + */ +static inline msgpack_zone* msgpack_unpacked_release_zone(msgpack_unpacked* result); + + +MSGPACK_DLLEXPORT +int msgpack_unpacker_execute(msgpack_unpacker* mpac); + +MSGPACK_DLLEXPORT +msgpack_object msgpack_unpacker_data(msgpack_unpacker* mpac); + +MSGPACK_DLLEXPORT +msgpack_zone* msgpack_unpacker_release_zone(msgpack_unpacker* mpac); + +MSGPACK_DLLEXPORT +void msgpack_unpacker_reset_zone(msgpack_unpacker* mpac); + +MSGPACK_DLLEXPORT +void msgpack_unpacker_reset(msgpack_unpacker* mpac); + +static inline size_t msgpack_unpacker_message_size(const msgpack_unpacker* mpac); + + +/** @} */ + + +// obsolete +MSGPACK_DLLEXPORT +msgpack_unpack_return +msgpack_unpack(const char* data, size_t len, size_t* off, + msgpack_zone* result_zone, msgpack_object* result); + + + + +static inline size_t msgpack_unpacker_parsed_size(const msgpack_unpacker* mpac); + +MSGPACK_DLLEXPORT +bool msgpack_unpacker_flush_zone(msgpack_unpacker* mpac); + +MSGPACK_DLLEXPORT +bool msgpack_unpacker_expand_buffer(msgpack_unpacker* mpac, size_t size); + +static inline bool msgpack_unpacker_reserve_buffer(msgpack_unpacker* mpac, size_t size) +{ + if(mpac->free >= size) { return true; } + return msgpack_unpacker_expand_buffer(mpac, size); +} + +static inline char* msgpack_unpacker_buffer(msgpack_unpacker* mpac) +{ + return mpac->buffer + mpac->used; +} + +static inline size_t msgpack_unpacker_buffer_capacity(const msgpack_unpacker* mpac) +{ + return mpac->free; +} + +static inline void msgpack_unpacker_buffer_consumed(msgpack_unpacker* mpac, size_t size) +{ + mpac->used += size; + mpac->free -= size; +} + +static inline size_t msgpack_unpacker_message_size(const msgpack_unpacker* mpac) +{ + return mpac->parsed - mpac->off + mpac->used; +} + +static inline size_t msgpack_unpacker_parsed_size(const msgpack_unpacker* mpac) +{ + return mpac->parsed; +} + + +static inline void msgpack_unpacked_init(msgpack_unpacked* result) +{ + memset(result, 0, sizeof(msgpack_unpacked)); +} + +static inline void msgpack_unpacked_destroy(msgpack_unpacked* result) +{ + if(result->zone != NULL) { + msgpack_zone_free(result->zone); + result->zone = NULL; + memset(&result->data, 0, sizeof(msgpack_object)); + } +} + +static inline msgpack_zone* msgpack_unpacked_release_zone(msgpack_unpacked* result) +{ + if(result->zone != NULL) { + msgpack_zone* z = result->zone; + result->zone = NULL; + return z; + } + return NULL; +} + + +#ifdef __cplusplus +} +#endif + +#endif /* msgpack/unpack.h */ diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/unpack_define.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/unpack_define.h new file mode 100644 index 0000000..7919466 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/unpack_define.h @@ -0,0 +1,89 @@ +/* + * MessagePack unpacking routine template + * + * Copyright (C) 2008-2010 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_UNPACK_DEFINE_H +#define MSGPACK_UNPACK_DEFINE_H + +#include "sysdep.h" +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifndef MSGPACK_EMBED_STACK_SIZE +#define MSGPACK_EMBED_STACK_SIZE 32 +#endif + + +typedef enum { + MSGPACK_CS_HEADER = 0x00, // nil + + //MSGPACK_CS_ = 0x01, + //MSGPACK_CS_ = 0x02, // false + //MSGPACK_CS_ = 0x03, // true + + MSGPACK_CS_BIN_8 = 0x04, + MSGPACK_CS_BIN_16 = 0x05, + MSGPACK_CS_BIN_32 = 0x06, + + MSGPACK_CS_EXT_8 = 0x07, + MSGPACK_CS_EXT_16 = 0x08, + MSGPACK_CS_EXT_32 = 0x09, + + MSGPACK_CS_FLOAT = 0x0a, + MSGPACK_CS_DOUBLE = 0x0b, + MSGPACK_CS_UINT_8 = 0x0c, + MSGPACK_CS_UINT_16 = 0x0d, + MSGPACK_CS_UINT_32 = 0x0e, + MSGPACK_CS_UINT_64 = 0x0f, + MSGPACK_CS_INT_8 = 0x10, + MSGPACK_CS_INT_16 = 0x11, + MSGPACK_CS_INT_32 = 0x12, + MSGPACK_CS_INT_64 = 0x13, + + MSGPACK_CS_FIXEXT_1 = 0x14, + MSGPACK_CS_FIXEXT_2 = 0x15, + MSGPACK_CS_FIXEXT_4 = 0x16, + MSGPACK_CS_FIXEXT_8 = 0x17, + MSGPACK_CS_FIXEXT_16 = 0x18, + + MSGPACK_CS_STR_8 = 0x19, // str8 + MSGPACK_CS_STR_16 = 0x1a, // str16 + MSGPACK_CS_STR_32 = 0x1b, // str32 + MSGPACK_CS_ARRAY_16 = 0x1c, + MSGPACK_CS_ARRAY_32 = 0x1d, + MSGPACK_CS_MAP_16 = 0x1e, + MSGPACK_CS_MAP_32 = 0x1f, + + //MSGPACK_ACS_BIG_INT_VALUE, + //MSGPACK_ACS_BIG_FLOAT_VALUE, + MSGPACK_ACS_STR_VALUE, + MSGPACK_ACS_BIN_VALUE, + MSGPACK_ACS_EXT_VALUE +} msgpack_unpack_state; + + +typedef enum { + MSGPACK_CT_ARRAY_ITEM, + MSGPACK_CT_MAP_KEY, + MSGPACK_CT_MAP_VALUE +} msgpack_container_type; + + +#ifdef __cplusplus +} +#endif + +#endif /* msgpack/unpack_define.h */ + diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/unpack_template.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/unpack_template.h new file mode 100644 index 0000000..de30f3c --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/unpack_template.h @@ -0,0 +1,471 @@ +/* + * MessagePack unpacking routine template + * + * Copyright (C) 2008-2010 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ + +#ifndef msgpack_unpack_func +#error msgpack_unpack_func template is not defined +#endif + +#ifndef msgpack_unpack_callback +#error msgpack_unpack_callback template is not defined +#endif + +#ifndef msgpack_unpack_struct +#error msgpack_unpack_struct template is not defined +#endif + +#ifndef msgpack_unpack_struct_decl +#define msgpack_unpack_struct_decl(name) msgpack_unpack_struct(name) +#endif + +#ifndef msgpack_unpack_object +#error msgpack_unpack_object type is not defined +#endif + +#ifndef msgpack_unpack_user +#error msgpack_unpack_user type is not defined +#endif + +#ifndef USE_CASE_RANGE +#if !defined(_MSC_VER) +#define USE_CASE_RANGE +#endif +#endif + +#if defined(_KERNEL_MODE) +#undef assert +#define assert NT_ASSERT +#endif + +msgpack_unpack_struct_decl(_stack) { + msgpack_unpack_object obj; + size_t count; + unsigned int ct; + msgpack_unpack_object map_key; +}; + +msgpack_unpack_struct_decl(_context) { + msgpack_unpack_user user; + unsigned int cs; + unsigned int trail; + unsigned int top; + /* + msgpack_unpack_struct(_stack)* stack; + unsigned int stack_size; + msgpack_unpack_struct(_stack) embed_stack[MSGPACK_EMBED_STACK_SIZE]; + */ + msgpack_unpack_struct(_stack) stack[MSGPACK_EMBED_STACK_SIZE]; +}; + + +msgpack_unpack_func(void, _init)(msgpack_unpack_struct(_context)* ctx) +{ + ctx->cs = MSGPACK_CS_HEADER; + ctx->trail = 0; + ctx->top = 0; + /* + ctx->stack = ctx->embed_stack; + ctx->stack_size = MSGPACK_EMBED_STACK_SIZE; + */ + ctx->stack[0].obj = msgpack_unpack_callback(_root)(&ctx->user); +} + +/* +msgpack_unpack_func(void, _destroy)(msgpack_unpack_struct(_context)* ctx) +{ + if(ctx->stack_size != MSGPACK_EMBED_STACK_SIZE) { + free(ctx->stack); + } +} +*/ + +msgpack_unpack_func(msgpack_unpack_object, _data)(msgpack_unpack_struct(_context)* ctx) +{ + return (ctx)->stack[0].obj; +} + + +msgpack_unpack_func(int, _execute)(msgpack_unpack_struct(_context)* ctx, const char* data, size_t len, size_t* off) +{ + assert(len >= *off); + { + const unsigned char* p = (unsigned char*)data + *off; + const unsigned char* const pe = (unsigned char*)data + len; + const void* n = NULL; + + unsigned int trail = ctx->trail; + unsigned int cs = ctx->cs; + unsigned int top = ctx->top; + msgpack_unpack_struct(_stack)* stack = ctx->stack; + /* + unsigned int stack_size = ctx->stack_size; + */ + msgpack_unpack_user* user = &ctx->user; + + msgpack_unpack_object obj; + msgpack_unpack_struct(_stack)* c = NULL; + + int ret; + +#define push_simple_value(func) \ + ret = msgpack_unpack_callback(func)(user, &obj); \ + if(ret < 0) { goto _failed; } \ + goto _push +#define push_fixed_value(func, arg) \ + ret = msgpack_unpack_callback(func)(user, arg, &obj); \ + if(ret < 0) { goto _failed; } \ + goto _push +#define push_variable_value(func, base, pos, len) \ + ret = msgpack_unpack_callback(func)(user, \ + (const char*)base, (const char*)pos, len, &obj); \ + if(ret < 0) { goto _failed; } \ + goto _push + +#define again_fixed_trail(_cs, trail_len) \ + trail = trail_len; \ + cs = _cs; \ + goto _fixed_trail_again +#define again_fixed_trail_if_zero(_cs, trail_len, ifzero) \ + trail = trail_len; \ + if(trail == 0) { goto ifzero; } \ + cs = _cs; \ + goto _fixed_trail_again + +#define start_container(func, count_, ct_) \ + if(top >= MSGPACK_EMBED_STACK_SIZE) { \ + ret = MSGPACK_UNPACK_NOMEM_ERROR; \ + goto _failed; \ + } /* FIXME */ \ + ret = msgpack_unpack_callback(func)(user, count_, &stack[top].obj); \ + if(ret < 0) { goto _failed; } \ + if((count_) == 0) { obj = stack[top].obj; goto _push; } \ + stack[top].ct = ct_; \ + stack[top].count = count_; \ + ++top; \ + goto _header_again + +#define NEXT_CS(p) \ + ((unsigned int)*p & 0x1f) + +#ifdef USE_CASE_RANGE +#define SWITCH_RANGE_BEGIN switch(*p) { +#define SWITCH_RANGE(FROM, TO) case FROM ... TO: +#define SWITCH_RANGE_DEFAULT default: +#define SWITCH_RANGE_END } +#else +#define SWITCH_RANGE_BEGIN { if(0) { +#define SWITCH_RANGE(FROM, TO) } else if(FROM <= *p && *p <= TO) { +#define SWITCH_RANGE_DEFAULT } else { +#define SWITCH_RANGE_END } } +#endif + + if(p == pe) { goto _out; } + do { + switch(cs) { + case MSGPACK_CS_HEADER: + SWITCH_RANGE_BEGIN + SWITCH_RANGE(0x00, 0x7f) // Positive Fixnum + push_fixed_value(_uint8, *(uint8_t*)p); + SWITCH_RANGE(0xe0, 0xff) // Negative Fixnum + push_fixed_value(_int8, *(int8_t*)p); + SWITCH_RANGE(0xc0, 0xdf) // Variable + switch(*p) { + case 0xc0: // nil + push_simple_value(_nil); + //case 0xc1: // string + // again_terminal_trail(NEXT_CS(p), p+1); + case 0xc2: // false + push_simple_value(_false); + case 0xc3: // true + push_simple_value(_true); + case 0xc4: // bin 8 + case 0xc5: // bin 16 + case 0xc6: // bin 32 + again_fixed_trail(NEXT_CS(p), 1 << (((unsigned int)*p) & 0x03)); + case 0xc7: // ext 8 + case 0xc8: // ext 16 + case 0xc9: // ext 32 + again_fixed_trail(NEXT_CS(p), 1 << ((((unsigned int)*p) + 1) & 0x03)); + case 0xca: // float + case 0xcb: // double + case 0xcc: // unsigned int 8 + case 0xcd: // unsigned int 16 + case 0xce: // unsigned int 32 + case 0xcf: // unsigned int 64 + case 0xd0: // signed int 8 + case 0xd1: // signed int 16 + case 0xd2: // signed int 32 + case 0xd3: // signed int 64 + again_fixed_trail(NEXT_CS(p), 1 << (((unsigned int)*p) & 0x03)); + case 0xd4: // fixext 1 + case 0xd5: // fixext 2 + case 0xd6: // fixext 4 + case 0xd7: // fixext 8 + again_fixed_trail_if_zero(MSGPACK_ACS_EXT_VALUE, + (1 << (((unsigned int)*p) & 0x03)) + 1, _ext_zero); + case 0xd8: // fixext 16 + again_fixed_trail_if_zero(MSGPACK_ACS_EXT_VALUE, 16+1, _ext_zero); + + case 0xd9: // str 8 + case 0xda: // str 16 + case 0xdb: // str 32 + again_fixed_trail(NEXT_CS(p), 1 << ((((unsigned int)*p) & 0x03) - 1)); + case 0xdc: // array 16 + case 0xdd: // array 32 + case 0xde: // map 16 + case 0xdf: // map 32 + again_fixed_trail(NEXT_CS(p), 2u << (((unsigned int)*p) & 0x01)); + default: + ret = MSGPACK_UNPACK_PARSE_ERROR; + goto _failed; + } + SWITCH_RANGE(0xa0, 0xbf) // FixStr + again_fixed_trail_if_zero(MSGPACK_ACS_STR_VALUE, ((unsigned int)*p & 0x1f), _str_zero); + SWITCH_RANGE(0x90, 0x9f) // FixArray + start_container(_array, ((unsigned int)*p) & 0x0f, MSGPACK_CT_ARRAY_ITEM); + SWITCH_RANGE(0x80, 0x8f) // FixMap + start_container(_map, ((unsigned int)*p) & 0x0f, MSGPACK_CT_MAP_KEY); + + SWITCH_RANGE_DEFAULT + ret = MSGPACK_UNPACK_PARSE_ERROR; + goto _failed; + SWITCH_RANGE_END + // end MSGPACK_CS_HEADER + + + _fixed_trail_again: + ++p; + // fallthrough + + default: + if((size_t)(pe - p) < trail) { goto _out; } + n = p; p += trail - 1; + switch(cs) { + //case MSGPACK_CS_ + //case MSGPACK_CS_ + case MSGPACK_CS_FLOAT: { + union { uint32_t i; float f; } mem; + _msgpack_load32(uint32_t, n, &mem.i); + push_fixed_value(_float, mem.f); } + case MSGPACK_CS_DOUBLE: { + union { uint64_t i; double f; } mem; + _msgpack_load64(uint64_t, n, &mem.i); +#if defined(TARGET_OS_IPHONE) + // ok +#elif defined(__arm__) && !(__ARM_EABI__) // arm-oabi + // https://github.com/msgpack/msgpack-perl/pull/1 + mem.i = (mem.i & 0xFFFFFFFFUL) << 32UL | (mem.i >> 32UL); +#endif + push_fixed_value(_double, mem.f); } + case MSGPACK_CS_UINT_8: + push_fixed_value(_uint8, *(uint8_t*)n); + case MSGPACK_CS_UINT_16:{ + uint16_t tmp; + _msgpack_load16(uint16_t,n,&tmp); + push_fixed_value(_uint16, tmp); + } + case MSGPACK_CS_UINT_32:{ + uint32_t tmp; + _msgpack_load32(uint32_t,n,&tmp); + push_fixed_value(_uint32, tmp); + } + case MSGPACK_CS_UINT_64:{ + uint64_t tmp; + _msgpack_load64(uint64_t,n,&tmp); + push_fixed_value(_uint64, tmp); + } + case MSGPACK_CS_INT_8: + push_fixed_value(_int8, *(int8_t*)n); + case MSGPACK_CS_INT_16:{ + int16_t tmp; + _msgpack_load16(int16_t,n,&tmp); + push_fixed_value(_int16, tmp); + } + case MSGPACK_CS_INT_32:{ + int32_t tmp; + _msgpack_load32(int32_t,n,&tmp); + push_fixed_value(_int32, tmp); + } + case MSGPACK_CS_INT_64:{ + int64_t tmp; + _msgpack_load64(int64_t,n,&tmp); + push_fixed_value(_int64, tmp); + } + case MSGPACK_CS_FIXEXT_1: + again_fixed_trail_if_zero(MSGPACK_ACS_EXT_VALUE, 1+1, _ext_zero); + case MSGPACK_CS_FIXEXT_2: + again_fixed_trail_if_zero(MSGPACK_ACS_EXT_VALUE, 2+1, _ext_zero); + case MSGPACK_CS_FIXEXT_4: + again_fixed_trail_if_zero(MSGPACK_ACS_EXT_VALUE, 4+1, _ext_zero); + case MSGPACK_CS_FIXEXT_8: + again_fixed_trail_if_zero(MSGPACK_ACS_EXT_VALUE, 8+1, _ext_zero); + case MSGPACK_CS_FIXEXT_16: + again_fixed_trail_if_zero(MSGPACK_ACS_EXT_VALUE, 16+1, _ext_zero); + case MSGPACK_CS_STR_8: + again_fixed_trail_if_zero(MSGPACK_ACS_STR_VALUE, *(uint8_t*)n, _str_zero); + case MSGPACK_CS_BIN_8: + again_fixed_trail_if_zero(MSGPACK_ACS_BIN_VALUE, *(uint8_t*)n, _bin_zero); + case MSGPACK_CS_EXT_8: + again_fixed_trail_if_zero(MSGPACK_ACS_EXT_VALUE, (*(uint8_t*)n) + 1, _ext_zero); + case MSGPACK_CS_STR_16:{ + uint16_t tmp; + _msgpack_load16(uint16_t,n,&tmp); + again_fixed_trail_if_zero(MSGPACK_ACS_STR_VALUE, tmp, _str_zero); + } + case MSGPACK_CS_BIN_16:{ + uint16_t tmp; + _msgpack_load16(uint16_t,n,&tmp); + again_fixed_trail_if_zero(MSGPACK_ACS_BIN_VALUE, tmp, _bin_zero); + } + case MSGPACK_CS_EXT_16:{ + uint16_t tmp; + _msgpack_load16(uint16_t,n,&tmp); + again_fixed_trail_if_zero(MSGPACK_ACS_EXT_VALUE, tmp + 1, _ext_zero); + } + case MSGPACK_CS_STR_32:{ + uint32_t tmp; + _msgpack_load32(uint32_t,n,&tmp); + again_fixed_trail_if_zero(MSGPACK_ACS_STR_VALUE, tmp, _str_zero); + } + case MSGPACK_CS_BIN_32:{ + uint32_t tmp; + _msgpack_load32(uint32_t,n,&tmp); + again_fixed_trail_if_zero(MSGPACK_ACS_BIN_VALUE, tmp, _bin_zero); + } + case MSGPACK_CS_EXT_32:{ + uint32_t tmp; + _msgpack_load32(uint32_t,n,&tmp); + again_fixed_trail_if_zero(MSGPACK_ACS_EXT_VALUE, tmp + 1, _ext_zero); + } + case MSGPACK_ACS_STR_VALUE: + _str_zero: + push_variable_value(_str, data, n, trail); + case MSGPACK_ACS_BIN_VALUE: + _bin_zero: + push_variable_value(_bin, data, n, trail); + case MSGPACK_ACS_EXT_VALUE: + _ext_zero: + push_variable_value(_ext, data, n, trail); + + case MSGPACK_CS_ARRAY_16:{ + uint16_t tmp; + _msgpack_load16(uint16_t,n,&tmp); + start_container(_array, tmp, MSGPACK_CT_ARRAY_ITEM); + } + case MSGPACK_CS_ARRAY_32:{ + /* FIXME security guard */ + uint32_t tmp; + _msgpack_load32(uint32_t,n,&tmp); + start_container(_array, tmp, MSGPACK_CT_ARRAY_ITEM); + } + + case MSGPACK_CS_MAP_16:{ + uint16_t tmp; + _msgpack_load16(uint16_t,n,&tmp); + start_container(_map, tmp, MSGPACK_CT_MAP_KEY); + } + case MSGPACK_CS_MAP_32:{ + /* FIXME security guard */ + uint32_t tmp; + _msgpack_load32(uint32_t,n,&tmp); + start_container(_map, tmp, MSGPACK_CT_MAP_KEY); + } + + default: + ret = MSGPACK_UNPACK_PARSE_ERROR; + goto _failed; + } + } + + _push: + if(top == 0) { goto _finish; } + c = &stack[top-1]; + switch(c->ct) { + case MSGPACK_CT_ARRAY_ITEM: + ret = msgpack_unpack_callback(_array_item)(user, &c->obj, obj); \ + if(ret < 0) { goto _failed; } + if(--c->count == 0) { + obj = c->obj; + --top; + /*printf("stack pop %d\n", top);*/ + goto _push; + } + goto _header_again; + case MSGPACK_CT_MAP_KEY: + c->map_key = obj; + c->ct = MSGPACK_CT_MAP_VALUE; + goto _header_again; + case MSGPACK_CT_MAP_VALUE: + ret = msgpack_unpack_callback(_map_item)(user, &c->obj, c->map_key, obj); \ + if(ret < 0) { goto _failed; } + if(--c->count == 0) { + obj = c->obj; + --top; + /*printf("stack pop %d\n", top);*/ + goto _push; + } + c->ct = MSGPACK_CT_MAP_KEY; + goto _header_again; + + default: + ret = MSGPACK_UNPACK_PARSE_ERROR; + goto _failed; + } + + _header_again: + cs = MSGPACK_CS_HEADER; + ++p; + } while(p != pe); + goto _out; + + + _finish: + stack[0].obj = obj; + ++p; + ret = 1; + /*printf("-- finish --\n"); */ + goto _end; + + _failed: + /*printf("** FAILED **\n"); */ + goto _end; + + _out: + ret = 0; + goto _end; + + _end: + ctx->cs = cs; + ctx->trail = trail; + ctx->top = top; + *off = (size_t)(p - (const unsigned char*)data); + + return ret; + } +} + +#undef msgpack_unpack_func +#undef msgpack_unpack_callback +#undef msgpack_unpack_struct +#undef msgpack_unpack_object +#undef msgpack_unpack_user + +#undef push_simple_value +#undef push_fixed_value +#undef push_variable_value +#undef again_fixed_trail +#undef again_fixed_trail_if_zero +#undef start_container + +#undef NEXT_CS + +#undef SWITCH_RANGE_BEGIN +#undef SWITCH_RANGE +#undef SWITCH_RANGE_DEFAULT +#undef SWITCH_RANGE_END diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/util.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/util.h new file mode 100644 index 0000000..959b56b --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/util.h @@ -0,0 +1,15 @@ +/* + * MessagePack for C utilities + * + * Copyright (C) 2014 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_UTIL_H +#define MSGPACK_UTIL_H + +#define MSGPACK_UNUSED(a) (void)(a) + +#endif /* MSGPACK_UTIL_H */ diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/version_master.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/version_master.h new file mode 100644 index 0000000..763c732 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/version_master.h @@ -0,0 +1,3 @@ +#define MSGPACK_VERSION_MAJOR 4 +#define MSGPACK_VERSION_MINOR 0 +#define MSGPACK_VERSION_REVISION 0 diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/vrefbuffer.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/vrefbuffer.h new file mode 100644 index 0000000..c263305 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/vrefbuffer.h @@ -0,0 +1,146 @@ +/* + * MessagePack for C zero-copy buffer implementation + * + * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_VREFBUFFER_H +#define MSGPACK_VREFBUFFER_H + +#include "zone.h" +#include +#include + +#if defined(unix) || defined(__unix) || defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__QNX__) || defined(__QNXTO__) || defined(__HAIKU__) +#include +typedef struct iovec msgpack_iovec; +#else +struct msgpack_iovec { + void *iov_base; + size_t iov_len; +}; +typedef struct msgpack_iovec msgpack_iovec; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup msgpack_vrefbuffer Vectored Referencing buffer + * @ingroup msgpack_buffer + * @{ + */ + +struct msgpack_vrefbuffer_chunk; +typedef struct msgpack_vrefbuffer_chunk msgpack_vrefbuffer_chunk; + +typedef struct msgpack_vrefbuffer_inner_buffer { + size_t free; + char* ptr; + msgpack_vrefbuffer_chunk* head; +} msgpack_vrefbuffer_inner_buffer; + +typedef struct msgpack_vrefbuffer { + msgpack_iovec* tail; + msgpack_iovec* end; + msgpack_iovec* array; + + size_t chunk_size; + size_t ref_size; + + msgpack_vrefbuffer_inner_buffer inner_buffer; +} msgpack_vrefbuffer; + + +#ifndef MSGPACK_VREFBUFFER_REF_SIZE +#define MSGPACK_VREFBUFFER_REF_SIZE 32 +#endif + +#ifndef MSGPACK_VREFBUFFER_CHUNK_SIZE +#define MSGPACK_VREFBUFFER_CHUNK_SIZE 8192 +#endif + +MSGPACK_DLLEXPORT +bool msgpack_vrefbuffer_init(msgpack_vrefbuffer* vbuf, + size_t ref_size, size_t chunk_size); +MSGPACK_DLLEXPORT +void msgpack_vrefbuffer_destroy(msgpack_vrefbuffer* vbuf); + +static inline msgpack_vrefbuffer* msgpack_vrefbuffer_new(size_t ref_size, size_t chunk_size); +static inline void msgpack_vrefbuffer_free(msgpack_vrefbuffer* vbuf); + +static inline int msgpack_vrefbuffer_write(void* data, const char* buf, size_t len); + +static inline const msgpack_iovec* msgpack_vrefbuffer_vec(const msgpack_vrefbuffer* vref); +static inline size_t msgpack_vrefbuffer_veclen(const msgpack_vrefbuffer* vref); + +MSGPACK_DLLEXPORT +int msgpack_vrefbuffer_append_copy(msgpack_vrefbuffer* vbuf, + const char* buf, size_t len); + +MSGPACK_DLLEXPORT +int msgpack_vrefbuffer_append_ref(msgpack_vrefbuffer* vbuf, + const char* buf, size_t len); + +MSGPACK_DLLEXPORT +int msgpack_vrefbuffer_migrate(msgpack_vrefbuffer* vbuf, msgpack_vrefbuffer* to); + +MSGPACK_DLLEXPORT +void msgpack_vrefbuffer_clear(msgpack_vrefbuffer* vref); + +/** @} */ + + +static inline msgpack_vrefbuffer* msgpack_vrefbuffer_new(size_t ref_size, size_t chunk_size) +{ + msgpack_vrefbuffer* vbuf = (msgpack_vrefbuffer*)malloc(sizeof(msgpack_vrefbuffer)); + if (vbuf == NULL) return NULL; + if(!msgpack_vrefbuffer_init(vbuf, ref_size, chunk_size)) { + free(vbuf); + return NULL; + } + return vbuf; +} + +static inline void msgpack_vrefbuffer_free(msgpack_vrefbuffer* vbuf) +{ + if(vbuf == NULL) { return; } + msgpack_vrefbuffer_destroy(vbuf); + free(vbuf); +} + +static inline int msgpack_vrefbuffer_write(void* data, const char* buf, size_t len) +{ + msgpack_vrefbuffer* vbuf = (msgpack_vrefbuffer*)data; + assert(buf || len == 0); + + if(!buf) return 0; + + if(len < vbuf->ref_size) { + return msgpack_vrefbuffer_append_copy(vbuf, buf, len); + } else { + return msgpack_vrefbuffer_append_ref(vbuf, buf, len); + } +} + +static inline const msgpack_iovec* msgpack_vrefbuffer_vec(const msgpack_vrefbuffer* vref) +{ + return vref->array; +} + +static inline size_t msgpack_vrefbuffer_veclen(const msgpack_vrefbuffer* vref) +{ + return (size_t)(vref->tail - vref->array); +} + + +#ifdef __cplusplus +} +#endif + +#endif /* msgpack/vrefbuffer.h */ diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/zbuffer.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/zbuffer.h new file mode 100644 index 0000000..c38d627 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/zbuffer.h @@ -0,0 +1,205 @@ +/* + * MessagePack for C deflate buffer implementation + * + * Copyright (C) 2010 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_ZBUFFER_H +#define MSGPACK_ZBUFFER_H + +#include "sysdep.h" +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup msgpack_zbuffer Compressed buffer + * @ingroup msgpack_buffer + * @{ + */ + +typedef struct msgpack_zbuffer { + z_stream stream; + char* data; + size_t init_size; +} msgpack_zbuffer; + +#ifndef MSGPACK_ZBUFFER_INIT_SIZE +#define MSGPACK_ZBUFFER_INIT_SIZE 8192 +#endif + +static inline bool msgpack_zbuffer_init( + msgpack_zbuffer* zbuf, int level, size_t init_size); +static inline void msgpack_zbuffer_destroy(msgpack_zbuffer* zbuf); + +static inline msgpack_zbuffer* msgpack_zbuffer_new(int level, size_t init_size); +static inline void msgpack_zbuffer_free(msgpack_zbuffer* zbuf); + +static inline char* msgpack_zbuffer_flush(msgpack_zbuffer* zbuf); + +static inline const char* msgpack_zbuffer_data(const msgpack_zbuffer* zbuf); +static inline size_t msgpack_zbuffer_size(const msgpack_zbuffer* zbuf); + +static inline bool msgpack_zbuffer_reset(msgpack_zbuffer* zbuf); +static inline void msgpack_zbuffer_reset_buffer(msgpack_zbuffer* zbuf); +static inline char* msgpack_zbuffer_release_buffer(msgpack_zbuffer* zbuf); + + +#ifndef MSGPACK_ZBUFFER_RESERVE_SIZE +#define MSGPACK_ZBUFFER_RESERVE_SIZE 512 +#endif + +static inline int msgpack_zbuffer_write(void* data, const char* buf, size_t len); + +static inline bool msgpack_zbuffer_expand(msgpack_zbuffer* zbuf); + + +static inline bool msgpack_zbuffer_init(msgpack_zbuffer* zbuf, + int level, size_t init_size) +{ + memset(zbuf, 0, sizeof(msgpack_zbuffer)); + zbuf->init_size = init_size; + if(deflateInit(&zbuf->stream, level) != Z_OK) { + free(zbuf->data); + return false; + } + return true; +} + +static inline void msgpack_zbuffer_destroy(msgpack_zbuffer* zbuf) +{ + deflateEnd(&zbuf->stream); + free(zbuf->data); +} + +static inline msgpack_zbuffer* msgpack_zbuffer_new(int level, size_t init_size) +{ + msgpack_zbuffer* zbuf = (msgpack_zbuffer*)malloc(sizeof(msgpack_zbuffer)); + if (zbuf == NULL) return NULL; + if(!msgpack_zbuffer_init(zbuf, level, init_size)) { + free(zbuf); + return NULL; + } + return zbuf; +} + +static inline void msgpack_zbuffer_free(msgpack_zbuffer* zbuf) +{ + if(zbuf == NULL) { return; } + msgpack_zbuffer_destroy(zbuf); + free(zbuf); +} + +static inline bool msgpack_zbuffer_expand(msgpack_zbuffer* zbuf) +{ + size_t used = (size_t)((char *)(zbuf->stream.next_out) - zbuf->data); + size_t csize = used + zbuf->stream.avail_out; + + size_t nsize = (csize == 0) ? zbuf->init_size : csize * 2; + + char* tmp = (char*)realloc(zbuf->data, nsize); + if(tmp == NULL) { + return false; + } + + zbuf->data = tmp; + zbuf->stream.next_out = (Bytef*)(tmp + used); + zbuf->stream.avail_out = (uInt)(nsize - used); + + return true; +} + +static inline int msgpack_zbuffer_write(void* data, const char* buf, size_t len) +{ + msgpack_zbuffer* zbuf = (msgpack_zbuffer*)data; + + assert(buf || len == 0); + if(!buf) return 0; + + zbuf->stream.next_in = (Bytef*)buf; + zbuf->stream.avail_in = (uInt)len; + + while(zbuf->stream.avail_in > 0) { + if(zbuf->stream.avail_out < MSGPACK_ZBUFFER_RESERVE_SIZE) { + if(!msgpack_zbuffer_expand(zbuf)) { + return -1; + } + } + + if(deflate(&zbuf->stream, Z_NO_FLUSH) != Z_OK) { + return -1; + } + } + + return 0; +} + +static inline char* msgpack_zbuffer_flush(msgpack_zbuffer* zbuf) +{ + while(true) { + switch(deflate(&zbuf->stream, Z_FINISH)) { + case Z_STREAM_END: + return zbuf->data; + case Z_OK: + case Z_BUF_ERROR: + if(!msgpack_zbuffer_expand(zbuf)) { + return NULL; + } + break; + default: + return NULL; + } + } +} + +static inline const char* msgpack_zbuffer_data(const msgpack_zbuffer* zbuf) +{ + return zbuf->data; +} + +static inline size_t msgpack_zbuffer_size(const msgpack_zbuffer* zbuf) +{ + return (size_t)((char *)(zbuf->stream.next_out) - zbuf->data); +} + +static inline void msgpack_zbuffer_reset_buffer(msgpack_zbuffer* zbuf) +{ + zbuf->stream.avail_out += (uInt)((char*)zbuf->stream.next_out - zbuf->data); + zbuf->stream.next_out = (Bytef*)zbuf->data; +} + +static inline bool msgpack_zbuffer_reset(msgpack_zbuffer* zbuf) +{ + if(deflateReset(&zbuf->stream) != Z_OK) { + return false; + } + msgpack_zbuffer_reset_buffer(zbuf); + return true; +} + +static inline char* msgpack_zbuffer_release_buffer(msgpack_zbuffer* zbuf) +{ + char* tmp = zbuf->data; + zbuf->data = NULL; + zbuf->stream.next_out = NULL; + zbuf->stream.avail_out = 0; + return tmp; +} + +/** @} */ + + +#ifdef __cplusplus +} +#endif + +#endif /* msgpack/zbuffer.h */ diff --git a/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/zone.h b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/zone.h new file mode 100644 index 0000000..7facd54 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/include/msgpack/zone.h @@ -0,0 +1,163 @@ +/* + * MessagePack for C memory pool implementation + * + * Copyright (C) 2008-2010 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef MSGPACK_ZONE_H +#define MSGPACK_ZONE_H + +#include "sysdep.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup msgpack_zone Memory zone + * @ingroup msgpack + * @{ + */ + +typedef struct msgpack_zone_finalizer { + void (*func)(void* data); + void* data; +} msgpack_zone_finalizer; + +typedef struct msgpack_zone_finalizer_array { + msgpack_zone_finalizer* tail; + msgpack_zone_finalizer* end; + msgpack_zone_finalizer* array; +} msgpack_zone_finalizer_array; + +struct msgpack_zone_chunk; +typedef struct msgpack_zone_chunk msgpack_zone_chunk; + +typedef struct msgpack_zone_chunk_list { + size_t free; + char* ptr; + msgpack_zone_chunk* head; +} msgpack_zone_chunk_list; + +typedef struct msgpack_zone { + msgpack_zone_chunk_list chunk_list; + msgpack_zone_finalizer_array finalizer_array; + size_t chunk_size; +} msgpack_zone; + +#ifndef MSGPACK_ZONE_CHUNK_SIZE +#define MSGPACK_ZONE_CHUNK_SIZE 8192 +#endif + +MSGPACK_DLLEXPORT +bool msgpack_zone_init(msgpack_zone* zone, size_t chunk_size); +MSGPACK_DLLEXPORT +void msgpack_zone_destroy(msgpack_zone* zone); + +MSGPACK_DLLEXPORT +msgpack_zone* msgpack_zone_new(size_t chunk_size); +MSGPACK_DLLEXPORT +void msgpack_zone_free(msgpack_zone* zone); + +static inline void* msgpack_zone_malloc(msgpack_zone* zone, size_t size); +static inline void* msgpack_zone_malloc_no_align(msgpack_zone* zone, size_t size); + +static inline bool msgpack_zone_push_finalizer(msgpack_zone* zone, + void (*func)(void* data), void* data); + +static inline void msgpack_zone_swap(msgpack_zone* a, msgpack_zone* b); + +MSGPACK_DLLEXPORT +bool msgpack_zone_is_empty(msgpack_zone* zone); + +MSGPACK_DLLEXPORT +void msgpack_zone_clear(msgpack_zone* zone); + +/** @} */ + + +#ifndef MSGPACK_ZONE_ALIGN +#define MSGPACK_ZONE_ALIGN sizeof(void*) +#endif + +MSGPACK_DLLEXPORT +void* msgpack_zone_malloc_expand(msgpack_zone* zone, size_t size); + +static inline void* msgpack_zone_malloc_no_align(msgpack_zone* zone, size_t size) +{ + char* ptr; + msgpack_zone_chunk_list* cl = &zone->chunk_list; + + if(zone->chunk_list.free < size) { + return msgpack_zone_malloc_expand(zone, size); + } + + ptr = cl->ptr; + cl->free -= size; + cl->ptr += size; + + return ptr; +} + +static inline void* msgpack_zone_malloc(msgpack_zone* zone, size_t size) +{ + char* aligned = + (char*)( + (uintptr_t)( + zone->chunk_list.ptr + (MSGPACK_ZONE_ALIGN - 1) + ) & ~(uintptr_t)(MSGPACK_ZONE_ALIGN - 1) + ); + size_t adjusted_size = size + (size_t)(aligned - zone->chunk_list.ptr); + if(zone->chunk_list.free >= adjusted_size) { + zone->chunk_list.free -= adjusted_size; + zone->chunk_list.ptr += adjusted_size; + return aligned; + } + { + void* ptr = msgpack_zone_malloc_expand(zone, size + (MSGPACK_ZONE_ALIGN - 1)); + if (ptr) { + return (char*)((uintptr_t)(ptr) & ~(uintptr_t)(MSGPACK_ZONE_ALIGN - 1)); + } + } + return NULL; +} + + +bool msgpack_zone_push_finalizer_expand(msgpack_zone* zone, + void (*func)(void* data), void* data); + +static inline bool msgpack_zone_push_finalizer(msgpack_zone* zone, + void (*func)(void* data), void* data) +{ + msgpack_zone_finalizer_array* const fa = &zone->finalizer_array; + msgpack_zone_finalizer* fin = fa->tail; + + if(fin == fa->end) { + return msgpack_zone_push_finalizer_expand(zone, func, data); + } + + fin->func = func; + fin->data = data; + + ++fa->tail; + + return true; +} + +static inline void msgpack_zone_swap(msgpack_zone* a, msgpack_zone* b) +{ + msgpack_zone tmp = *a; + *a = *b; + *b = tmp; +} + + +#ifdef __cplusplus +} +#endif + +#endif /* msgpack/zone.h */ diff --git a/local_pod_repo/msgpack-c/msgpack-c/msgpack-config.cmake.in b/local_pod_repo/msgpack-c/msgpack-c/msgpack-config.cmake.in new file mode 100644 index 0000000..bd712e7 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/msgpack-config.cmake.in @@ -0,0 +1,19 @@ +#.rst: +# msgpack +# ------- +# +# The following import targets are created +# +# :: +# +# msgpackc +# msgpackc-static (optional) +# + +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +if(NOT TARGET msgpackc AND NOT TARGET msgpackc-static) + include("${CMAKE_CURRENT_LIST_DIR}/msgpack-targets.cmake") +endif() diff --git a/local_pod_repo/msgpack-c/msgpack-c/msgpack.pc.in b/local_pod_repo/msgpack-c/msgpack-c/msgpack.pc.in new file mode 100644 index 0000000..4c75d57 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/msgpack.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: MessagePack +Description: Binary-based efficient object serialization library +Version: @VERSION@ +Libs: -L${libdir} -lmsgpackc +Cflags: -I${includedir} diff --git a/local_pod_repo/msgpack-c/msgpack-c/src/objectc.c b/local_pod_repo/msgpack-c/msgpack-c/src/objectc.c new file mode 100644 index 0000000..3b3ea79 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/src/objectc.c @@ -0,0 +1,484 @@ +/* + * MessagePack for C dynamic typing routine + * + * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#if defined(_KERNEL_MODE) +# undef _NO_CRT_STDIO_INLINE +# define _NO_CRT_STDIO_INLINE +#endif + +#include "object.h" +#include "pack.h" +#include + +#include +#include + +#if defined(_MSC_VER) +#if _MSC_VER >= 1800 +#include +#else +#define PRIu64 "I64u" +#define PRIi64 "I64i" +#define PRIi8 "i" +#endif +#else +#include +#endif + +#if defined(_KERNEL_MODE) +# undef snprintf +# define snprintf _snprintf +#endif + +int msgpack_pack_object(msgpack_packer* pk, msgpack_object d) +{ + switch(d.type) { + case MSGPACK_OBJECT_NIL: + return msgpack_pack_nil(pk); + + case MSGPACK_OBJECT_BOOLEAN: + if(d.via.boolean) { + return msgpack_pack_true(pk); + } else { + return msgpack_pack_false(pk); + } + + case MSGPACK_OBJECT_POSITIVE_INTEGER: + return msgpack_pack_uint64(pk, d.via.u64); + + case MSGPACK_OBJECT_NEGATIVE_INTEGER: + return msgpack_pack_int64(pk, d.via.i64); + + case MSGPACK_OBJECT_FLOAT32: + return msgpack_pack_float(pk, (float)d.via.f64); + + case MSGPACK_OBJECT_FLOAT64: + return msgpack_pack_double(pk, d.via.f64); + + case MSGPACK_OBJECT_STR: + { + int ret = msgpack_pack_str(pk, d.via.str.size); + if(ret < 0) { return ret; } + return msgpack_pack_str_body(pk, d.via.str.ptr, d.via.str.size); + } + + case MSGPACK_OBJECT_BIN: + { + int ret = msgpack_pack_bin(pk, d.via.bin.size); + if(ret < 0) { return ret; } + return msgpack_pack_bin_body(pk, d.via.bin.ptr, d.via.bin.size); + } + + case MSGPACK_OBJECT_EXT: + { + int ret = msgpack_pack_ext(pk, d.via.ext.size, d.via.ext.type); + if(ret < 0) { return ret; } + return msgpack_pack_ext_body(pk, d.via.ext.ptr, d.via.ext.size); + } + + case MSGPACK_OBJECT_ARRAY: + { + int ret = msgpack_pack_array(pk, d.via.array.size); + if(ret < 0) { + return ret; + } + else { + msgpack_object* o = d.via.array.ptr; + msgpack_object* const oend = d.via.array.ptr + d.via.array.size; + for(; o != oend; ++o) { + ret = msgpack_pack_object(pk, *o); + if(ret < 0) { return ret; } + } + + return 0; + } + } + + case MSGPACK_OBJECT_MAP: + { + int ret = msgpack_pack_map(pk, d.via.map.size); + if(ret < 0) { + return ret; + } + else { + msgpack_object_kv* kv = d.via.map.ptr; + msgpack_object_kv* const kvend = d.via.map.ptr + d.via.map.size; + for(; kv != kvend; ++kv) { + ret = msgpack_pack_object(pk, kv->key); + if(ret < 0) { return ret; } + ret = msgpack_pack_object(pk, kv->val); + if(ret < 0) { return ret; } + } + + return 0; + } + } + + default: + return -1; + } +} + +#if !defined(_KERNEL_MODE) + +static void msgpack_object_bin_print(FILE* out, const char *ptr, size_t size) +{ + size_t i; + for (i = 0; i < size; ++i) { + if (ptr[i] == '"') { + fputs("\\\"", out); + } else if (isprint((unsigned char)ptr[i])) { + fputc(ptr[i], out); + } else { + fprintf(out, "\\x%02x", (unsigned char)ptr[i]); + } + } +} + +void msgpack_object_print(FILE* out, msgpack_object o) +{ + switch(o.type) { + case MSGPACK_OBJECT_NIL: + fprintf(out, "nil"); + break; + + case MSGPACK_OBJECT_BOOLEAN: + fprintf(out, (o.via.boolean ? "true" : "false")); + break; + + case MSGPACK_OBJECT_POSITIVE_INTEGER: +#if defined(PRIu64) + fprintf(out, "%" PRIu64, o.via.u64); +#else + if (o.via.u64 > ULONG_MAX) + fprintf(out, "over 4294967295"); + else + fprintf(out, "%lu", (unsigned long)o.via.u64); +#endif + break; + + case MSGPACK_OBJECT_NEGATIVE_INTEGER: +#if defined(PRIi64) + fprintf(out, "%" PRIi64, o.via.i64); +#else + if (o.via.i64 > LONG_MAX) + fprintf(out, "over +2147483647"); + else if (o.via.i64 < LONG_MIN) + fprintf(out, "under -2147483648"); + else + fprintf(out, "%ld", (signed long)o.via.i64); +#endif + break; + + case MSGPACK_OBJECT_FLOAT32: + case MSGPACK_OBJECT_FLOAT64: + fprintf(out, "%f", o.via.f64); + break; + + case MSGPACK_OBJECT_STR: + fprintf(out, "\""); + fwrite(o.via.str.ptr, o.via.str.size, 1, out); + fprintf(out, "\""); + break; + + case MSGPACK_OBJECT_BIN: + fprintf(out, "\""); + msgpack_object_bin_print(out, o.via.bin.ptr, o.via.bin.size); + fprintf(out, "\""); + break; + + case MSGPACK_OBJECT_EXT: +#if defined(PRIi8) + fprintf(out, "(ext: %" PRIi8 ")", o.via.ext.type); +#else + fprintf(out, "(ext: %d)", (int)o.via.ext.type); +#endif + fprintf(out, "\""); + msgpack_object_bin_print(out, o.via.ext.ptr, o.via.ext.size); + fprintf(out, "\""); + break; + + case MSGPACK_OBJECT_ARRAY: + fprintf(out, "["); + if(o.via.array.size != 0) { + msgpack_object* p = o.via.array.ptr; + msgpack_object* const pend = o.via.array.ptr + o.via.array.size; + msgpack_object_print(out, *p); + ++p; + for(; p < pend; ++p) { + fprintf(out, ", "); + msgpack_object_print(out, *p); + } + } + fprintf(out, "]"); + break; + + case MSGPACK_OBJECT_MAP: + fprintf(out, "{"); + if(o.via.map.size != 0) { + msgpack_object_kv* p = o.via.map.ptr; + msgpack_object_kv* const pend = o.via.map.ptr + o.via.map.size; + msgpack_object_print(out, p->key); + fprintf(out, "=>"); + msgpack_object_print(out, p->val); + ++p; + for(; p < pend; ++p) { + fprintf(out, ", "); + msgpack_object_print(out, p->key); + fprintf(out, "=>"); + msgpack_object_print(out, p->val); + } + } + fprintf(out, "}"); + break; + + default: + // FIXME +#if defined(PRIu64) + fprintf(out, "#", o.type, o.via.u64); +#else + if (o.via.u64 > ULONG_MAX) + fprintf(out, "#", o.type); + else + fprintf(out, "#", o.type, (unsigned long)o.via.u64); +#endif + + } +} + +#endif + +#define MSGPACK_CHECKED_CALL(ret, func, aux_buffer, aux_buffer_size, ...) \ + ret = func(aux_buffer, aux_buffer_size, __VA_ARGS__); \ + if (ret <= 0 || ret >= (int)aux_buffer_size) return 0; \ + aux_buffer = aux_buffer + ret; \ + aux_buffer_size = aux_buffer_size - ret \ + +static int msgpack_object_bin_print_buffer(char *buffer, size_t buffer_size, const char *ptr, size_t size) +{ + size_t i; + char *aux_buffer = buffer; + size_t aux_buffer_size = buffer_size; + int ret; + + for (i = 0; i < size; ++i) { + if (ptr[i] == '"') { + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "\\\""); + } else if (isprint((unsigned char)ptr[i])) { + if (aux_buffer_size > 0) { + memcpy(aux_buffer, ptr + i, 1); + aux_buffer = aux_buffer + 1; + aux_buffer_size = aux_buffer_size - 1; + } + } else { + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "\\x%02x", (unsigned char)ptr[i]); + } + } + + return (int)(buffer_size - aux_buffer_size); +} + +int msgpack_object_print_buffer(char *buffer, size_t buffer_size, msgpack_object o) +{ + char *aux_buffer = buffer; + size_t aux_buffer_size = buffer_size; + int ret; + switch(o.type) { + case MSGPACK_OBJECT_NIL: + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "nil"); + break; + + case MSGPACK_OBJECT_BOOLEAN: + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, (o.via.boolean ? "true" : "false")); + break; + + case MSGPACK_OBJECT_POSITIVE_INTEGER: +#if defined(PRIu64) + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "%" PRIu64, o.via.u64); +#else + if (o.via.u64 > ULONG_MAX) { + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "over 4294967295"); + } else { + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "%lu", (unsigned long)o.via.u64); + } +#endif + break; + + case MSGPACK_OBJECT_NEGATIVE_INTEGER: +#if defined(PRIi64) + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "%" PRIi64, o.via.i64); +#else + if (o.via.i64 > LONG_MAX) { + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "over +2147483647"); + } else if (o.via.i64 < LONG_MIN) { + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "under -2147483648"); + } else { + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "%ld", (signed long)o.via.i64); + } +#endif + break; + + case MSGPACK_OBJECT_FLOAT32: + case MSGPACK_OBJECT_FLOAT64: + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "%f", o.via.f64); + break; + + case MSGPACK_OBJECT_STR: + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "\""); + if (o.via.str.size > 0) { + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "%.*s", (int)o.via.str.size, o.via.str.ptr); + } + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "\""); + break; + + case MSGPACK_OBJECT_BIN: + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "\""); + MSGPACK_CHECKED_CALL(ret, msgpack_object_bin_print_buffer, aux_buffer, aux_buffer_size, o.via.bin.ptr, o.via.bin.size); + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "\""); + break; + + case MSGPACK_OBJECT_EXT: +#if defined(PRIi8) + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "(ext: %" PRIi8 ")", o.via.ext.type); +#else + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "(ext: %d)", (int)o.via.ext.type); +#endif + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "\""); + MSGPACK_CHECKED_CALL(ret, msgpack_object_bin_print_buffer, aux_buffer, aux_buffer_size, o.via.ext.ptr, o.via.ext.size); + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "\""); + break; + + case MSGPACK_OBJECT_ARRAY: + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "["); + if(o.via.array.size != 0) { + msgpack_object* p = o.via.array.ptr; + msgpack_object* const pend = o.via.array.ptr + o.via.array.size; + MSGPACK_CHECKED_CALL(ret, msgpack_object_print_buffer, aux_buffer, aux_buffer_size, *p); + ++p; + for(; p < pend; ++p) { + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, ", "); + MSGPACK_CHECKED_CALL(ret, msgpack_object_print_buffer, aux_buffer, aux_buffer_size, *p); + } + } + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "]"); + break; + + case MSGPACK_OBJECT_MAP: + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "{"); + if(o.via.map.size != 0) { + msgpack_object_kv* p = o.via.map.ptr; + msgpack_object_kv* const pend = o.via.map.ptr + o.via.map.size; + MSGPACK_CHECKED_CALL(ret, msgpack_object_print_buffer, aux_buffer, aux_buffer_size, p->key); + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "=>"); + MSGPACK_CHECKED_CALL(ret, msgpack_object_print_buffer, aux_buffer, aux_buffer_size, p->val); + ++p; + for(; p < pend; ++p) { + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, ", "); + MSGPACK_CHECKED_CALL(ret, msgpack_object_print_buffer, aux_buffer, aux_buffer_size, p->key); + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "=>"); + MSGPACK_CHECKED_CALL(ret, msgpack_object_print_buffer, aux_buffer, aux_buffer_size, p->val); + } + } + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "}"); + break; + + default: + // FIXME +#if defined(PRIu64) + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "#", o.type, o.via.u64); +#else + if (o.via.u64 > ULONG_MAX) { + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "#", o.type); + } else { + MSGPACK_CHECKED_CALL(ret, snprintf, aux_buffer, aux_buffer_size, "#", o.type, (unsigned long)o.via.u64); + } +#endif + } + + return (int)(buffer_size - aux_buffer_size); +} + +#undef MSGPACK_CHECKED_CALL + +bool msgpack_object_equal(const msgpack_object x, const msgpack_object y) +{ + if(x.type != y.type) { return false; } + + switch(x.type) { + case MSGPACK_OBJECT_NIL: + return true; + + case MSGPACK_OBJECT_BOOLEAN: + return x.via.boolean == y.via.boolean; + + case MSGPACK_OBJECT_POSITIVE_INTEGER: + return x.via.u64 == y.via.u64; + + case MSGPACK_OBJECT_NEGATIVE_INTEGER: + return x.via.i64 == y.via.i64; + + case MSGPACK_OBJECT_FLOAT32: + case MSGPACK_OBJECT_FLOAT64: + return x.via.f64 == y.via.f64; + + case MSGPACK_OBJECT_STR: + return x.via.str.size == y.via.str.size && + memcmp(x.via.str.ptr, y.via.str.ptr, x.via.str.size) == 0; + + case MSGPACK_OBJECT_BIN: + return x.via.bin.size == y.via.bin.size && + memcmp(x.via.bin.ptr, y.via.bin.ptr, x.via.bin.size) == 0; + + case MSGPACK_OBJECT_EXT: + return x.via.ext.size == y.via.ext.size && + x.via.ext.type == y.via.ext.type && + memcmp(x.via.ext.ptr, y.via.ext.ptr, x.via.ext.size) == 0; + + case MSGPACK_OBJECT_ARRAY: + if(x.via.array.size != y.via.array.size) { + return false; + } else if(x.via.array.size == 0) { + return true; + } else { + msgpack_object* px = x.via.array.ptr; + msgpack_object* const pxend = x.via.array.ptr + x.via.array.size; + msgpack_object* py = y.via.array.ptr; + do { + if(!msgpack_object_equal(*px, *py)) { + return false; + } + ++px; + ++py; + } while(px < pxend); + return true; + } + + case MSGPACK_OBJECT_MAP: + if(x.via.map.size != y.via.map.size) { + return false; + } else if(x.via.map.size == 0) { + return true; + } else { + msgpack_object_kv* px = x.via.map.ptr; + msgpack_object_kv* const pxend = x.via.map.ptr + x.via.map.size; + msgpack_object_kv* py = y.via.map.ptr; + do { + if(!msgpack_object_equal(px->key, py->key) || !msgpack_object_equal(px->val, py->val)) { + return false; + } + ++px; + ++py; + } while(px < pxend); + return true; + } + + default: + return false; + } +} diff --git a/local_pod_repo/msgpack-c/msgpack-c/src/unpack.c b/local_pod_repo/msgpack-c/msgpack-c/src/unpack.c new file mode 100644 index 0000000..62271d0 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/src/unpack.c @@ -0,0 +1,702 @@ +/* + * MessagePack for C unpacking routine + * + * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#include "unpack.h" +#include "unpack_define.h" +#include "util.h" +#include + +#ifdef _msgpack_atomic_counter_header +#include _msgpack_atomic_counter_header +#endif + + +typedef struct { + msgpack_zone** z; + bool referenced; +} unpack_user; + + +#define msgpack_unpack_struct(name) \ + struct template ## name + +#define msgpack_unpack_func(ret, name) \ + ret template ## name + +#define msgpack_unpack_callback(name) \ + template_callback ## name + +#define msgpack_unpack_object msgpack_object + +#define msgpack_unpack_user unpack_user + + +struct template_context; +typedef struct template_context template_context; + +static void template_init(template_context* ctx); + +static msgpack_object template_data(template_context* ctx); + +static int template_execute( + template_context* ctx, const char* data, size_t len, size_t* off); + + +static inline msgpack_object template_callback_root(unpack_user* u) +{ + msgpack_object o; + MSGPACK_UNUSED(u); + o.type = MSGPACK_OBJECT_NIL; + return o; +} + +static inline int template_callback_uint8(unpack_user* u, uint8_t d, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + o->type = MSGPACK_OBJECT_POSITIVE_INTEGER; + o->via.u64 = d; + return 0; +} + +static inline int template_callback_uint16(unpack_user* u, uint16_t d, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + o->type = MSGPACK_OBJECT_POSITIVE_INTEGER; + o->via.u64 = d; + return 0; +} + +static inline int template_callback_uint32(unpack_user* u, uint32_t d, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + o->type = MSGPACK_OBJECT_POSITIVE_INTEGER; + o->via.u64 = d; + return 0; +} + +static inline int template_callback_uint64(unpack_user* u, uint64_t d, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + o->type = MSGPACK_OBJECT_POSITIVE_INTEGER; + o->via.u64 = d; + return 0; +} + +static inline int template_callback_int8(unpack_user* u, int8_t d, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + if(d >= 0) { + o->type = MSGPACK_OBJECT_POSITIVE_INTEGER; + o->via.u64 = (uint64_t)d; + return 0; + } + else { + o->type = MSGPACK_OBJECT_NEGATIVE_INTEGER; + o->via.i64 = d; + return 0; + } +} + +static inline int template_callback_int16(unpack_user* u, int16_t d, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + if(d >= 0) { + o->type = MSGPACK_OBJECT_POSITIVE_INTEGER; + o->via.u64 = (uint64_t)d; + return 0; + } + else { + o->type = MSGPACK_OBJECT_NEGATIVE_INTEGER; + o->via.i64 = d; + return 0; + } +} + +static inline int template_callback_int32(unpack_user* u, int32_t d, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + if(d >= 0) { + o->type = MSGPACK_OBJECT_POSITIVE_INTEGER; + o->via.u64 = (uint64_t)d; + return 0; + } + else { + o->type = MSGPACK_OBJECT_NEGATIVE_INTEGER; + o->via.i64 = d; + return 0; + } +} + +static inline int template_callback_int64(unpack_user* u, int64_t d, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + if(d >= 0) { + o->type = MSGPACK_OBJECT_POSITIVE_INTEGER; + o->via.u64 = (uint64_t)d; + return 0; + } + else { + o->type = MSGPACK_OBJECT_NEGATIVE_INTEGER; + o->via.i64 = d; + return 0; + } +} + +static inline int template_callback_float(unpack_user* u, float d, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + o->type = MSGPACK_OBJECT_FLOAT32; + o->via.f64 = d; + return 0; +} + +static inline int template_callback_double(unpack_user* u, double d, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + o->type = MSGPACK_OBJECT_FLOAT64; + o->via.f64 = d; + return 0; +} + +static inline int template_callback_nil(unpack_user* u, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + o->type = MSGPACK_OBJECT_NIL; + return 0; +} + +static inline int template_callback_true(unpack_user* u, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + o->type = MSGPACK_OBJECT_BOOLEAN; + o->via.boolean = true; + return 0; +} + +static inline int template_callback_false(unpack_user* u, msgpack_object* o) +{ + MSGPACK_UNUSED(u); + o->type = MSGPACK_OBJECT_BOOLEAN; + o->via.boolean = false; + return 0; +} + +static inline int template_callback_array(unpack_user* u, unsigned int n, msgpack_object* o) +{ + size_t size; + // Let's leverage the fact that sizeof(msgpack_object) is a compile time constant + // to check for int overflows. + // Note - while n is constrained to 32-bit, the product of n * sizeof(msgpack_object) + // might not be constrained to 4GB on 64-bit systems +#if SIZE_MAX == UINT_MAX + if (n > SIZE_MAX/sizeof(msgpack_object)) + return MSGPACK_UNPACK_NOMEM_ERROR; +#endif + + o->type = MSGPACK_OBJECT_ARRAY; + o->via.array.size = 0; + + size = n * sizeof(msgpack_object); + + if (*u->z == NULL) { + *u->z = msgpack_zone_new(MSGPACK_ZONE_CHUNK_SIZE); + if(*u->z == NULL) { + return MSGPACK_UNPACK_NOMEM_ERROR; + } + } + + // Unsure whether size = 0 should be an error, and if so, what to return + o->via.array.ptr = (msgpack_object*)msgpack_zone_malloc(*u->z, size); + if(o->via.array.ptr == NULL) { return MSGPACK_UNPACK_NOMEM_ERROR; } + return 0; +} + +static inline int template_callback_array_item(unpack_user* u, msgpack_object* c, msgpack_object o) +{ + MSGPACK_UNUSED(u); +#if defined(__GNUC__) && !defined(__clang__) + memcpy(&c->via.array.ptr[c->via.array.size], &o, sizeof(msgpack_object)); +#else /* __GNUC__ && !__clang__ */ + c->via.array.ptr[c->via.array.size] = o; +#endif /* __GNUC__ && !__clang__ */ + ++c->via.array.size; + return 0; +} + +static inline int template_callback_map(unpack_user* u, unsigned int n, msgpack_object* o) +{ + size_t size; + // Let's leverage the fact that sizeof(msgpack_object_kv) is a compile time constant + // to check for int overflows + // Note - while n is constrained to 32-bit, the product of n * sizeof(msgpack_object) + // might not be constrained to 4GB on 64-bit systems + + // Note - this will always be false on 64-bit systems +#if SIZE_MAX == UINT_MAX + if (n > SIZE_MAX/sizeof(msgpack_object_kv)) + return MSGPACK_UNPACK_NOMEM_ERROR; +#endif + + o->type = MSGPACK_OBJECT_MAP; + o->via.map.size = 0; + + size = n * sizeof(msgpack_object_kv); + + if (*u->z == NULL) { + *u->z = msgpack_zone_new(MSGPACK_ZONE_CHUNK_SIZE); + if(*u->z == NULL) { + return MSGPACK_UNPACK_NOMEM_ERROR; + } + } + + // Should size = 0 be an error? If so, what error to return? + o->via.map.ptr = (msgpack_object_kv*)msgpack_zone_malloc(*u->z, size); + if(o->via.map.ptr == NULL) { return MSGPACK_UNPACK_NOMEM_ERROR; } + return 0; +} + +static inline int template_callback_map_item(unpack_user* u, msgpack_object* c, msgpack_object k, msgpack_object v) +{ + MSGPACK_UNUSED(u); +#if defined(__GNUC__) && !defined(__clang__) + memcpy(&c->via.map.ptr[c->via.map.size].key, &k, sizeof(msgpack_object)); + memcpy(&c->via.map.ptr[c->via.map.size].val, &v, sizeof(msgpack_object)); +#else /* __GNUC__ && !__clang__ */ + c->via.map.ptr[c->via.map.size].key = k; + c->via.map.ptr[c->via.map.size].val = v; +#endif /* __GNUC__ && !__clang__ */ + ++c->via.map.size; + return 0; +} + +static inline int template_callback_str(unpack_user* u, const char* b, const char* p, unsigned int l, msgpack_object* o) +{ + MSGPACK_UNUSED(b); + if (*u->z == NULL) { + *u->z = msgpack_zone_new(MSGPACK_ZONE_CHUNK_SIZE); + if(*u->z == NULL) { + return MSGPACK_UNPACK_NOMEM_ERROR; + } + } + o->type = MSGPACK_OBJECT_STR; + o->via.str.ptr = p; + o->via.str.size = l; + u->referenced = true; + return 0; +} + +static inline int template_callback_bin(unpack_user* u, const char* b, const char* p, unsigned int l, msgpack_object* o) +{ + MSGPACK_UNUSED(b); + if (*u->z == NULL) { + *u->z = msgpack_zone_new(MSGPACK_ZONE_CHUNK_SIZE); + if(*u->z == NULL) { + return MSGPACK_UNPACK_NOMEM_ERROR; + } + } + o->type = MSGPACK_OBJECT_BIN; + o->via.bin.ptr = p; + o->via.bin.size = l; + u->referenced = true; + return 0; +} + +static inline int template_callback_ext(unpack_user* u, const char* b, const char* p, unsigned int l, msgpack_object* o) +{ + MSGPACK_UNUSED(b); + if (l == 0) { + return MSGPACK_UNPACK_PARSE_ERROR; + } + if (*u->z == NULL) { + *u->z = msgpack_zone_new(MSGPACK_ZONE_CHUNK_SIZE); + if(*u->z == NULL) { + return MSGPACK_UNPACK_NOMEM_ERROR; + } + } + o->type = MSGPACK_OBJECT_EXT; + o->via.ext.type = *p; + o->via.ext.ptr = p + 1; + o->via.ext.size = l - 1; + u->referenced = true; + return 0; +} + +#include "unpack_template.h" + + +#define CTX_CAST(m) ((template_context*)(m)) +#define CTX_REFERENCED(mpac) CTX_CAST((mpac)->ctx)->user.referenced + +#define COUNTER_SIZE (sizeof(_msgpack_atomic_counter_t)) + + +static inline void init_count(void* buffer) +{ + *(volatile _msgpack_atomic_counter_t*)buffer = 1; +} + +static inline void decr_count(void* buffer) +{ + // atomic if(--*(_msgpack_atomic_counter_t*)buffer == 0) { free(buffer); } + if(_msgpack_sync_decr_and_fetch((volatile _msgpack_atomic_counter_t*)buffer) == 0) { + free(buffer); + } +} + +static inline void incr_count(void* buffer) +{ + // atomic ++*(_msgpack_atomic_counter_t*)buffer; + _msgpack_sync_incr_and_fetch((volatile _msgpack_atomic_counter_t*)buffer); +} + +static inline _msgpack_atomic_counter_t get_count(void* buffer) +{ + return *(volatile _msgpack_atomic_counter_t*)buffer; +} + +bool msgpack_unpacker_init(msgpack_unpacker* mpac, size_t initial_buffer_size) +{ + char* buffer; + void* ctx; + + if(initial_buffer_size < COUNTER_SIZE) { + initial_buffer_size = COUNTER_SIZE; + } + + buffer = (char*)malloc(initial_buffer_size); + if(buffer == NULL) { + return false; + } + + ctx = malloc(sizeof(template_context)); + if(ctx == NULL) { + free(buffer); + return false; + } + + mpac->buffer = buffer; + mpac->used = COUNTER_SIZE; + mpac->free = initial_buffer_size - mpac->used; + mpac->off = COUNTER_SIZE; + mpac->parsed = 0; + mpac->initial_buffer_size = initial_buffer_size; + mpac->z = NULL; + mpac->ctx = ctx; + + init_count(mpac->buffer); + + template_init(CTX_CAST(mpac->ctx)); + CTX_CAST(mpac->ctx)->user.z = &mpac->z; + CTX_CAST(mpac->ctx)->user.referenced = false; + + return true; +} + +void msgpack_unpacker_destroy(msgpack_unpacker* mpac) +{ + msgpack_zone_free(mpac->z); + free(mpac->ctx); + decr_count(mpac->buffer); +} + +msgpack_unpacker* msgpack_unpacker_new(size_t initial_buffer_size) +{ + msgpack_unpacker* mpac = (msgpack_unpacker*)malloc(sizeof(msgpack_unpacker)); + if(mpac == NULL) { + return NULL; + } + + if(!msgpack_unpacker_init(mpac, initial_buffer_size)) { + free(mpac); + return NULL; + } + + return mpac; +} + +void msgpack_unpacker_free(msgpack_unpacker* mpac) +{ + msgpack_unpacker_destroy(mpac); + free(mpac); +} + +bool msgpack_unpacker_expand_buffer(msgpack_unpacker* mpac, size_t size) +{ + if(mpac->used == mpac->off && get_count(mpac->buffer) == 1 + && !CTX_REFERENCED(mpac)) { + // rewind buffer + mpac->free += mpac->used - COUNTER_SIZE; + mpac->used = COUNTER_SIZE; + mpac->off = COUNTER_SIZE; + + if(mpac->free >= size) { + return true; + } + } + + if(mpac->off == COUNTER_SIZE) { + char* tmp; + size_t next_size = (mpac->used + mpac->free) * 2; // include COUNTER_SIZE + while(next_size < size + mpac->used) { + size_t tmp_next_size = next_size * 2; + if (tmp_next_size <= next_size) { + next_size = size + mpac->used; + break; + } + next_size = tmp_next_size; + } + + tmp = (char*)realloc(mpac->buffer, next_size); + if(tmp == NULL) { + return false; + } + + mpac->buffer = tmp; + mpac->free = next_size - mpac->used; + + } else { + char* tmp; + size_t next_size = mpac->initial_buffer_size; // include COUNTER_SIZE + size_t not_parsed = mpac->used - mpac->off; + while(next_size < size + not_parsed + COUNTER_SIZE) { + size_t tmp_next_size = next_size * 2; + if (tmp_next_size <= next_size) { + next_size = size + not_parsed + COUNTER_SIZE; + break; + } + next_size = tmp_next_size; + } + + tmp = (char*)malloc(next_size); + if(tmp == NULL) { + return false; + } + + init_count(tmp); + + memcpy(tmp+COUNTER_SIZE, mpac->buffer+mpac->off, not_parsed); + + if(CTX_REFERENCED(mpac)) { + if(!msgpack_zone_push_finalizer(mpac->z, decr_count, mpac->buffer)) { + free(tmp); + return false; + } + CTX_REFERENCED(mpac) = false; + } else { + decr_count(mpac->buffer); + } + + mpac->buffer = tmp; + mpac->used = not_parsed + COUNTER_SIZE; + mpac->free = next_size - mpac->used; + mpac->off = COUNTER_SIZE; + } + + return true; +} + +int msgpack_unpacker_execute(msgpack_unpacker* mpac) +{ + size_t off = mpac->off; + int ret = template_execute(CTX_CAST(mpac->ctx), + mpac->buffer, mpac->used, &mpac->off); + if(mpac->off > off) { + mpac->parsed += mpac->off - off; + } + return ret; +} + +msgpack_object msgpack_unpacker_data(msgpack_unpacker* mpac) +{ + return template_data(CTX_CAST(mpac->ctx)); +} + +msgpack_zone* msgpack_unpacker_release_zone(msgpack_unpacker* mpac) +{ + msgpack_zone* old = mpac->z; + + if (old == NULL) return NULL; + if(!msgpack_unpacker_flush_zone(mpac)) { + return NULL; + } + + mpac->z = NULL; + CTX_CAST(mpac->ctx)->user.z = &mpac->z; + + return old; +} + +void msgpack_unpacker_reset_zone(msgpack_unpacker* mpac) +{ + msgpack_zone_clear(mpac->z); +} + +bool msgpack_unpacker_flush_zone(msgpack_unpacker* mpac) +{ + if(CTX_REFERENCED(mpac)) { + if(!msgpack_zone_push_finalizer(mpac->z, decr_count, mpac->buffer)) { + return false; + } + CTX_REFERENCED(mpac) = false; + + incr_count(mpac->buffer); + } + + return true; +} + +void msgpack_unpacker_reset(msgpack_unpacker* mpac) +{ + template_init(CTX_CAST(mpac->ctx)); + // don't reset referenced flag + mpac->parsed = 0; +} + +static inline msgpack_unpack_return unpacker_next(msgpack_unpacker* mpac, + msgpack_unpacked* result) +{ + int ret; + + msgpack_unpacked_destroy(result); + + ret = msgpack_unpacker_execute(mpac); + + if(ret < 0) { + result->zone = NULL; + memset(&result->data, 0, sizeof(msgpack_object)); + return (msgpack_unpack_return)ret; + } + + if(ret == 0) { + return MSGPACK_UNPACK_CONTINUE; + } + result->zone = msgpack_unpacker_release_zone(mpac); + result->data = msgpack_unpacker_data(mpac); + + return MSGPACK_UNPACK_SUCCESS; +} + +msgpack_unpack_return msgpack_unpacker_next(msgpack_unpacker* mpac, + msgpack_unpacked* result) +{ + msgpack_unpack_return ret; + + ret = unpacker_next(mpac, result); + if (ret == MSGPACK_UNPACK_SUCCESS) { + msgpack_unpacker_reset(mpac); + } + + return ret; +} + +msgpack_unpack_return +msgpack_unpacker_next_with_size(msgpack_unpacker* mpac, + msgpack_unpacked* result, size_t *p_bytes) +{ + msgpack_unpack_return ret; + + ret = unpacker_next(mpac, result); + if (ret == MSGPACK_UNPACK_SUCCESS || ret == MSGPACK_UNPACK_CONTINUE) { + *p_bytes = mpac->parsed; + } + + if (ret == MSGPACK_UNPACK_SUCCESS) { + msgpack_unpacker_reset(mpac); + } + + return ret; +} + +msgpack_unpack_return +msgpack_unpack(const char* data, size_t len, size_t* off, + msgpack_zone* result_zone, msgpack_object* result) +{ + size_t noff = 0; + if(off != NULL) { noff = *off; } + + if(len <= noff) { + // FIXME + return MSGPACK_UNPACK_CONTINUE; + } + else { + int e; + template_context ctx; + template_init(&ctx); + + ctx.user.z = &result_zone; + ctx.user.referenced = false; + + e = template_execute(&ctx, data, len, &noff); + if(e < 0) { + return (msgpack_unpack_return)e; + } + + if(off != NULL) { *off = noff; } + + if(e == 0) { + return MSGPACK_UNPACK_CONTINUE; + } + + *result = template_data(&ctx); + + if(noff < len) { + return MSGPACK_UNPACK_EXTRA_BYTES; + } + + return MSGPACK_UNPACK_SUCCESS; + } +} + +msgpack_unpack_return +msgpack_unpack_next(msgpack_unpacked* result, + const char* data, size_t len, size_t* off) +{ + size_t noff = 0; + msgpack_unpacked_destroy(result); + + if(off != NULL) { noff = *off; } + + if(len <= noff) { + return MSGPACK_UNPACK_CONTINUE; + } + + { + int e; + template_context ctx; + template_init(&ctx); + + ctx.user.z = &result->zone; + ctx.user.referenced = false; + + e = template_execute(&ctx, data, len, &noff); + + if(off != NULL) { *off = noff; } + + if(e < 0) { + msgpack_zone_free(result->zone); + result->zone = NULL; + return (msgpack_unpack_return)e; + } + + if(e == 0) { + return MSGPACK_UNPACK_CONTINUE; + } + + result->data = template_data(&ctx); + + return MSGPACK_UNPACK_SUCCESS; + } +} + +#if defined(MSGPACK_OLD_COMPILER_BUS_ERROR_WORKAROUND) +// FIXME: Dirty hack to avoid a bus error caused by OS X's old gcc. +static void dummy_function_to_avoid_bus_error() +{ +} +#endif diff --git a/local_pod_repo/msgpack-c/msgpack-c/src/version.c b/local_pod_repo/msgpack-c/msgpack-c/src/version.c new file mode 100644 index 0000000..83f7510 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/src/version.c @@ -0,0 +1,22 @@ +#include "msgpack.h" + +const char* msgpack_version(void) +{ + return MSGPACK_VERSION; +} + +int msgpack_version_major(void) +{ + return MSGPACK_VERSION_MAJOR; +} + +int msgpack_version_minor(void) +{ + return MSGPACK_VERSION_MINOR; +} + +int msgpack_version_revision(void) +{ + return MSGPACK_VERSION_REVISION; +} + diff --git a/local_pod_repo/msgpack-c/msgpack-c/src/vrefbuffer.c b/local_pod_repo/msgpack-c/msgpack-c/src/vrefbuffer.c new file mode 100644 index 0000000..38aedd5 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/src/vrefbuffer.c @@ -0,0 +1,250 @@ +/* + * MessagePack for C zero-copy buffer implementation + * + * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#include "vrefbuffer.h" +#include +#include + +#define MSGPACK_PACKER_MAX_BUFFER_SIZE 9 + +struct msgpack_vrefbuffer_chunk { + struct msgpack_vrefbuffer_chunk* next; + /* data ... */ +}; + +bool msgpack_vrefbuffer_init(msgpack_vrefbuffer* vbuf, + size_t ref_size, size_t chunk_size) +{ + size_t nfirst; + msgpack_iovec* array; + msgpack_vrefbuffer_chunk* chunk; + + if (ref_size == 0) { + ref_size = MSGPACK_VREFBUFFER_REF_SIZE; + } + if(chunk_size == 0) { + chunk_size = MSGPACK_VREFBUFFER_CHUNK_SIZE; + } + vbuf->chunk_size = chunk_size; + vbuf->ref_size = + ref_size > MSGPACK_PACKER_MAX_BUFFER_SIZE + 1 ? + ref_size : MSGPACK_PACKER_MAX_BUFFER_SIZE + 1 ; + + if((sizeof(msgpack_vrefbuffer_chunk) + chunk_size) < chunk_size) { + return false; + } + + nfirst = (sizeof(msgpack_iovec) < 72/2) ? + 72 / sizeof(msgpack_iovec) : 8; + + array = (msgpack_iovec*)malloc( + sizeof(msgpack_iovec) * nfirst); + if(array == NULL) { + return false; + } + + vbuf->tail = array; + vbuf->end = array + nfirst; + vbuf->array = array; + + chunk = (msgpack_vrefbuffer_chunk*)malloc( + sizeof(msgpack_vrefbuffer_chunk) + chunk_size); + if(chunk == NULL) { + free(array); + return false; + } + else { + msgpack_vrefbuffer_inner_buffer* const ib = &vbuf->inner_buffer; + + ib->free = chunk_size; + ib->ptr = ((char*)chunk) + sizeof(msgpack_vrefbuffer_chunk); + ib->head = chunk; + chunk->next = NULL; + + return true; + } +} + +void msgpack_vrefbuffer_destroy(msgpack_vrefbuffer* vbuf) +{ + msgpack_vrefbuffer_chunk* c = vbuf->inner_buffer.head; + while(true) { + msgpack_vrefbuffer_chunk* n = c->next; + free(c); + if(n != NULL) { + c = n; + } else { + break; + } + } + free(vbuf->array); +} + +void msgpack_vrefbuffer_clear(msgpack_vrefbuffer* vbuf) +{ + msgpack_vrefbuffer_chunk* c = vbuf->inner_buffer.head->next; + msgpack_vrefbuffer_chunk* n; + while(c != NULL) { + n = c->next; + free(c); + c = n; + } + + { + msgpack_vrefbuffer_inner_buffer* const ib = &vbuf->inner_buffer; + msgpack_vrefbuffer_chunk* chunk = ib->head; + chunk->next = NULL; + ib->free = vbuf->chunk_size; + ib->ptr = ((char*)chunk) + sizeof(msgpack_vrefbuffer_chunk); + + vbuf->tail = vbuf->array; + } +} + +int msgpack_vrefbuffer_append_ref(msgpack_vrefbuffer* vbuf, + const char* buf, size_t len) +{ + if(vbuf->tail == vbuf->end) { + const size_t nused = (size_t)(vbuf->tail - vbuf->array); + const size_t nnext = nused * 2; + + msgpack_iovec* nvec = (msgpack_iovec*)realloc( + vbuf->array, sizeof(msgpack_iovec)*nnext); + if(nvec == NULL) { + return -1; + } + + vbuf->array = nvec; + vbuf->end = nvec + nnext; + vbuf->tail = nvec + nused; + } + + vbuf->tail->iov_base = (char*)buf; + vbuf->tail->iov_len = len; + ++vbuf->tail; + + return 0; +} + +int msgpack_vrefbuffer_append_copy(msgpack_vrefbuffer* vbuf, + const char* buf, size_t len) +{ + msgpack_vrefbuffer_inner_buffer* const ib = &vbuf->inner_buffer; + char* m; + + if(ib->free < len) { + msgpack_vrefbuffer_chunk* chunk; + size_t sz = vbuf->chunk_size; + if(sz < len) { + sz = len; + } + + if((sizeof(msgpack_vrefbuffer_chunk) + sz) < sz){ + return -1; + } + chunk = (msgpack_vrefbuffer_chunk*)malloc( + sizeof(msgpack_vrefbuffer_chunk) + sz); + if(chunk == NULL) { + return -1; + } + + chunk->next = ib->head; + ib->head = chunk; + ib->free = sz; + ib->ptr = ((char*)chunk) + sizeof(msgpack_vrefbuffer_chunk); + } + + m = ib->ptr; + memcpy(m, buf, len); + ib->free -= len; + ib->ptr += len; + + if(vbuf->tail != vbuf->array && m == + (const char*)((vbuf->tail-1)->iov_base) + (vbuf->tail-1)->iov_len) { + (vbuf->tail-1)->iov_len += len; + return 0; + } else { + return msgpack_vrefbuffer_append_ref(vbuf, m, len); + } +} + +int msgpack_vrefbuffer_migrate(msgpack_vrefbuffer* vbuf, msgpack_vrefbuffer* to) +{ + size_t sz = vbuf->chunk_size; + msgpack_vrefbuffer_chunk* empty; + + if((sizeof(msgpack_vrefbuffer_chunk) + sz) < sz){ + return -1; + } + + empty = (msgpack_vrefbuffer_chunk*)malloc( + sizeof(msgpack_vrefbuffer_chunk) + sz); + if(empty == NULL) { + return -1; + } + + empty->next = NULL; + + { + const size_t nused = (size_t)(vbuf->tail - vbuf->array); + if(to->tail + nused < vbuf->end) { + msgpack_iovec* nvec; + const size_t tosize = (size_t)(to->tail - to->array); + const size_t reqsize = nused + tosize; + size_t nnext = (size_t)(to->end - to->array) * 2; + while(nnext < reqsize) { + size_t tmp_nnext = nnext * 2; + if (tmp_nnext <= nnext) { + nnext = reqsize; + break; + } + nnext = tmp_nnext; + } + + nvec = (msgpack_iovec*)realloc( + to->array, sizeof(msgpack_iovec)*nnext); + if(nvec == NULL) { + free(empty); + return -1; + } + + to->array = nvec; + to->end = nvec + nnext; + to->tail = nvec + tosize; + } + + memcpy(to->tail, vbuf->array, sizeof(msgpack_iovec)*nused); + + to->tail += nused; + vbuf->tail = vbuf->array; + + { + msgpack_vrefbuffer_inner_buffer* const ib = &vbuf->inner_buffer; + msgpack_vrefbuffer_inner_buffer* const toib = &to->inner_buffer; + + msgpack_vrefbuffer_chunk* last = ib->head; + while(last->next != NULL) { + last = last->next; + } + last->next = toib->head; + toib->head = ib->head; + + if(toib->free < ib->free) { + toib->free = ib->free; + toib->ptr = ib->ptr; + } + + ib->head = empty; + ib->free = sz; + ib->ptr = ((char*)empty) + sizeof(msgpack_vrefbuffer_chunk); + } + } + + return 0; +} diff --git a/local_pod_repo/msgpack-c/msgpack-c/src/zone.c b/local_pod_repo/msgpack-c/msgpack-c/src/zone.c new file mode 100644 index 0000000..f4fb043 --- /dev/null +++ b/local_pod_repo/msgpack-c/msgpack-c/src/zone.c @@ -0,0 +1,222 @@ +/* + * MessagePack for C memory pool implementation + * + * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +#include "zone.h" +#include +#include + +struct msgpack_zone_chunk { + struct msgpack_zone_chunk* next; + /* data ... */ +}; + +static inline bool init_chunk_list(msgpack_zone_chunk_list* cl, size_t chunk_size) +{ + msgpack_zone_chunk* chunk = (msgpack_zone_chunk*)malloc( + sizeof(msgpack_zone_chunk) + chunk_size); + if(chunk == NULL) { + return false; + } + + cl->head = chunk; + cl->free = chunk_size; + cl->ptr = ((char*)chunk) + sizeof(msgpack_zone_chunk); + chunk->next = NULL; + + return true; +} + +static inline void destroy_chunk_list(msgpack_zone_chunk_list* cl) +{ + msgpack_zone_chunk* c = cl->head; + while(true) { + msgpack_zone_chunk* n = c->next; + free(c); + if(n != NULL) { + c = n; + } else { + break; + } + } +} + +static inline void clear_chunk_list(msgpack_zone_chunk_list* cl, size_t chunk_size) +{ + msgpack_zone_chunk* c = cl->head; + while(true) { + msgpack_zone_chunk* n = c->next; + if(n != NULL) { + free(c); + c = n; + } else { + cl->head = c; + break; + } + } + cl->head->next = NULL; + cl->free = chunk_size; + cl->ptr = ((char*)cl->head) + sizeof(msgpack_zone_chunk); +} + +void* msgpack_zone_malloc_expand(msgpack_zone* zone, size_t size) +{ + msgpack_zone_chunk_list* const cl = &zone->chunk_list; + msgpack_zone_chunk* chunk; + + size_t sz = zone->chunk_size; + + while(sz < size) { + size_t tmp_sz = sz * 2; + if (tmp_sz <= sz) { + sz = size; + break; + } + sz = tmp_sz; + } + + chunk = (msgpack_zone_chunk*)malloc( + sizeof(msgpack_zone_chunk) + sz); + if (chunk == NULL) { + return NULL; + } + else { + char* ptr = ((char*)chunk) + sizeof(msgpack_zone_chunk); + chunk->next = cl->head; + cl->head = chunk; + cl->free = sz - size; + cl->ptr = ptr + size; + + return ptr; + } +} + + +static inline void init_finalizer_array(msgpack_zone_finalizer_array* fa) +{ + fa->tail = NULL; + fa->end = NULL; + fa->array = NULL; +} + +static inline void call_finalizer_array(msgpack_zone_finalizer_array* fa) +{ + msgpack_zone_finalizer* fin = fa->tail; + for(; fin != fa->array; --fin) { + (*(fin-1)->func)((fin-1)->data); + } +} + +static inline void destroy_finalizer_array(msgpack_zone_finalizer_array* fa) +{ + call_finalizer_array(fa); + free(fa->array); +} + +static inline void clear_finalizer_array(msgpack_zone_finalizer_array* fa) +{ + call_finalizer_array(fa); + fa->tail = fa->array; +} + +bool msgpack_zone_push_finalizer_expand(msgpack_zone* zone, + void (*func)(void* data), void* data) +{ + msgpack_zone_finalizer_array* const fa = &zone->finalizer_array; + msgpack_zone_finalizer* tmp; + + const size_t nused = (size_t)(fa->end - fa->array); + + size_t nnext; + if(nused == 0) { + nnext = (sizeof(msgpack_zone_finalizer) < 72/2) ? + 72 / sizeof(msgpack_zone_finalizer) : 8; + + } else { + nnext = nused * 2; + } + + tmp = (msgpack_zone_finalizer*)realloc(fa->array, + sizeof(msgpack_zone_finalizer) * nnext); + if(tmp == NULL) { + return false; + } + + fa->array = tmp; + fa->end = tmp + nnext; + fa->tail = tmp + nused; + + fa->tail->func = func; + fa->tail->data = data; + + ++fa->tail; + + return true; +} + + +bool msgpack_zone_is_empty(msgpack_zone* zone) +{ + msgpack_zone_chunk_list* const cl = &zone->chunk_list; + msgpack_zone_finalizer_array* const fa = &zone->finalizer_array; + return cl->free == zone->chunk_size && cl->head->next == NULL && + fa->tail == fa->array; +} + + +void msgpack_zone_destroy(msgpack_zone* zone) +{ + destroy_finalizer_array(&zone->finalizer_array); + destroy_chunk_list(&zone->chunk_list); +} + +void msgpack_zone_clear(msgpack_zone* zone) +{ + clear_finalizer_array(&zone->finalizer_array); + clear_chunk_list(&zone->chunk_list, zone->chunk_size); +} + +bool msgpack_zone_init(msgpack_zone* zone, size_t chunk_size) +{ + zone->chunk_size = chunk_size; + + if(!init_chunk_list(&zone->chunk_list, chunk_size)) { + return false; + } + + init_finalizer_array(&zone->finalizer_array); + + return true; +} + +msgpack_zone* msgpack_zone_new(size_t chunk_size) +{ + msgpack_zone* zone = (msgpack_zone*)malloc( + sizeof(msgpack_zone)); + if(zone == NULL) { + return NULL; + } + + zone->chunk_size = chunk_size; + + if(!init_chunk_list(&zone->chunk_list, chunk_size)) { + free(zone); + return NULL; + } + + init_finalizer_array(&zone->finalizer_array); + + return zone; +} + +void msgpack_zone_free(msgpack_zone* zone) +{ + if(zone == NULL) { return; } + msgpack_zone_destroy(zone); + free(zone); +} diff --git a/local_pod_repo/objcTox/.gitignore b/local_pod_repo/objcTox/.gitignore new file mode 100644 index 0000000..db967ae --- /dev/null +++ b/local_pod_repo/objcTox/.gitignore @@ -0,0 +1,31 @@ +# Xcode +build/* +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +*.xcworkspace +!default.xcworkspace +xcuserdata +profile +*.moved-aside +*.mode1 +*.perspective +xcshareddata + +# Exclude temp nibs and swap files +*~.nib +*.swp + +# Exclude OS X folder attributes +.DS_Store +# +# Exclude AppCode files +.idea + +Pods +Podfile.lock diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioEngine+Private.h b/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioEngine+Private.h new file mode 100644 index 0000000..06f8bc3 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioEngine+Private.h @@ -0,0 +1,33 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTAudioEngine.h" +#import "TPCircularBuffer.h" + +@import AVFoundation; + +extern int kBufferLength; +extern int kNumberOfChannels; +extern int kDefaultSampleRate; +extern int kSampleCount_incoming_audio; +extern int kSampleCount_outgoing_audio; +extern int kBitsPerByte; +extern int kFramesPerPacket; +extern int kBytesPerSample; +extern int kNumberOfAudioQueueBuffers; + +@class OCTAudioQueue; +@interface OCTAudioEngine () + +#if ! TARGET_OS_IPHONE +@property (strong, nonatomic, readonly) NSString *inputDeviceID; +@property (strong, nonatomic, readonly) NSString *outputDeviceID; +#endif + +@property (nonatomic, strong) OCTAudioQueue *outputQueue; +@property (nonatomic, strong) OCTAudioQueue *inputQueue; + +- (void)makeQueues:(NSError **)error; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioEngine.h b/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioEngine.h new file mode 100644 index 0000000..043b758 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioEngine.h @@ -0,0 +1,92 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTToxAV.h" + +@interface OCTAudioEngine : NSObject + +@property (weak, nonatomic) OCTToxAV *toxav; +@property (nonatomic, assign) OCTToxFriendNumber friendNumber; + +/** + * YES to send audio frames over to tox, otherwise NO. + * Default is YES. + */ +@property (nonatomic, assign) BOOL enableMicrophone; + +/** + * Starts the Audio Processing Graph. + * @param error Pointer to error object. + * @return YES on success, otherwise NO. + */ +- (BOOL)startAudioFlow:(NSError **)error; + +/** + * Stops the Audio Processing Graph. + * @param error Pointer to error object. + * @return YES on success, otherwise NO. + */ +- (BOOL)stopAudioFlow:(NSError **)error; + +/** + * Checks if the Audio Graph is processing. + * @param error Pointer to error object. + * @return YES if Audio Graph is running, otherwise NO. + */ +- (BOOL)isAudioRunning:(NSError **)error; + +/** + * Provide audio data that will be placed in buffer to be played in speaker. + * @param pcm An array of audio samples (sample_count * channels elements). + * @param sampleCount The number of audio samples per channel in the PCM array. + * @param channels Number of audio channels. + * @param sampleRate Sampling rate used in this frame. + */ +- (void)provideAudioFrames:(OCTToxAVPCMData *)pcm sampleCount:(OCTToxAVSampleCount)sampleCount channels:(OCTToxAVChannels)channels sampleRate:(OCTToxAVSampleRate)sampleRate fromFriend:(OCTToxFriendNumber)friendNumber; + +@end + +#if ! TARGET_OS_IPHONE + +@interface OCTAudioEngine (MacDevice) + +/** + * Set the input device (not available on iOS). + * @param inputDeviceID Core Audio's unique ID for the device. See + * public OCTSubmanagerCalls.h for what these should be. + * @param error If this method returns NO, contains more information on the + * underlying error. + * @return YES on success, otherwise NO. + */ +- (BOOL)setInputDeviceID:(NSString *)inputDeviceID error:(NSError **)error; + +/** + * Set the output device (not available on iOS). + * @param outputDeviceID Core Audio's unique ID for the device. See + * public OCTSubmanagerCalls.h for what these should be. + * @param error If this method returns NO, contains more information on the + * underlying error. + * @return YES on success, otherwise NO. + */ +- (BOOL)setOutputDeviceID:(NSString *)outputDeviceID error:(NSError **)error; + +@end + +#else + +@interface OCTAudioEngine (iOSDevice) + +/** + * Switch the output to/from the device's speaker. + * @param speaker Whether we should use the speaker for output. + * @param error If this method returns NO, contains more information on the + * underlying error. + * @return YES on success, otherwise NO. + */ +- (BOOL)routeAudioToSpeaker:(BOOL)speaker error:(NSError **)error; + +@end + +#endif diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioEngine.m b/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioEngine.m new file mode 100644 index 0000000..642e83f --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioEngine.m @@ -0,0 +1,191 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTAudioEngine+Private.h" +#import "OCTToxAV+Private.h" +#import "OCTAudioQueue.h" +#import "OCTLogging.h" + +@import AVFoundation; + +@interface OCTAudioEngine () + +@property (nonatomic, assign) OCTToxAVSampleRate outputSampleRate; +@property (nonatomic, assign) OCTToxAVChannels outputNumberOfChannels; + +@end + +@implementation OCTAudioEngine + +#pragma mark - LifeCycle +- (instancetype)init +{ + self = [super init]; + if (! self) { + return nil; + } + + _outputSampleRate = kDefaultSampleRate; + _outputNumberOfChannels = kNumberOfChannels; + _enableMicrophone = YES; + + return self; +} + +#pragma mark - SPI + +#if ! TARGET_OS_IPHONE + +- (BOOL)setInputDeviceID:(NSString *)inputDeviceID error:(NSError **)error +{ + // if audio is not active, we can't really be bothered to check that the + // device exists; we rely on startAudioFlow: to fail later. + if (! self.inputQueue) { + _inputDeviceID = inputDeviceID; + return YES; + } + + if ([self.inputQueue setDeviceID:inputDeviceID error:error]) { + _inputDeviceID = inputDeviceID; + return YES; + } + + return NO; +} + +- (BOOL)setOutputDeviceID:(NSString *)outputDeviceID error:(NSError **)error +{ + if (! self.outputQueue) { + _outputDeviceID = outputDeviceID; + return YES; + } + + if ([self.outputQueue setDeviceID:outputDeviceID error:error]) { + _outputDeviceID = outputDeviceID; + return YES; + } + + return NO; +} + +#else + +- (BOOL)routeAudioToSpeaker:(BOOL)speaker error:(NSError **)error +{ + AVAudioSession *session = [AVAudioSession sharedInstance]; + + AVAudioSessionPortOverride override; + if (speaker) { + override = AVAudioSessionPortOverrideSpeaker; + } + else { + override = AVAudioSessionPortOverrideNone; + } + + return [session overrideOutputAudioPort:override error:error]; +} + +#endif + +- (BOOL)startAudioFlow:(NSError **)error +{ +#if TARGET_OS_IPHONE + AVAudioSession *session = [AVAudioSession sharedInstance]; + + if (! ([session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:error] && + [session setPreferredSampleRate:kDefaultSampleRate error:error] && + [session setMode:AVAudioSessionModeVoiceChat error:error] && + [session setActive:YES error:error])) { + return NO; + } +#endif + + [self makeQueues:error]; + + if (! (self.outputQueue && self.inputQueue)) { + return NO; + } + + OCTAudioEngine *__weak welf = self; + self.inputQueue.sendDataBlock = ^(void *data, OCTToxAVSampleCount samples, OCTToxAVSampleRate rate, OCTToxAVChannels channelCount) { + OCTAudioEngine *aoi = welf; + + if (aoi.enableMicrophone) { + // TOXAUDIO: -outgoing-audio- + [aoi.toxav sendAudioFrame:data + sampleCount:samples + channels:channelCount + sampleRate:rate + toFriend:aoi.friendNumber + error:nil]; + } + }; + + // TOXAUDIO: -incoming-audio- + [self.outputQueue updateSampleRate:self.outputSampleRate numberOfChannels:self.outputNumberOfChannels error:nil]; + + if (! [self.inputQueue begin:error] || ! [self.outputQueue begin:error]) { + return NO; + } + + return YES; +} + +- (BOOL)stopAudioFlow:(NSError **)error +{ + if (! [self.inputQueue stop:error] || ! [self.outputQueue stop:error]) { + return NO; + } + +#if TARGET_OS_IPHONE + AVAudioSession *session = [AVAudioSession sharedInstance]; + BOOL ret = [session setActive:NO error:error]; +#else + BOOL ret = YES; +#endif + + self.inputQueue = nil; + self.outputQueue = nil; + return ret; +} + +- (void)provideAudioFrames:(OCTToxAVPCMData *)pcm sampleCount:(OCTToxAVSampleCount)sampleCount channels:(OCTToxAVChannels)channels sampleRate:(OCTToxAVSampleRate)sampleRate fromFriend:(OCTToxFriendNumber)friendNumber +{ + // TOXAUDIO: -incoming-audio- + int32_t len = (int32_t)(channels * sampleCount * sizeof(OCTToxAVPCMData)); + TPCircularBuffer *buf = [self.outputQueue getBufferPointer]; + if (buf) { + TPCircularBufferProduceBytes(buf, pcm, len); + } + + if ((self.outputSampleRate != sampleRate) || (self.outputNumberOfChannels != channels)) { + // failure is logged by OCTAudioQueue. + [self.outputQueue updateSampleRate:sampleRate numberOfChannels:channels error:nil]; + + self.outputSampleRate = sampleRate; + self.outputNumberOfChannels = channels; + } +} + +- (BOOL)isAudioRunning:(NSError **)error +{ + return self.inputQueue.running && self.outputQueue.running; +} + +- (void)makeQueues:(NSError **)error +{ + // Note: OCTAudioQueue handles the case where the device ids are nil - in that case + // we don't set the device explicitly, and the default is used. +#if TARGET_OS_IPHONE + // TOXAUDIO: -incoming-audio- + self.outputQueue = [[OCTAudioQueue alloc] initWithOutputDeviceID:nil error:error]; + // TOXAUDIO: -outgoing-audio- + self.inputQueue = [[OCTAudioQueue alloc] initWithInputDeviceID:nil error:error]; +#else + self.outputQueue = [[OCTAudioQueue alloc] initWithOutputDeviceID:self.outputDeviceID error:error]; + self.inputQueue = [[OCTAudioQueue alloc] initWithInputDeviceID:self.inputDeviceID error:error]; +#endif +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioQueue.h b/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioQueue.h new file mode 100644 index 0000000..8935430 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioQueue.h @@ -0,0 +1,74 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "TPCircularBuffer.h" + +@import AudioToolbox; + +#pragma mark - C declarations + +extern OSStatus (*_AudioQueueAllocateBuffer)(AudioQueueRef inAQ, + UInt32 inBufferByteSize, + AudioQueueBufferRef *outBuffer); +extern OSStatus (*_AudioQueueDispose)(AudioQueueRef inAQ, + Boolean inImmediate); +extern OSStatus (*_AudioQueueEnqueueBuffer)(AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer, + UInt32 inNumPacketDescs, + const AudioStreamPacketDescription *inPacketDescs); +extern OSStatus (*_AudioQueueFreeBuffer)(AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer); +extern OSStatus (*_AudioQueueNewInput)(const AudioStreamBasicDescription *inFormat, + AudioQueueInputCallback inCallbackProc, + void *inUserData, + CFRunLoopRef inCallbackRunLoop, + CFStringRef inCallbackRunLoopMode, + UInt32 inFlags, + AudioQueueRef *outAQ); +extern OSStatus (*_AudioQueueNewOutput)(const AudioStreamBasicDescription *inFormat, + AudioQueueOutputCallback inCallbackProc, + void *inUserData, + CFRunLoopRef inCallbackRunLoop, + CFStringRef inCallbackRunLoopMode, + UInt32 inFlags, + AudioQueueRef *outAQ); +extern OSStatus (*_AudioQueueSetProperty)(AudioQueueRef inAQ, + AudioQueuePropertyID inID, + const void *inData, + UInt32 inDataSize); +extern OSStatus (*_AudioQueueStart)(AudioQueueRef inAQ, + const AudioTimeStamp *inStartTime); +extern OSStatus (*_AudioQueueStop)(AudioQueueRef inAQ, + Boolean inImmediate); +#if ! TARGET_OS_IPHONE +extern OSStatus (*_AudioObjectGetPropertyData)(AudioObjectID inObjectID, + const AudioObjectPropertyAddress *inAddress, + UInt32 inQualifierDataSize, + const void *inQualifierData, + UInt32 *ioDataSize, + void *outData); +#endif + +/* no idea what to name this thing, so here it is */ +@interface OCTAudioQueue : NSObject + +@property (strong, nonatomic, readonly) NSString *deviceID; +@property (copy, nonatomic) void (^sendDataBlock)(void *, OCTToxAVSampleCount, OCTToxAVSampleRate, OCTToxAVChannels); +@property (assign, nonatomic, readonly) BOOL running; + +- (instancetype)initWithInputDeviceID:(NSString *)devID error:(NSError **)error; +- (instancetype)initWithOutputDeviceID:(NSString *)devID error:(NSError **)error; + +- (TPCircularBuffer *)getBufferPointer; +- (BOOL)updateSampleRate:(OCTToxAVSampleRate)sampleRate numberOfChannels:(OCTToxAVChannels)numberOfChannels error:(NSError **)err; + +#if ! TARGET_OS_IPHONE +- (BOOL)setDeviceID:(NSString *)deviceID error:(NSError **)err; +#endif + +- (BOOL)begin:(NSError **)error; +- (BOOL)stop:(NSError **)error; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioQueue.m b/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioQueue.m new file mode 100644 index 0000000..2b5ae2e --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Audio/OCTAudioQueue.m @@ -0,0 +1,425 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTToxAV.h" +#import "OCTAudioQueue.h" +#import "TPCircularBuffer.h" +#import "OCTLogging.h" + +@import AVFoundation; +@import AudioToolbox; + +const int kBufferLength = 384000; +const int kNumberOfChannels = 1; +const int kDefaultSampleRate = 48000; +const int kSampleCount_incoming_audio = 1920; +const int kSampleCount_outgoing_audio = (1920 / 2); +const int kBitsPerByte = 8; +const int kFramesPerPacket = 1; +// if you make this too small, the output queue will silently not play, +// but you will still get fill callbacks; it's really weird +const int kFramesPerOutputBuffer_incoming_audio = kSampleCount_incoming_audio / 4; +const int kFramesPerOutputBuffer_outgoing_audio = kSampleCount_outgoing_audio / 4; +const int kBytesPerSample = sizeof(SInt16); +const int kNumberOfAudioQueueBuffers = 8; + +OSStatus (*_AudioQueueAllocateBuffer)(AudioQueueRef inAQ, + UInt32 inBufferByteSize, + AudioQueueBufferRef *outBuffer) = AudioQueueAllocateBuffer; +OSStatus (*_AudioQueueDispose)(AudioQueueRef inAQ, + Boolean inImmediate) = AudioQueueDispose; +OSStatus (*_AudioQueueEnqueueBuffer)(AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer, + UInt32 inNumPacketDescs, + const AudioStreamPacketDescription *inPacketDescs) = AudioQueueEnqueueBuffer; +OSStatus (*_AudioQueueFreeBuffer)(AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer) = AudioQueueFreeBuffer; +OSStatus (*_AudioQueueNewInput)(const AudioStreamBasicDescription *inFormat, + AudioQueueInputCallback inCallbackProc, + void *inUserData, + CFRunLoopRef inCallbackRunLoop, + CFStringRef inCallbackRunLoopMode, + UInt32 inFlags, + AudioQueueRef *outAQ) = AudioQueueNewInput; +OSStatus (*_AudioQueueNewOutput)(const AudioStreamBasicDescription *inFormat, + AudioQueueOutputCallback inCallbackProc, + void *inUserData, + CFRunLoopRef inCallbackRunLoop, + CFStringRef inCallbackRunLoopMode, + UInt32 inFlags, + AudioQueueRef *outAQ) = AudioQueueNewOutput; +OSStatus (*_AudioQueueSetProperty)(AudioQueueRef inAQ, + AudioQueuePropertyID inID, + const void *inData, + UInt32 inDataSize) = AudioQueueSetProperty; +OSStatus (*_AudioQueueStart)(AudioQueueRef inAQ, + const AudioTimeStamp *inStartTime) = AudioQueueStart; +OSStatus (*_AudioQueueStop)(AudioQueueRef inAQ, + Boolean inImmediate) = AudioQueueStop; +#if ! TARGET_OS_IPHONE +OSStatus (*_AudioObjectGetPropertyData)(AudioObjectID inObjectID, + const AudioObjectPropertyAddress *inAddress, + UInt32 inQualifierDataSize, + const void *inQualifierData, + UInt32 *ioDataSize, + void *outData) = AudioObjectGetPropertyData; +#endif + +static NSError *OCTErrorFromCoreAudioCode(OSStatus resultCode) +{ + return [NSError errorWithDomain:NSOSStatusErrorDomain + code:resultCode + userInfo:@{NSLocalizedDescriptionKey : @"Consult the CoreAudio header files/google for the meaning of the error code."}]; +} + +#if ! TARGET_OS_IPHONE +static NSString *OCTGetSystemAudioDevice(AudioObjectPropertySelector sel, NSError **err) +{ + AudioDeviceID devID = 0; + OSStatus ok = 0; + UInt32 size = sizeof(AudioDeviceID); + AudioObjectPropertyAddress address = { + .mSelector = sel, + .mScope = kAudioObjectPropertyScopeGlobal, + .mElement = kAudioObjectPropertyElementMaster + }; + + ok = _AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &size, &devID); + if (ok != kAudioHardwareNoError) { + OCTLogCCError(@"failed AudioObjectGetPropertyData for system object: %d! Crash may or may not be imminent", ok); + if (err) { + *err = OCTErrorFromCoreAudioCode(ok); + } + return nil; + } + + address.mSelector = kAudioDevicePropertyDeviceUID; + CFStringRef unique = NULL; + size = sizeof(unique); + ok = _AudioObjectGetPropertyData(devID, &address, 0, NULL, &size, &unique); + if (ok != kAudioHardwareNoError) { + OCTLogCCError(@"failed AudioObjectGetPropertyData for selected device: %d! Crash may or may not be imminent", ok); + if (err) { + *err = OCTErrorFromCoreAudioCode(ok); + } + return nil; + } + + return (__bridge NSString *)unique; +} +#endif + +@interface OCTAudioQueue () + +// use this to track what nil means in terms of audio device +@property (assign, nonatomic) BOOL isOutput; +@property (assign, nonatomic) AudioStreamBasicDescription streamFmt; +@property (assign, nonatomic) AudioQueueRef audioQueue; +@property (assign, nonatomic) TPCircularBuffer buffer; +@property (assign, nonatomic) BOOL running; + +@end + +@implementation OCTAudioQueue +{ + AudioQueueBufferRef _AQBuffers[kNumberOfAudioQueueBuffers]; +} + +- (instancetype)initWithDeviceID:(NSString *)devID isOutput:(BOOL)output error:(NSError **)error +{ +#if TARGET_OS_IPHONE + AVAudioSession *session = [AVAudioSession sharedInstance]; + _streamFmt.mSampleRate = session.sampleRate; +#else + _streamFmt.mSampleRate = kDefaultSampleRate; +#endif + _streamFmt.mFormatID = kAudioFormatLinearPCM; + _streamFmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; + _streamFmt.mChannelsPerFrame = kNumberOfChannels; + _streamFmt.mBytesPerFrame = kBytesPerSample * kNumberOfChannels; + _streamFmt.mBitsPerChannel = kBitsPerByte * kBytesPerSample; + _streamFmt.mFramesPerPacket = kFramesPerPacket; + _streamFmt.mBytesPerPacket = kBytesPerSample * kNumberOfChannels * kFramesPerPacket; + _isOutput = output; + _deviceID = devID; + + TPCircularBufferInit(&_buffer, kBufferLength); + OSStatus res = [self createAudioQueue]; + if (res != 0) { + if (error) { + *error = OCTErrorFromCoreAudioCode(res); + } + return nil; + } + + return self; +} + + +- (instancetype)initWithInputDeviceID:(NSString *)devID error:(NSError **)error +{ + // TOXAUDIO: -outgoing-audio- + return [self initWithDeviceID:devID isOutput:NO error:error]; +} + +- (instancetype)initWithOutputDeviceID:(NSString *)devID error:(NSError **)error +{ + // TOXAUDIO: -incoming-audio- + return [self initWithDeviceID:devID isOutput:YES error:error]; +} + +- (void)dealloc +{ + if (self.running) { + [self stop:nil]; + } + + if (self.audioQueue) { + _AudioQueueDispose(self.audioQueue, true); + } + + TPCircularBufferCleanup(&_buffer); +} + +- (OSStatus)createAudioQueue +{ + OSStatus err; + if (self.isOutput) { + // TOXAUDIO: -incoming-audio- + err = _AudioQueueNewOutput(&_streamFmt, (void *)&FillOutputBuffer, (__bridge void *)self, NULL, kCFRunLoopCommonModes, 0, &_audioQueue); + } + else { + // TOXAUDIO: -outgoing-audio- + err = _AudioQueueNewInput(&_streamFmt, (void *)&InputAvailable, (__bridge void *)self, NULL, kCFRunLoopCommonModes, 0, &_audioQueue); + } + + if (err != 0) { + return err; + } + + if (_deviceID) { + err = _AudioQueueSetProperty(self.audioQueue, kAudioQueueProperty_CurrentDevice, &_deviceID, sizeof(CFStringRef)); + } + + return err; +} + +- (BOOL)begin:(NSError **)error +{ + OCTLogVerbose(@"begin"); + + if (! self.audioQueue) { + OSStatus res = [self createAudioQueue]; + if (res != 0) { + OCTLogError(@"Can't create the audio queue again after a presumably failed updateSampleRate:... call."); + if (error) { + *error = OCTErrorFromCoreAudioCode(res); + } + return NO; + } + } + + for (int i = 0; i < kNumberOfAudioQueueBuffers; ++i) { + if (self.isOutput) { + // TOXAUDIO: -incoming-audio- + _AudioQueueAllocateBuffer(self.audioQueue, kBytesPerSample * kNumberOfChannels * kFramesPerOutputBuffer_incoming_audio, &(_AQBuffers[i])); + } else { + // TOXAUDIO: -outgoing-audio- + _AudioQueueAllocateBuffer(self.audioQueue, kBytesPerSample * kNumberOfChannels * kFramesPerOutputBuffer_outgoing_audio, &(_AQBuffers[i])); + } + _AudioQueueEnqueueBuffer(self.audioQueue, _AQBuffers[i], 0, NULL); + if (self.isOutput) { + // TOXAUDIO: -outgoing-audio- + // For some reason we have to fill it with zero or the callback never gets called. + FillOutputBuffer(self, self.audioQueue, _AQBuffers[i]); + } + } + + OCTLogVerbose(@"Allocated buffers; starting now!"); + OSStatus res = _AudioQueueStart(self.audioQueue, NULL); + if (res != 0) { + if (error) { + *error = OCTErrorFromCoreAudioCode(res); + } + return NO; + } + + self.running = YES; + return YES; +} + +- (BOOL)stop:(NSError **)error +{ + OCTLogVerbose(@"stop"); + OSStatus res = _AudioQueueStop(self.audioQueue, true); + if (res != 0) { + if (error) { + *error = OCTErrorFromCoreAudioCode(res); + } + return NO; + } + + for (int i = 0; i < kNumberOfAudioQueueBuffers; ++i) { + _AudioQueueFreeBuffer(self.audioQueue, _AQBuffers[i]); + } + + OCTLogVerbose(@"Freed buffers"); + self.running = NO; + return YES; +} + +- (TPCircularBuffer *)getBufferPointer +{ + return &_buffer; +} + +- (BOOL)setDeviceID:(NSString *)deviceID error:(NSError **)err +{ +#if ! TARGET_OS_IPHONE + if (deviceID == nil) { + OCTLogVerbose(@"using the default device because nil passed to OCTAudioQueue setDeviceID:"); + deviceID = OCTGetSystemAudioDevice(self.isOutput ? + kAudioHardwarePropertyDefaultOutputDevice : + kAudioHardwarePropertyDefaultInputDevice, err); + if (! deviceID) { + return NO; + } + } + + BOOL needToRestart = self.running; + + // we need to pause the queue for a sec + if (needToRestart && ! [self stop:err]) { + return NO; + } + + OSStatus ok = _AudioQueueSetProperty(self.audioQueue, kAudioQueueProperty_CurrentDevice, &deviceID, sizeof(CFStringRef)); + + if (ok != 0) { + OCTLogError(@"setDeviceID: Error while live setting device to '%@': %d", deviceID, ok); + if (err) { + *err = OCTErrorFromCoreAudioCode(ok); + } + } + else { + _deviceID = deviceID; + OCTLogVerbose(@"Successfully set the device id to %@", deviceID); + } + + if ((needToRestart && ! [self begin:err]) || (ok != 0)) { + return NO; + } + else { + return YES; + } +#else + return NO; +#endif +} + +- (BOOL)updateSampleRate:(OCTToxAVSampleRate)sampleRate numberOfChannels:(OCTToxAVChannels)numberOfChannels error:(NSError **)err +{ + OCTLogVerbose(@"updateSampleRate %u, %u", sampleRate, (unsigned int)numberOfChannels); + + BOOL needToRestart = self.running; + + if (needToRestart && ! [self stop:err]) { + return NO; + } + + AudioQueueRef aq = self.audioQueue; + self.audioQueue = nil; + _AudioQueueDispose(aq, true); + + _streamFmt.mSampleRate = sampleRate; + _streamFmt.mChannelsPerFrame = numberOfChannels; + _streamFmt.mBytesPerFrame = kBytesPerSample * numberOfChannels; + _streamFmt.mBytesPerPacket = kBytesPerSample * numberOfChannels * kFramesPerPacket; + + OSStatus res = [self createAudioQueue]; + if (res != 0) { + OCTLogError(@"oops, could not recreate the audio queue: %d after samplerate/nc change. enjoy your overflowing buffer", (int)res); + if (err) { + *err = OCTErrorFromCoreAudioCode(res); + } + return NO; + } + else if (needToRestart) { + return [self begin:err]; + } + else { + return YES; + } +} + +// avoid annoying bridge cast in 1st param! +static void InputAvailable(OCTAudioQueue *__unsafe_unretained context, + AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer, + const AudioTimeStamp *inStartTime, + UInt32 inNumPackets, + const AudioStreamPacketDescription *inPacketDesc) +{ + TPCircularBufferProduceBytes(&(context->_buffer), + inBuffer->mAudioData, + inBuffer->mAudioDataByteSize); + + int32_t availableBytesToConsume; + void *tail = TPCircularBufferTail(&context->_buffer, &availableBytesToConsume); + + // TOXAUDIO: -outgoing-audio- + int32_t minimalBytesToConsume = kSampleCount_outgoing_audio * kNumberOfChannels * sizeof(SInt16); + + if (context.isOutput) { + // TOXAUDIO: -incoming-audio- + minimalBytesToConsume = kSampleCount_incoming_audio * kNumberOfChannels * sizeof(SInt16); + } + + int32_t cyclesToConsume = availableBytesToConsume / minimalBytesToConsume; + + for (int32_t i = 0; i < cyclesToConsume; i++) { + if (context.isOutput) { + // TOXAUDIO: -incoming-audio- + context.sendDataBlock(tail, kSampleCount_incoming_audio, context.streamFmt.mSampleRate, kNumberOfChannels); + } else { + // TOXAUDIO: -outgoing-audio- + context.sendDataBlock(tail, kSampleCount_outgoing_audio, context.streamFmt.mSampleRate, kNumberOfChannels); + } + TPCircularBufferConsume(&context->_buffer, minimalBytesToConsume); + tail = TPCircularBufferTail(&context->_buffer, &availableBytesToConsume); + } + + _AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL); +} + +static void FillOutputBuffer(OCTAudioQueue *__unsafe_unretained context, + AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer) +{ + int32_t targetBufferSize = inBuffer->mAudioDataBytesCapacity; + SInt16 *targetBuffer = inBuffer->mAudioData; + + int32_t availableBytes; + SInt16 *buffer = TPCircularBufferTail(&context->_buffer, &availableBytes); + + if (buffer) { + uint32_t cpy = MIN(availableBytes, targetBufferSize); + memcpy(targetBuffer, buffer, cpy); + TPCircularBufferConsume(&context->_buffer, cpy); + + if (cpy != targetBufferSize) { + memset(targetBuffer + cpy, 0, targetBufferSize - cpy); + OCTLogCCWarn(@"warning not enough frames!!!"); + } + inBuffer->mAudioDataByteSize = targetBufferSize; + } + else { + memset(targetBuffer, 0, targetBufferSize); + inBuffer->mAudioDataByteSize = targetBufferSize; + } + + _AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL); +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Configuration/OCTDefaultFileStorage.m b/local_pod_repo/objcTox/Classes/Private/Manager/Configuration/OCTDefaultFileStorage.m new file mode 100644 index 0000000..23980cb --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Configuration/OCTDefaultFileStorage.m @@ -0,0 +1,77 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTDefaultFileStorage.h" + +@interface OCTDefaultFileStorage () + +@property (copy, nonatomic) NSString *saveFileName; +@property (copy, nonatomic) NSString *baseDirectory; +@property (copy, nonatomic) NSString *temporaryDirectory; + +@end + +@implementation OCTDefaultFileStorage + +#pragma mark - Lifecycle + +- (instancetype)initWithBaseDirectory:(NSString *)baseDirectory temporaryDirectory:(NSString *)temporaryDirectory +{ + return [self initWithToxSaveFileName:nil baseDirectory:baseDirectory temporaryDirectory:temporaryDirectory]; +} + +- (instancetype)initWithToxSaveFileName:(NSString *)saveFileName + baseDirectory:(NSString *)baseDirectory + temporaryDirectory:(NSString *)temporaryDirectory +{ + self = [super init]; + + if (! self) { + return nil; + } + + if (! saveFileName) { + saveFileName = @"save"; + } + + self.saveFileName = [saveFileName stringByAppendingString:@".tox"]; + self.baseDirectory = baseDirectory; + self.temporaryDirectory = temporaryDirectory; + + return self; +} + +#pragma mark - OCTFileStorageProtocol + +- (NSString *)pathForToxSaveFile +{ + return [self.baseDirectory stringByAppendingPathComponent:self.saveFileName]; +} + +- (NSString *)pathForDatabase +{ + return [self.baseDirectory stringByAppendingPathComponent:@"database"]; +} + +- (NSString *)pathForDatabaseEncryptionKey +{ + return [self.baseDirectory stringByAppendingPathComponent:@"database.encryptionkey"]; +} + +- (NSString *)pathForDownloadedFilesDirectory +{ + return [self.baseDirectory stringByAppendingPathComponent:@"files"]; +} + +- (NSString *)pathForUploadedFilesDirectory +{ + return [self.baseDirectory stringByAppendingPathComponent:@"files"]; +} + +- (NSString *)pathForTemporaryFilesDirectory +{ + return self.temporaryDirectory; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Configuration/OCTManagerConfiguration.m b/local_pod_repo/objcTox/Classes/Private/Manager/Configuration/OCTManagerConfiguration.m new file mode 100644 index 0000000..c4c0bd7 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Configuration/OCTManagerConfiguration.m @@ -0,0 +1,51 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTManagerConfiguration.h" +#import "OCTDefaultFileStorage.h" + +static NSString *const kDefaultBaseDirectory = @"me.dvor.objcTox"; + +@implementation OCTManagerConfiguration + +#pragma mark - Class methods + ++ (instancetype)defaultConfiguration +{ + OCTManagerConfiguration *configuration = [OCTManagerConfiguration new]; + + NSString *baseDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; + baseDirectory = [baseDirectory stringByAppendingPathComponent:kDefaultBaseDirectory]; + + [[NSFileManager defaultManager] createDirectoryAtPath:baseDirectory + withIntermediateDirectories:YES + attributes:nil + error:nil]; + + configuration.fileStorage = [[OCTDefaultFileStorage alloc] initWithBaseDirectory:baseDirectory + temporaryDirectory:NSTemporaryDirectory()]; + + configuration.options = [OCTToxOptions new]; + + configuration.importToxSaveFromPath = nil; + configuration.useFauxOfflineMessaging = YES; + + return configuration; +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone +{ + OCTManagerConfiguration *configuration = [[[self class] allocWithZone:zone] init]; + + configuration.fileStorage = self.fileStorage; + configuration.options = [self.options copy]; + configuration.importToxSaveFromPath = [self.importToxSaveFromPath copy]; + configuration.useFauxOfflineMessaging = self.useFauxOfflineMessaging; + + return configuration; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Database/OCTRealmManager.h b/local_pod_repo/objcTox/Classes/Private/Manager/Database/OCTRealmManager.h new file mode 100644 index 0000000..aacd55e --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Database/OCTRealmManager.h @@ -0,0 +1,112 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTToxConstants.h" +#import "OCTManagerConstants.h" + +@class OCTObject; +@class OCTFriend; +@class OCTChat; +@class OCTCall; +@class OCTMessageAbstract; +@class OCTSettingsStorageObject; +@class RLMResults; + +@interface OCTRealmManager : NSObject + +/** + * Storage with all objcTox settings. + */ +@property (strong, nonatomic, readonly) OCTSettingsStorageObject *settingsStorage; + +/** + * Migrate unencrypted database to encrypted one. + * + * @param databasePath Path to unencrypted database. + * @param encryptionKey Key used to encrypt database. + * @param error Error parameter will be filled in case of failure. It will contain RLMRealm or NSFileManager error. + * + * @return YES on success, NO on failure. + */ ++ (BOOL)migrateToEncryptedDatabase:(NSString *)databasePath + encryptionKey:(NSData *)encryptionKey + error:(NSError **)error; + +/** + * Create RealmManager. + * + * @param fileURL path to Realm file. File will be created if it doesn't exist. + * @param encryptionKey A 64-byte key to use to encrypt the data, or nil if encryption is not enabled. + */ +- (instancetype)initWithDatabaseFileURL:(NSURL *)fileURL encryptionKey:(NSData *)encryptionKey; + +- (NSURL *)realmFileURL; + +#pragma mark - Basic methods + +- (id)objectWithUniqueIdentifier:(NSString *)uniqueIdentifier class:(Class)class; + +- (RLMResults *)objectsWithClass:(Class)class predicate:(NSPredicate *)predicate; + +- (void)addObject:(OCTObject *)object; +- (void)deleteObject:(OCTObject *)object; + +/* + * All realm objects should be updated ONLY using following two methods. + * + * Specified object will be passed in block. + */ +- (void)updateObject:(OCTObject *)object withBlock:(void (^)(id theObject))updateBlock; + +- (void)updateObjectsWithClass:(Class)class + predicate:(NSPredicate *)predicate + updateBlock:(void (^)(id theObject))updateBlock; + +#pragma mark - Other methods + +- (OCTFriend *)friendWithPublicKey:(NSString *)publicKey; +- (OCTChat *)getOrCreateChatWithFriend:(OCTFriend *)friend; +- (OCTCall *)createCallWithChat:(OCTChat *)chat status:(OCTCallStatus)status; + +/** + * Gets the current call for the chat if and only if it exists. + * This will not create a call object. + * @param chat The chat that is related to the call. + * @return A call object if it exists, nil if no call is session for this call. + */ +- (OCTCall *)getCurrentCallForChat:(OCTChat *)chat; + +- (void)removeMessages:(NSArray *)messages; +- (void)removeAllMessagesInChat:(OCTChat *)chat removeChat:(BOOL)removeChat; + +/** + * Converts all the OCTCalls to OCTMessageCalls. + * Only use this when first starting the app or during termination. + */ +- (void)convertAllCallsToMessages; + +- (OCTMessageAbstract *)addMessageWithText:(NSString *)text + type:(OCTToxMessageType)type + chat:(OCTChat *)chat + sender:(OCTFriend *)sender + messageId:(OCTToxMessageId)messageId + msgv3HashHex:(NSString *)msgv3HashHex + sentPush:(BOOL)sentPush + tssent:(UInt32)tssent + tsrcvd:(UInt32)tsrcvd; + +- (OCTMessageAbstract *)addMessageWithFileNumber:(OCTToxFileNumber)fileNumber + fileType:(OCTMessageFileType)fileType + fileSize:(OCTToxFileSize)fileSize + fileName:(NSString *)fileName + filePath:(NSString *)filePath + fileUTI:(NSString *)fileUTI + chat:(OCTChat *)chat + sender:(OCTFriend *)sender; + +- (OCTMessageAbstract *)addMessageCall:(OCTCall *)call; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Database/OCTRealmManager.m b/local_pod_repo/objcTox/Classes/Private/Manager/Database/OCTRealmManager.m new file mode 100644 index 0000000..af445cb --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Database/OCTRealmManager.m @@ -0,0 +1,665 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTRealmManager.h" +#import "OCTFriend.h" +#import "OCTFriendRequest.h" +#import "OCTChat.h" +#import "OCTCall.h" +#import "OCTMessageAbstract.h" +#import "OCTMessageText.h" +#import "OCTMessageFile.h" +#import "OCTMessageCall.h" +#import "OCTSettingsStorageObject.h" +#import "OCTLogging.h" + +static const uint64_t kCurrentSchemeVersion = 16; +static NSString *kSettingsStorageObjectPrimaryKey = @"kSettingsStorageObjectPrimaryKey"; + +@interface OCTRealmManager () + +@property (strong, nonatomic) dispatch_queue_t queue; +@property (strong, nonatomic) RLMRealm *realm; + +@end + +@implementation OCTRealmManager +@synthesize settingsStorage = _settingsStorage; + +#pragma mark - Class methods + ++ (BOOL)migrateToEncryptedDatabase:(NSString *)databasePath + encryptionKey:(NSData *)encryptionKey + error:(NSError **)error +{ + NSString *tempPath = [databasePath stringByAppendingPathExtension:@"tmp"]; + + @autoreleasepool { + RLMRealm *old = [OCTRealmManager createRealmWithFileURL:[NSURL fileURLWithPath:databasePath] + encryptionKey:nil + error:error]; + + if (! old) { + return NO; + } + + if (! [old writeCopyToURL:[NSURL fileURLWithPath:tempPath] encryptionKey:encryptionKey error:error]) { + return NO; + } + } + + if (! [[NSFileManager defaultManager] removeItemAtPath:databasePath error:error]) { + return NO; + } + + if (! [[NSFileManager defaultManager] moveItemAtPath:tempPath toPath:databasePath error:error]) { + return NO; + } + + return YES; +} + +#pragma mark - Lifecycle + +- (instancetype)initWithDatabaseFileURL:(NSURL *)fileURL encryptionKey:(NSData *)encryptionKey +{ + NSParameterAssert(fileURL); + + self = [super init]; + + if (! self) { + return nil; + } + + OCTLogInfo(@"init with fileURL %@", fileURL); + + _queue = dispatch_queue_create("OCTRealmManager queue", NULL); + + __weak OCTRealmManager *weakSelf = self; + dispatch_sync(_queue, ^{ + __strong OCTRealmManager *strongSelf = weakSelf; + + // TODO handle error + self->_realm = [OCTRealmManager createRealmWithFileURL:fileURL encryptionKey:encryptionKey error:nil]; + [strongSelf createSettingsStorage]; + }); + + [self convertAllCallsToMessages]; + + return self; +} + +#pragma mark - Public + +- (NSURL *)realmFileURL +{ + return self.realm.configuration.fileURL; +} + +#pragma mark - Basic methods + +- (id)objectWithUniqueIdentifier:(NSString *)uniqueIdentifier class:(Class)class +{ + NSParameterAssert(uniqueIdentifier); + NSParameterAssert(class); + + __block OCTObject *object = nil; + + dispatch_sync(self.queue, ^{ + object = [class objectInRealm:self.realm forPrimaryKey:uniqueIdentifier]; + }); + + return object; +} + +- (RLMResults *)objectsWithClass:(Class)class predicate:(NSPredicate *)predicate +{ + NSParameterAssert(class); + + __block RLMResults *results; + + dispatch_sync(self.queue, ^{ + results = [class objectsInRealm:self.realm withPredicate:predicate]; + }); + + return results; +} + +- (void)updateObject:(OCTObject *)object withBlock:(void (^)(id theObject))updateBlock +{ + NSParameterAssert(object); + NSParameterAssert(updateBlock); + + // OCTLogInfo(@"updateObject %@", object); + + dispatch_sync(self.queue, ^{ + [self.realm beginWriteTransaction]; + + updateBlock(object); + + [self.realm commitWriteTransaction]; + }); +} + +- (void)updateObjectsWithClass:(Class)class + predicate:(NSPredicate *)predicate + updateBlock:(void (^)(id theObject))updateBlock +{ + NSParameterAssert(class); + NSParameterAssert(updateBlock); + + // OCTLogInfo(@"updating objects of class %@ with predicate %@", NSStringFromClass(class), predicate); + + dispatch_sync(self.queue, ^{ + RLMResults *results = [class objectsInRealm:self.realm withPredicate:predicate]; + + [self.realm beginWriteTransaction]; + for (id object in results) { + updateBlock(object); + } + [self.realm commitWriteTransaction]; + }); +} + +- (void)addObject:(OCTObject *)object +{ + NSParameterAssert(object); + + // OCTLogInfo(@"add object %@", object); + + dispatch_sync(self.queue, ^{ + [self.realm beginWriteTransaction]; + + [self.realm addObject:object]; + + [self.realm commitWriteTransaction]; + }); +} + +- (void)deleteObject:(OCTObject *)object +{ + NSParameterAssert(object); + + // OCTLogInfo(@"delete object %@", object); + + dispatch_sync(self.queue, ^{ + [self.realm beginWriteTransaction]; + + [self.realm deleteObject:object]; + + [self.realm commitWriteTransaction]; + }); +} + +#pragma mark - Other methods + ++ (RLMRealm *)createRealmWithFileURL:(NSURL *)fileURL encryptionKey:(NSData *)encryptionKey error:(NSError **)error +{ + RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; + configuration.fileURL = fileURL; + configuration.schemaVersion = kCurrentSchemeVersion; + configuration.migrationBlock = [self realmMigrationBlock]; + configuration.encryptionKey = encryptionKey; + + RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:error]; + + if (! realm && error) { + OCTLogInfo(@"Cannot create Realm, error %@", *error); + } + + return realm; +} + +- (void)createSettingsStorage +{ + _settingsStorage = [OCTSettingsStorageObject objectInRealm:self.realm + forPrimaryKey:kSettingsStorageObjectPrimaryKey]; + + if (! _settingsStorage) { + OCTLogInfo(@"no _settingsStorage, creating it"); + _settingsStorage = [OCTSettingsStorageObject new]; + _settingsStorage.uniqueIdentifier = kSettingsStorageObjectPrimaryKey; + + [self.realm beginWriteTransaction]; + [self.realm addObject:_settingsStorage]; + [self.realm commitWriteTransaction]; + } +} + +- (OCTFriend *)friendWithPublicKey:(NSString *)publicKey +{ + NSAssert(publicKey, @"Public key should be non-empty."); + __block OCTFriend *friend; + + dispatch_sync(self.queue, ^{ + friend = [[OCTFriend objectsInRealm:self.realm where:@"publicKey == %@", publicKey] firstObject]; + }); + + return friend; +} + +- (OCTChat *)getOrCreateChatWithFriend:(OCTFriend *)friend +{ + __block OCTChat *chat = nil; + + dispatch_sync(self.queue, ^{ + // TODO add this (friends.@count == 1) condition. Currentry Realm doesn't support collection queries + // See https://github.com/realm/realm-cocoa/issues/1490 + chat = [[OCTChat objectsInRealm:self.realm where:@"ANY friends == %@", friend] firstObject]; + + if (chat) { + return; + } + + OCTLogInfo(@"creating chat with friend %@", friend); + + chat = [OCTChat new]; + chat.lastActivityDateInterval = [[NSDate date] timeIntervalSince1970]; + + [self.realm beginWriteTransaction]; + + [self.realm addObject:chat]; + [chat.friends addObject:friend]; + + [self.realm commitWriteTransaction]; + }); + + return chat; +} + +- (OCTCall *)createCallWithChat:(OCTChat *)chat status:(OCTCallStatus)status +{ + __block OCTCall *call = nil; + + dispatch_sync(self.queue, ^{ + + call = [[OCTCall objectsInRealm:self.realm where:@"chat == %@", chat] firstObject]; + + if (call) { + return; + } + + OCTLogInfo(@"creating call with chat %@", chat); + + call = [OCTCall new]; + call.status = status; + call.chat = chat; + + [self.realm beginWriteTransaction]; + [self.realm addObject:call]; + [self.realm commitWriteTransaction]; + }); + + return call; +} + +- (OCTCall *)getCurrentCallForChat:(OCTChat *)chat +{ + __block OCTCall *call = nil; + + dispatch_sync(self.queue, ^{ + + call = [[OCTCall objectsInRealm:self.realm where:@"chat == %@", chat] firstObject]; + }); + + return call; +} + +- (void)removeMessages:(NSArray *)messages +{ + NSParameterAssert(messages); + + OCTLogInfo(@"removing messages %lu", (unsigned long)messages.count); + + dispatch_sync(self.queue, ^{ + [self.realm beginWriteTransaction]; + + NSMutableSet *changedChats = [NSMutableSet new]; + for (OCTMessageAbstract *message in messages) { + [changedChats addObject:message.chatUniqueIdentifier]; + } + + [self removeMessagesWithSubmessages:messages]; + + for (NSString *chatUniqueIdentifier in changedChats) { + RLMResults *messages = [OCTMessageAbstract objectsInRealm:self.realm where:@"chatUniqueIdentifier == %@", chatUniqueIdentifier]; + messages = [messages sortedResultsUsingKeyPath:@"dateInterval" ascending:YES]; + + OCTChat *chat = [OCTChat objectInRealm:self.realm forPrimaryKey:chatUniqueIdentifier]; + chat.lastMessage = messages.lastObject; + } + + [self.realm commitWriteTransaction]; + }); +} + +- (void)removeAllMessagesInChat:(OCTChat *)chat removeChat:(BOOL)removeChat +{ + NSParameterAssert(chat); + + OCTLogInfo(@"removing chat with all messages %@", chat); + + dispatch_sync(self.queue, ^{ + RLMResults *messages = [OCTMessageAbstract objectsInRealm:self.realm where:@"chatUniqueIdentifier == %@", chat.uniqueIdentifier]; + + [self.realm beginWriteTransaction]; + + [self removeMessagesWithSubmessages:messages]; + if (removeChat) { + [self.realm deleteObject:chat]; + } + + [self.realm commitWriteTransaction]; + }); +} + +- (void)convertAllCallsToMessages +{ + RLMResults *calls = [OCTCall allObjectsInRealm:self.realm]; + + OCTLogInfo(@"removing %lu calls", (unsigned long)calls.count); + + for (OCTCall *call in calls) { + [self addMessageCall:call]; + } + + [self.realm beginWriteTransaction]; + [self.realm deleteObjects:calls]; + [self.realm commitWriteTransaction]; +} + +- (OCTMessageAbstract *)addMessageWithText:(NSString *)text + type:(OCTToxMessageType)type + chat:(OCTChat *)chat + sender:(OCTFriend *)sender + messageId:(OCTToxMessageId)messageId + msgv3HashHex:(NSString *)msgv3HashHex + sentPush:(BOOL)sentPush + tssent:(UInt32)tssent + tsrcvd:(UInt32)tsrcvd +{ + NSParameterAssert(text); + + OCTLogInfo(@"adding messageText to chat %@", chat); + + OCTMessageText *messageText = [OCTMessageText new]; + messageText.text = text; + messageText.isDelivered = NO; + messageText.type = type; + messageText.messageId = messageId; + messageText.msgv3HashHex = msgv3HashHex; + messageText.sentPush = sentPush; + + return [self addMessageAbstractWithChat:chat sender:sender messageText:messageText messageFile:nil messageCall:nil tssent:tssent tsrcvd:tsrcvd]; +} + +- (OCTMessageAbstract *)addMessageWithFileNumber:(OCTToxFileNumber)fileNumber + fileType:(OCTMessageFileType)fileType + fileSize:(OCTToxFileSize)fileSize + fileName:(NSString *)fileName + filePath:(NSString *)filePath + fileUTI:(NSString *)fileUTI + chat:(OCTChat *)chat + sender:(OCTFriend *)sender +{ + OCTLogInfo(@"adding messageFile to chat %@, fileSize %lld", chat, fileSize); + + OCTMessageFile *messageFile = [OCTMessageFile new]; + messageFile.internalFileNumber = fileNumber; + messageFile.fileType = fileType; + messageFile.fileSize = fileSize; + messageFile.fileName = fileName; + [messageFile internalSetFilePath:filePath]; + messageFile.fileUTI = fileUTI; + + return [self addMessageAbstractWithChat:chat sender:sender messageText:nil messageFile:messageFile messageCall:nil tssent:0 tsrcvd:0]; +} + +- (OCTMessageAbstract *)addMessageCall:(OCTCall *)call +{ + OCTLogInfo(@"adding messageCall to call %@", call); + + OCTMessageCallEvent event; + switch (call.status) { + case OCTCallStatusDialing: + case OCTCallStatusRinging: + event = OCTMessageCallEventUnanswered; + break; + case OCTCallStatusActive: + event = OCTMessageCallEventAnswered; + break; + } + + OCTMessageCall *messageCall = [OCTMessageCall new]; + messageCall.callDuration = call.callDuration; + messageCall.callEvent = event; + + return [self addMessageAbstractWithChat:call.chat sender:call.caller messageText:nil messageFile:nil messageCall:messageCall tssent:0 tsrcvd:0]; +} + +#pragma mark - Private + ++ (RLMMigrationBlock)realmMigrationBlock +{ + return ^(RLMMigration *migration, uint64_t oldSchemaVersion) { + if (oldSchemaVersion < 1) { + // objcTox version 0.1.0 + } + + if (oldSchemaVersion < 2) { + // objcTox version 0.2.1 + } + + if (oldSchemaVersion < 3) { + // objcTox version 0.4.0 + } + + if (oldSchemaVersion < 4) { + // objcTox version 0.5.0 + [self doMigrationVersion4:migration]; + } + + if (oldSchemaVersion < 5) { + // OCTMessageAbstract: chat property replaced with chatUniqueIdentifier + [self doMigrationVersion5:migration]; + } + + if (oldSchemaVersion < 6) { + // OCTSettingsStorageObject: adding genericSettingsData property. + } + + if (oldSchemaVersion < 7) { + [self doMigrationVersion7:migration]; + } + + if (oldSchemaVersion < 8) { + [self doMigrationVersion8:migration]; + } + + if (oldSchemaVersion < 9) {} + + if (oldSchemaVersion < 10) {} + + if (oldSchemaVersion < 11) { + [self doMigrationVersion11:migration]; + } + + if (oldSchemaVersion < 12) { + [self doMigrationVersion12:migration]; + } + + if (oldSchemaVersion < 13) { + [self doMigrationVersion13:migration]; + } + + if (oldSchemaVersion < 14) { + [self doMigrationVersion14:migration]; + } + + if (oldSchemaVersion < 16) { + [self doMigrationVersion16:migration]; + } + }; +} + ++ (void)doMigrationVersion4:(RLMMigration *)migration +{ + [migration enumerateObjects:OCTChat.className block:^(RLMObject *oldObject, RLMObject *newObject) { + newObject[@"enteredText"] = [oldObject[@"enteredText"] length] > 0 ? oldObject[@"enteredText"] : nil; + }]; + + [migration enumerateObjects:OCTFriend.className block:^(RLMObject *oldObject, RLMObject *newObject) { + newObject[@"name"] = [oldObject[@"name"] length] > 0 ? oldObject[@"name"] : nil; + newObject[@"statusMessage"] = [oldObject[@"statusMessage"] length] > 0 ? oldObject[@"statusMessage"] : nil; + }]; + + [migration enumerateObjects:OCTFriendRequest.className block:^(RLMObject *oldObject, RLMObject *newObject) { + newObject[@"message"] = [oldObject[@"message"] length] > 0 ? oldObject[@"message"] : nil; + }]; + + [migration enumerateObjects:OCTMessageFile.className block:^(RLMObject *oldObject, RLMObject *newObject) { + newObject[@"fileName"] = [oldObject[@"fileName"] length] > 0 ? oldObject[@"fileName"] : nil; + newObject[@"fileUTI"] = [oldObject[@"fileUTI"] length] > 0 ? oldObject[@"fileUTI"] : nil; + }]; + + [migration enumerateObjects:OCTMessageText.className block:^(RLMObject *oldObject, RLMObject *newObject) { + newObject[@"text"] = [oldObject[@"text"] length] > 0 ? oldObject[@"text"] : nil; + }]; +} + ++ (void)doMigrationVersion5:(RLMMigration *)migration +{ + [migration enumerateObjects:OCTMessageAbstract.className block:^(RLMObject *oldObject, RLMObject *newObject) { + newObject[@"chatUniqueIdentifier"] = oldObject[@"chat"][@"uniqueIdentifier"]; + newObject[@"senderUniqueIdentifier"] = oldObject[@"sender"][@"uniqueIdentifier"]; + }]; +} + ++ (void)doMigrationVersion7:(RLMMigration *)migration +{ + // Before this version OCTMessageText.isDelivered was broken. + // See https://github.com/Antidote-for-Tox/objcTox/issues/158 + // + // After update it was fixed + resending of undelivered messages feature was introduced. + // This fired resending all messages that were in history for all friends. + // + // To fix an issue and stop people suffering we mark all outgoing text messages as delivered. + + [migration enumerateObjects:OCTMessageAbstract.className block:^(RLMObject *oldObject, RLMObject *newObject) { + if (newObject[@"senderUniqueIdentifier"] != nil) { + return; + } + + RLMObject *messageText = newObject[@"messageText"]; + + if (! messageText) { + return; + } + + messageText[@"isDelivered"] = @YES; + }]; +} + ++ (void)doMigrationVersion8:(RLMMigration *)migration +{ + [migration enumerateObjects:OCTFriend.className block:^(RLMObject *oldObject, RLMObject *newObject) { + newObject[@"pushToken"] = nil; + }]; +} + ++ (void)doMigrationVersion11:(RLMMigration *)migration +{ + [migration enumerateObjects:OCTFriend.className block:^(RLMObject *oldObject, RLMObject *newObject) { + newObject[@"msgv3Capability"] = @NO; + }]; +} + ++ (void)doMigrationVersion12:(RLMMigration *)migration +{ + [migration enumerateObjects:OCTMessageText.className block:^(RLMObject *oldObject, RLMObject *newObject) { + newObject[@"msgv3HashHex"] = nil; + }]; +} + ++ (void)doMigrationVersion13:(RLMMigration *)migration +{ + [migration enumerateObjects:OCTMessageText.className block:^(RLMObject *oldObject, RLMObject *newObject) { + newObject[@"sentPush"] = @YES; + }]; +} + ++ (void)doMigrationVersion14:(RLMMigration *)migration +{ + [migration enumerateObjects:OCTMessageAbstract.className block:^(RLMObject *oldObject, RLMObject *newObject) { + newObject[@"tssent"] = @0; + newObject[@"tsrcvd"] = @0; + }]; +} + ++ (void)doMigrationVersion16:(RLMMigration *)migration +{ + [migration enumerateObjects:OCTFriend.className block:^(RLMObject *oldObject, RLMObject *newObject) { + newObject[@"capabilities2"] = nil; + }]; +} + +/** + * Only one of messageText, messageFile or messageCall can be non-nil. + */ +- (OCTMessageAbstract *)addMessageAbstractWithChat:(OCTChat *)chat + sender:(OCTFriend *)sender + messageText:(OCTMessageText *)messageText + messageFile:(OCTMessageFile *)messageFile + messageCall:(OCTMessageCall *)messageCall + tssent:(UInt32)tssent + tsrcvd:(UInt32)tsrcvd +{ + NSParameterAssert(chat); + + NSAssert( (messageText && ! messageFile && ! messageCall) || + (! messageText && messageFile && ! messageCall) || + (! messageText && ! messageFile && messageCall), + @"Wrong options passed. Only one of messageText, messageFile or messageCall should be non-nil."); + + OCTMessageAbstract *messageAbstract = [OCTMessageAbstract new]; + messageAbstract.dateInterval = [[NSDate date] timeIntervalSince1970]; + messageAbstract.senderUniqueIdentifier = sender.uniqueIdentifier; + messageAbstract.chatUniqueIdentifier = chat.uniqueIdentifier; + messageAbstract.tssent = tssent; + messageAbstract.tsrcvd = tsrcvd; + messageAbstract.messageText = messageText; + messageAbstract.messageFile = messageFile; + messageAbstract.messageCall = messageCall; + + [self addObject:messageAbstract]; + + [self updateObject:chat withBlock:^(OCTChat *theChat) { + theChat.lastMessage = messageAbstract; + theChat.lastActivityDateInterval = messageAbstract.dateInterval; + }]; + + return messageAbstract; +} + +// Delete an NSArray, RLMArray, or RLMResults of messages from this Realm. +- (void)removeMessagesWithSubmessages:(id)messages +{ + for (OCTMessageAbstract *message in messages) { + if (message.messageText) { + [self.realm deleteObject:message.messageText]; + } + if (message.messageFile) { + [self.realm deleteObject:message.messageFile]; + } + if (message.messageCall) { + [self.realm deleteObject:message.messageCall]; + } + } + + [self.realm deleteObjects:messages]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/NSError+OCTFile.h b/local_pod_repo/objcTox/Classes/Private/Manager/Files/NSError+OCTFile.h new file mode 100644 index 0000000..1918893 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/NSError+OCTFile.h @@ -0,0 +1,31 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTToxConstants.h" + +@class OCTMessageAbstract; + +@interface NSError (OCTFile) + ++ (NSError *)sendFileErrorInternalError; ++ (NSError *)sendFileErrorCannotReadFile; ++ (NSError *)sendFileErrorCannotSaveFileToUploads; ++ (NSError *)sendFileErrorFriendNotFound; ++ (NSError *)sendFileErrorFriendNotConnected; ++ (NSError *)sendFileErrorNameTooLong; ++ (NSError *)sendFileErrorTooMany; ++ (NSError *)sendFileErrorFromToxFileSendError:(OCTToxErrorFileSend)code; + ++ (NSError *)acceptFileErrorInternalError; ++ (NSError *)acceptFileErrorCannotWriteToFile; ++ (NSError *)acceptFileErrorFriendNotFound; ++ (NSError *)acceptFileErrorFriendNotConnected; ++ (NSError *)acceptFileErrorWrongMessage:(OCTMessageAbstract *)message; ++ (NSError *)acceptFileErrorFromToxFileSendChunkError:(OCTToxErrorFileSendChunk)code; ++ (NSError *)acceptFileErrorFromToxFileControl:(OCTToxErrorFileControl)code; + ++ (NSError *)fileTransferErrorWrongMessage:(OCTMessageAbstract *)message; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/NSError+OCTFile.m b/local_pod_repo/objcTox/Classes/Private/Manager/Files/NSError+OCTFile.m new file mode 100644 index 0000000..8bca707 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/NSError+OCTFile.m @@ -0,0 +1,189 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "NSError+OCTFile.h" +#import "OCTManagerConstants.h" + +@implementation NSError (OCTFile) + ++ (NSError *)sendFileErrorInternalError +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTSendFileErrorInternalError + userInfo:@{ + NSLocalizedDescriptionKey : @"Send file", + NSLocalizedFailureReasonErrorKey : @"Internal error", + }]; +} + ++ (NSError *)sendFileErrorCannotReadFile +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTSendFileErrorCannotReadFile + userInfo:@{ + NSLocalizedDescriptionKey : @"Send file", + NSLocalizedFailureReasonErrorKey : @"Cannot read file", + }]; +} + ++ (NSError *)sendFileErrorCannotSaveFileToUploads +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTSendFileErrorCannotSaveFileToUploads + userInfo:@{ + NSLocalizedDescriptionKey : @"Send file", + NSLocalizedFailureReasonErrorKey : @"Cannot save send file to uploads folder.", + }]; +} + ++ (NSError *)sendFileErrorFriendNotFound +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTSendFileErrorFriendNotFound + userInfo:@{ + NSLocalizedDescriptionKey : @"Send file", + NSLocalizedFailureReasonErrorKey : @"Friend to send file to was not found.", + }]; +} + ++ (NSError *)sendFileErrorFriendNotConnected +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTSendFileErrorFriendNotConnected + userInfo:@{ + NSLocalizedDescriptionKey : @"Send file", + NSLocalizedFailureReasonErrorKey : @"Friend is not connected at the moment.", + }]; +} + ++ (NSError *)sendFileErrorNameTooLong +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTSendFileErrorFriendNotConnected + userInfo:@{ + NSLocalizedDescriptionKey : @"Send file", + NSLocalizedFailureReasonErrorKey : @"File name is too long.", + }]; +} + ++ (NSError *)sendFileErrorTooMany +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTSendFileErrorFriendNotConnected + userInfo:@{ + NSLocalizedDescriptionKey : @"Send file", + NSLocalizedFailureReasonErrorKey : @"Too many active file transfers.", + }]; +} + ++ (NSError *)sendFileErrorFromToxFileSendError:(OCTToxErrorFileSend)code +{ + switch (code) { + case OCTToxErrorFileSendUnknown: + return [self sendFileErrorInternalError]; + case OCTToxErrorFileSendFriendNotFound: + return [self sendFileErrorFriendNotFound]; + case OCTToxErrorFileSendFriendNotConnected: + return [self sendFileErrorFriendNotConnected]; + case OCTToxErrorFileSendNameTooLong: + return [self sendFileErrorNameTooLong]; + case OCTToxErrorFileSendTooMany: + return [self sendFileErrorTooMany]; + } +} + ++ (NSError *)acceptFileErrorInternalError +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTAcceptFileErrorInternalError + userInfo:@{ + NSLocalizedDescriptionKey : @"Download file", + NSLocalizedFailureReasonErrorKey : @"Internal error", + }]; +} + ++ (NSError *)acceptFileErrorCannotWriteToFile +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTAcceptFileErrorCannotWriteToFile + userInfo:@{ + NSLocalizedDescriptionKey : @"Download file", + NSLocalizedFailureReasonErrorKey : @"File is not available for writing.", + }]; +} + ++ (NSError *)acceptFileErrorFriendNotFound +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTAcceptFileErrorFriendNotFound + userInfo:@{ + NSLocalizedDescriptionKey : @"Download file", + NSLocalizedFailureReasonErrorKey : @"Friend to send file to was not found.", + }]; +} + ++ (NSError *)acceptFileErrorFriendNotConnected +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTAcceptFileErrorFriendNotConnected + userInfo:@{ + NSLocalizedDescriptionKey : @"Download file", + NSLocalizedFailureReasonErrorKey : @"Friend is not connected at the moment.", + }]; +} + ++ (NSError *)acceptFileErrorWrongMessage:(OCTMessageAbstract *)message +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTAcceptFileErrorWrongMessage + userInfo:@{ + NSLocalizedDescriptionKey : @"Download file", + NSLocalizedFailureReasonErrorKey : [NSString stringWithFormat:@"Specified wrong message %@", message], + }]; +} + ++ (NSError *)acceptFileErrorFromToxFileSendChunkError:(OCTToxErrorFileSendChunk)code +{ + switch (code) { + case OCTToxErrorFileSendChunkFriendNotFound: + return [self acceptFileErrorFriendNotFound]; + case OCTToxErrorFileSendChunkFriendNotConnected: + return [self acceptFileErrorFriendNotConnected]; + case OCTToxErrorFileSendChunkUnknown: + case OCTToxErrorFileSendChunkNotFound: + case OCTToxErrorFileSendChunkNotTransferring: + case OCTToxErrorFileSendChunkInvalidLength: + case OCTToxErrorFileSendChunkSendq: + case OCTToxErrorFileSendChunkWrongPosition: + return [self acceptFileErrorInternalError]; + } +} + ++ (NSError *)acceptFileErrorFromToxFileControl:(OCTToxErrorFileControl)code +{ + switch (code) { + case OCTToxErrorFileControlFriendNotFound: + return [self acceptFileErrorFriendNotFound]; + case OCTToxErrorFileControlFriendNotConnected: + return [self acceptFileErrorFriendNotConnected]; + case OCTToxErrorFileControlNotFound: + case OCTToxErrorFileControlNotPaused: + case OCTToxErrorFileControlDenied: + case OCTToxErrorFileControlAlreadyPaused: + case OCTToxErrorFileControlSendq: + return [self acceptFileErrorInternalError]; + } +} + ++ (NSError *)fileTransferErrorWrongMessage:(OCTMessageAbstract *)message +{ + return [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTFileTransferErrorWrongMessage + userInfo:@{ + NSLocalizedDescriptionKey : @"Error", + NSLocalizedFailureReasonErrorKey : [NSString stringWithFormat:@"Specified wrong message %@", message], + }]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileBaseOperation+Private.h b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileBaseOperation+Private.h new file mode 100644 index 0000000..8499967 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileBaseOperation+Private.h @@ -0,0 +1,42 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFileBaseOperation.h" + +@interface OCTFileBaseOperation (Private) + +@property (weak, nonatomic, readonly, nullable) OCTTox *tox; + +@property (assign, nonatomic, readonly) OCTToxFriendNumber friendNumber; +@property (assign, nonatomic, readonly) OCTToxFileNumber fileNumber; +@property (assign, nonatomic, readonly) OCTToxFileSize fileSize; + +/** + * Override this method to start custom actions. Call finish when operation is done. + */ +- (void)operationStarted NS_REQUIRES_SUPER; + +/** + * Override this method to do clean up on operation cancellation. + */ +- (void)operationWasCanceled NS_REQUIRES_SUPER; + +/** + * Call this method to change bytes done value. + */ +- (void)updateBytesDone:(OCTToxFileSize)bytesDone; + +/** + * Call this method in case if operation was finished. + */ +- (void)finishWithSuccess; + +/** + * Call this method in case if operation was finished or cancelled with error. + * + * @param error Pass error if occured, nil on success. + */ +- (void)finishWithError:(nonnull NSError *)error; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileBaseOperation.h b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileBaseOperation.h new file mode 100644 index 0000000..5ca79c5 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileBaseOperation.h @@ -0,0 +1,85 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTTox.h" +#import "OCTToxConstants.h" + +@class OCTFileBaseOperation; + +/** + * Block to notify about operation progress. + * + * @param operation Operation that is running. + * @param progress Progress of operation. From 0.0 to 1.0. + * @param bytesPerSecond Speed of loading. + * @param eta Estimated time of finish of loading. + */ +typedef void (^OCTFileBaseOperationProgressBlock)(OCTFileBaseOperation *__nonnull operation); + +/** + * Block to notify about operation success. + * + * @param operation Operation that is running. + * @param filePath Path of file being loaded + */ +typedef void (^OCTFileBaseOperationSuccessBlock)(OCTFileBaseOperation *__nonnull operation); + +/** + * Block to notify about operation failure. + * + * @param operation Operation that is running. + */ +typedef void (^OCTFileBaseOperationFailureBlock)(OCTFileBaseOperation *__nonnull operation, NSError *__nonnull error); + + +@interface OCTFileBaseOperation : NSOperation + +/** + * Identifier of operation, unique for all active file operations. + */ +@property (strong, nonatomic, readonly, nonnull) NSString *operationId; + +/** + * Progress properties. + */ +@property (assign, nonatomic, readonly) OCTToxFileSize bytesDone; +@property (assign, nonatomic, readonly) float progress; +@property (assign, nonatomic, readonly) OCTToxFileSize bytesPerSecond; +@property (assign, nonatomic, readonly) CFTimeInterval eta; + +@property (strong, nonatomic, readonly, nullable) NSDictionary *userInfo; + +/** + * Creates operation id from file and friend number. + */ ++ (nonnull NSString *)operationIdFromFileNumber:(OCTToxFileNumber)fileNumber friendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Create operation. + * + * @param tox Tox object to load from. + * @param friendNumber Number of friend. + * @param fileNumber Number of file to load. + * @param fileSize Size of file in bytes. + * @param userInfo Any object that will be stored by operation. + * @param progressBlock Block called to notify about loading progress. Block will be called on main thread. + * @param etaUpdateBlock Block called to notify about loading eta update. Block will be called on main thread. + * @param successBlock Block called on operation success. Block will be called on main thread. + * @param failureBlock Block called on loading error. Block will be called on main thread. + */ +- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox + friendNumber:(OCTToxFriendNumber)friendNumber + fileNumber:(OCTToxFileNumber)fileNumber + fileSize:(OCTToxFileSize)fileSize + userInfo:(nullable NSDictionary *)userInfo + progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock + etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock + successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock + failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock; + +- (nullable instancetype)init NS_UNAVAILABLE; ++ (nullable instancetype)new NS_UNAVAILABLE; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileBaseOperation.m b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileBaseOperation.m new file mode 100644 index 0000000..9147bf3 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileBaseOperation.m @@ -0,0 +1,286 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFileBaseOperation.h" +#import "OCTFileBaseOperation+Private.h" +#import "OCTLogging.h" + +#import + +static const CFTimeInterval kMinUpdateProgressInterval = 0.1; +static const CFTimeInterval kMinUpdateEtaInterval = 1.0; + +@interface OCTEtaObject : NSObject +@property (assign, nonatomic) CFTimeInterval deltaTime; +@property (assign, nonatomic) OCTToxFileSize deltaBytes; +@end + +@implementation OCTEtaObject +@end + +@interface OCTFileBaseOperation () + +@property (assign, atomic) BOOL privateExecuting; +@property (assign, atomic) BOOL privateFinished; + +@property (weak, nonatomic, readonly, nullable) OCTTox *tox; + +@property (assign, nonatomic, readonly) OCTToxFriendNumber friendNumber; +@property (assign, nonatomic, readonly) OCTToxFileNumber fileNumber; +@property (assign, nonatomic, readonly) OCTToxFileSize fileSize; + +@property (assign, nonatomic, readwrite) OCTToxFileSize bytesDone; +@property (assign, nonatomic, readwrite) float progress; +@property (assign, nonatomic, readwrite) OCTToxFileSize bytesPerSecond; +@property (assign, nonatomic, readwrite) CFTimeInterval eta; + +@property (copy, nonatomic) OCTFileBaseOperationProgressBlock progressBlock; +@property (copy, nonatomic) OCTFileBaseOperationProgressBlock etaUpdateBlock; +@property (copy, nonatomic) OCTFileBaseOperationSuccessBlock successBlock; +@property (copy, nonatomic) OCTFileBaseOperationFailureBlock failureBlock; + +@property (assign, nonatomic) CFTimeInterval lastUpdateProgressTime; +@property (assign, nonatomic) OCTToxFileSize lastUpdateBytesDone; +@property (assign, nonatomic) CFTimeInterval lastUpdateEtaProgressTime; +@property (assign, nonatomic) OCTToxFileSize lastUpdateEtaBytesDone; + +@property (strong, nonatomic) NSMutableArray *last10EtaObjects; + +@end + +@implementation OCTFileBaseOperation + +#pragma mark - Class methods + ++ (NSString *)operationIdFromFileNumber:(OCTToxFileNumber)fileNumber friendNumber:(OCTToxFriendNumber)friendNumber +{ + return [NSString stringWithFormat:@"%d-%d", fileNumber, friendNumber]; +} + +#pragma mark - Lifecycle + +- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox + friendNumber:(OCTToxFriendNumber)friendNumber + fileNumber:(OCTToxFileNumber)fileNumber + fileSize:(OCTToxFileSize)fileSize + userInfo:(NSDictionary *)userInfo + progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock + etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock + successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock + failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock +{ + NSParameterAssert(tox); + NSParameterAssert(fileSize > 0); + + self = [super init]; + + if (! self) { + return nil; + } + + _operationId = [[self class] operationIdFromFileNumber:fileNumber friendNumber:friendNumber]; + + _tox = tox; + + _friendNumber = friendNumber; + _fileNumber = fileNumber; + _fileSize = fileSize; + + _progress = 0.0; + _bytesPerSecond = 0; + _eta = 0; + + _userInfo = userInfo; + + _progressBlock = [progressBlock copy]; + _etaUpdateBlock = [etaUpdateBlock copy]; + _successBlock = [successBlock copy]; + _failureBlock = [failureBlock copy]; + + _bytesDone = 0; + _lastUpdateProgressTime = 0; + + return self; +} + +#pragma mark - Properties + +- (void)setExecuting:(BOOL)executing +{ + [self willChangeValueForKey:@"isExecuting"]; + self.privateExecuting = executing; + [self didChangeValueForKey:@"isExecuting"]; +} + +- (BOOL)isExecuting +{ + return self.privateExecuting; +} + +- (void)setFinished:(BOOL)finished +{ + [self willChangeValueForKey:@"isFinished"]; + self.privateFinished = finished; + [self didChangeValueForKey:@"isFinished"]; +} + +- (BOOL)isFinished +{ + return self.privateFinished; +} + +#pragma mark - Private category + +- (void)updateBytesDone:(OCTToxFileSize)bytesDone +{ + self.bytesDone = bytesDone; + + [self updateProgressIfNeeded:bytesDone]; + [self updateEtaIfNeeded:bytesDone]; +} + +- (void)operationStarted +{ + OCTLogInfo(@"start loading file with identifier %@", self.operationId); +} + +- (void)operationWasCanceled +{ + OCTLogInfo(@"was cancelled"); +} + +- (void)finishWithSuccess +{ + OCTLogInfo(@"finished with success"); + + self.executing = NO; + self.finished = YES; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.successBlock) { + self.successBlock(self); + } + }); +} + +- (void)finishWithError:(nonnull NSError *)error +{ + NSParameterAssert(error); + + OCTLogInfo(@"finished with error %@", error); + + self.executing = NO; + self.finished = YES; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.failureBlock) { + self.failureBlock(self, error); + } + }); +} + +#pragma mark - Override + +- (void)start +{ + if (self.cancelled) { + self.finished = YES; + return; + } + + self.executing = YES; + + self.lastUpdateProgressTime = CACurrentMediaTime(); + self.lastUpdateBytesDone = 0; + self.lastUpdateEtaProgressTime = CACurrentMediaTime(); + self.lastUpdateEtaBytesDone = 0; + self.last10EtaObjects = [NSMutableArray new]; + + [self operationStarted]; +} + +- (void)cancel +{ + [super cancel]; + + [self operationWasCanceled]; + + self.executing = NO; + self.finished = YES; +} + +- (BOOL)asynchronous +{ + return YES; +} + +#pragma mark - Private + +- (void)updateProgressIfNeeded:(OCTToxFileSize)bytesDone +{ + CFTimeInterval time = CACurrentMediaTime(); + + CFTimeInterval deltaTime = time - self.lastUpdateProgressTime; + + if (deltaTime <= kMinUpdateProgressInterval) { + return; + } + + self.lastUpdateProgressTime = time; + self.lastUpdateBytesDone = bytesDone; + + self.progress = (float)bytesDone / self.fileSize; + + OCTLogInfo(@"progress %.2f, bytes per second %lld, eta %.0f seconds", self.progress, self.bytesPerSecond, self.eta); + + if (self.progressBlock) { + self.progressBlock(self); + } +} + +- (void)updateEtaIfNeeded:(OCTToxFileSize)bytesDone +{ + CFTimeInterval time = CACurrentMediaTime(); + + CFTimeInterval deltaTime = time - self.lastUpdateEtaProgressTime; + + if (deltaTime <= kMinUpdateEtaInterval) { + return; + } + + OCTToxFileSize deltaBytes = bytesDone - self.lastUpdateEtaBytesDone; + OCTToxFileSize bytesLeft = self.fileSize - bytesDone; + + self.lastUpdateEtaProgressTime = time; + self.lastUpdateEtaBytesDone = bytesDone; + + OCTEtaObject *etaObject = [OCTEtaObject new]; + etaObject.deltaTime = deltaTime; + etaObject.deltaBytes = deltaBytes; + + [self.last10EtaObjects addObject:etaObject]; + if (self.last10EtaObjects.count > 10) { + [self.last10EtaObjects removeObjectAtIndex:0]; + } + + CFTimeInterval totalDeltaTime = 0.0; + OCTToxFileSize totalDeltaBytes = 0; + + for (OCTEtaObject *object in self.last10EtaObjects) { + totalDeltaTime += object.deltaTime; + totalDeltaBytes += object.deltaBytes; + } + + self.bytesPerSecond = totalDeltaBytes / totalDeltaTime; + + if (totalDeltaBytes) { + self.eta = totalDeltaTime * bytesLeft / totalDeltaBytes; + } + + if (self.etaUpdateBlock) { + self.etaUpdateBlock(self); + } +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDataInput.h b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDataInput.h new file mode 100644 index 0000000..180b121 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDataInput.h @@ -0,0 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTFileInputProtocol.h" + +@interface OCTFileDataInput : NSObject + +- (nullable instancetype)initWithData:(nonnull NSData *)data; + +- (nullable instancetype)init NS_UNAVAILABLE; ++ (nullable instancetype)new NS_UNAVAILABLE; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDataInput.m b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDataInput.m new file mode 100644 index 0000000..8d9e4a1 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDataInput.m @@ -0,0 +1,50 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFileDataInput.h" +#import "OCTLogging.h" + +@interface OCTFileDataInput () + +@property (strong, nonatomic, readonly) NSData *data; + +@end + +@implementation OCTFileDataInput + +#pragma mark - Lifecycle + +- (nullable instancetype)initWithData:(nonnull NSData *)data +{ + self = [super init]; + + if (! self) { + return nil; + } + + _data = data; + + return self; +} + +#pragma mark - OCTFileInputProtocol + +- (BOOL)prepareToRead +{ + return YES; +} + +- (nonnull NSData *)bytesWithPosition:(OCTToxFileSize)position length:(size_t)length +{ + @try { + return [self.data subdataWithRange:NSMakeRange((NSUInteger)position, length)]; + } + @catch (NSException *ex) { + OCTLogWarn(@"catched exception %@", ex); + } + + return nil; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDataOutput.h b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDataOutput.h new file mode 100644 index 0000000..1475b23 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDataOutput.h @@ -0,0 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTFileOutputProtocol.h" + +@interface OCTFileDataOutput : NSObject + +/** + * Result data. This property will contain data only after download finishes. + */ +@property (strong, nonatomic, readonly, nullable) NSData *resultData; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDataOutput.m b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDataOutput.m new file mode 100644 index 0000000..9f23170 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDataOutput.m @@ -0,0 +1,42 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFileDataOutput.h" + +@interface OCTFileDataOutput () + +@property (strong, nonatomic) NSMutableData *tempData; +@property (strong, nonatomic) NSData *resultData; + +@end + +@implementation OCTFileDataOutput + +#pragma mark - OCTFileOutputProtocol + +- (BOOL)prepareToWrite +{ + self.tempData = [NSMutableData new]; + return YES; +} + +- (BOOL)writeData:(nonnull NSData *)data +{ + [self.tempData appendData:data]; + return YES; +} + +- (BOOL)finishWriting +{ + self.resultData = [self.tempData copy]; + self.tempData = nil; + return YES; +} + +- (void)cancel +{ + self.tempData = nil; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDownloadOperation.h b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDownloadOperation.h new file mode 100644 index 0000000..d491715 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDownloadOperation.h @@ -0,0 +1,45 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFileBaseOperation.h" + +@class OCTTox; +@protocol OCTFileOutputProtocol; + +/** + * File operation for downloading file. + * + * When started will automatically send resume control to friend. + */ +@interface OCTFileDownloadOperation : OCTFileBaseOperation + +@property (strong, nonatomic, readonly, nonnull) id output; + +/** + * Create operation. + * + * @param fileOutput Output to use as a destination for file transfer. + * + * For other parameters description see OCTFileBaseOperation. + */ +- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox + fileOutput:(nonnull id)fileOutput + friendNumber:(OCTToxFriendNumber)friendNumber + fileNumber:(OCTToxFileNumber)fileNumber + fileSize:(OCTToxFileSize)fileSize + userInfo:(nullable NSDictionary *)userInfo + progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock + etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock + successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock + failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock; + +/** + * Call this method to get next chunk to operation. + * + * @param chunk Next chunk of data to append to file. + * @param position Position in file to append chunk. + */ +- (void)receiveChunk:(nullable NSData *)chunk position:(OCTToxFileSize)position; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDownloadOperation.m b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDownloadOperation.m new file mode 100644 index 0000000..adef336 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileDownloadOperation.m @@ -0,0 +1,111 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFileDownloadOperation.h" +#import "OCTFileBaseOperation+Private.h" +#import "OCTFileOutputProtocol.h" +#import "OCTLogging.h" +#import "NSError+OCTFile.h" + +@interface OCTFileDownloadOperation () + +@end + +@implementation OCTFileDownloadOperation + +#pragma mark - Lifecycle + +- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox + fileOutput:(nonnull id)fileOutput + friendNumber:(OCTToxFriendNumber)friendNumber + fileNumber:(OCTToxFileNumber)fileNumber + fileSize:(OCTToxFileSize)fileSize + userInfo:(NSDictionary *)userInfo + progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock + etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock + successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock + failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock +{ + NSParameterAssert(fileOutput); + + self = [super initWithTox:tox + friendNumber:friendNumber + fileNumber:fileNumber + fileSize:fileSize + userInfo:userInfo + progressBlock:progressBlock + etaUpdateBlock:etaUpdateBlock + successBlock:successBlock + failureBlock:failureBlock]; + + if (! self) { + return nil; + } + + _output = fileOutput; + + return self; +} + +#pragma mark - Public + +- (void)receiveChunk:(NSData *)chunk position:(OCTToxFileSize)position +{ + if (! chunk) { + if ([self.output finishWriting]) { + [self finishWithSuccess]; + } + else { + [self finishWithError:[NSError acceptFileErrorCannotWriteToFile]]; + } + return; + } + + if (self.bytesDone != position) { + OCTLogWarn(@"bytesDone doesn't match position"); + [self.tox fileSendControlForFileNumber:self.fileNumber + friendNumber:self.friendNumber + control:OCTToxFileControlCancel + error:nil]; + [self finishWithError:[NSError acceptFileErrorInternalError]]; + return; + } + + if (! [self.output writeData:chunk]) { + [self finishWithError:[NSError acceptFileErrorCannotWriteToFile]]; + return; + } + + [self updateBytesDone:self.bytesDone + chunk.length]; +} + +#pragma mark - Override + +- (void)operationStarted +{ + [super operationStarted]; + + if (! [self.output prepareToWrite]) { + [self finishWithError:[NSError acceptFileErrorCannotWriteToFile]]; + } + + NSError *error; + if (! [self.tox fileSendControlForFileNumber:self.fileNumber + friendNumber:self.friendNumber + control:OCTToxFileControlResume + error:&error]) { + OCTLogWarn(@"cannot send control %@", error); + [self finishWithError:[NSError acceptFileErrorFromToxFileControl:error.code]]; + return; + } +} + +- (void)operationWasCanceled +{ + [super operationWasCanceled]; + + [self.output cancel]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileInputProtocol.h b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileInputProtocol.h new file mode 100644 index 0000000..708402b --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileInputProtocol.h @@ -0,0 +1,27 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTToxConstants.h" + +@protocol OCTFileInputProtocol + +/** + * Prepare input to read. This method will be called before first call to bytesWithPosition:length:. + * + * @return YES on success, NO on failure. + */ +- (BOOL)prepareToRead; + +/** + * Provide bytes. + * + * @param position Start position to start reading from. + * @param length Length of bytes to read. + * + * @return NSData on success, nil on failure + */ +- (nonnull NSData *)bytesWithPosition:(OCTToxFileSize)position length:(size_t)length; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileOutputProtocol.h b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileOutputProtocol.h new file mode 100644 index 0000000..bf57842 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileOutputProtocol.h @@ -0,0 +1,37 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@protocol OCTFileOutputProtocol + +/** + * Prepare input to write. This method will be called before first call to writeData:. + * + * @return YES on success, NO on failure. + */ +- (BOOL)prepareToWrite; + +/** + * Write data to output. + * + * @param data Data to write. + * + * @return YES on success, NO on failure. + */ +- (BOOL)writeData:(nonnull NSData *)data; + +/** + * This method is called after last writeData: method. + * + * @return YES on success, NO on failure. + */ +- (BOOL)finishWriting; + +/** + * This method is called if all progress was canceled. Do needed cleanup. + */ +- (void)cancel; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFilePathInput.h b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFilePathInput.h new file mode 100644 index 0000000..c490e10 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFilePathInput.h @@ -0,0 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTFileInputProtocol.h" + +@interface OCTFilePathInput : NSObject + +- (nullable instancetype)initWithFilePath:(nonnull NSString *)filePath; + +- (nullable instancetype)init NS_UNAVAILABLE; ++ (nullable instancetype)new NS_UNAVAILABLE; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFilePathInput.m b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFilePathInput.m new file mode 100644 index 0000000..832eff6 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFilePathInput.m @@ -0,0 +1,61 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFilePathInput.h" +#import "OCTLogging.h" + +@interface OCTFilePathInput () + +@property (strong, nonatomic, readonly) NSString *filePath; +@property (strong, nonatomic) NSFileHandle *handle; + +@end + +@implementation OCTFilePathInput + +#pragma mark - Lifecycle + +- (nullable instancetype)initWithFilePath:(nonnull NSString *)filePath +{ + self = [super init]; + + if (! self) { + return nil; + } + + _filePath = filePath; + + return self; +} + +#pragma mark - OCTFileInputProtocol + +- (BOOL)prepareToRead +{ + self.handle = [NSFileHandle fileHandleForReadingAtPath:self.filePath]; + + if (! self.handle) { + return NO; + } + + return YES; +} + +- (NSData *)bytesWithPosition:(OCTToxFileSize)position length:(size_t)length +{ + @try { + if (self.handle.offsetInFile != position) { + [self.handle seekToFileOffset:position]; + } + + return [self.handle readDataOfLength:length]; + } + @catch (NSException *ex) { + OCTLogWarn(@"catched exception %@", ex); + } + + return nil; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFilePathOutput.h b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFilePathOutput.h new file mode 100644 index 0000000..88668e7 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFilePathOutput.h @@ -0,0 +1,19 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTFileOutputProtocol.h" + +@interface OCTFilePathOutput : NSObject + +@property (copy, nonatomic, readonly, nonnull) NSString *resultFilePath; + +- (nullable instancetype)initWithTempFolder:(nonnull NSString *)tempFolder + resultFolder:(nonnull NSString *)resultFolder + fileName:(nonnull NSString *)fileName; + +- (nullable instancetype)init NS_UNAVAILABLE; ++ (nullable instancetype)new NS_UNAVAILABLE; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFilePathOutput.m b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFilePathOutput.m new file mode 100644 index 0000000..1375283 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFilePathOutput.m @@ -0,0 +1,100 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFilePathOutput.h" +#import "OCTLogging.h" +#import "OCTFileTools.h" + +@interface OCTFilePathOutput () + +@property (copy, nonatomic, readonly, nonnull) NSString *tempFilePath; + +@property (strong, nonatomic) NSFileHandle *handle; + +@end + +@implementation OCTFilePathOutput + +#pragma mark - Lifecycle + +- (nullable instancetype)initWithTempFolder:(nonnull NSString *)tempFolder + resultFolder:(nonnull NSString *)resultFolder + fileName:(nonnull NSString *)fileName +{ + self = [super init]; + + if (! self) { + return nil; + } + + _tempFilePath = [OCTFileTools createNewFilePathInDirectory:tempFolder fileName:fileName]; + _resultFilePath = [OCTFileTools createNewFilePathInDirectory:resultFolder fileName:fileName]; + + // Create dummy file to reserve fileName. + [[NSFileManager defaultManager] createFileAtPath:_resultFilePath contents:[NSData data] attributes:nil]; + + OCTLogInfo(@"temp path %@", _tempFilePath); + OCTLogInfo(@"result path %@", _resultFilePath); + + return self; +} + +#pragma mark - OCTFileOutputProtocol + +- (BOOL)prepareToWrite +{ + if (! [[NSFileManager defaultManager] createFileAtPath:self.tempFilePath contents:nil attributes:nil]) { + return NO; + } + + self.handle = [NSFileHandle fileHandleForWritingAtPath:self.tempFilePath]; + + if (! self.handle) { + return NO; + } + + return YES; +} + +- (BOOL)writeData:(nonnull NSData *)data +{ + @try { + [self.handle writeData:data]; + return YES; + } + @catch (NSException *ex) { + OCTLogWarn(@"catched exception %@", ex); + } + + return NO; +} + +- (BOOL)finishWriting +{ + @try { + [self.handle synchronizeFile]; + } + @catch (NSException *ex) { + OCTLogWarn(@"catched exception %@", ex); + return NO; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + + // Remove dummy file. + if (! [fileManager removeItemAtPath:self.resultFilePath error:nil]) { + return NO; + } + + return [[NSFileManager defaultManager] moveItemAtPath:self.tempFilePath toPath:self.resultFilePath error:nil]; +} + +- (void)cancel +{ + self.handle = nil; + + [[NSFileManager defaultManager] removeItemAtPath:self.tempFilePath error:nil]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileTools.h b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileTools.h new file mode 100644 index 0000000..05266c4 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileTools.h @@ -0,0 +1,20 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@interface OCTFileTools : NSObject + +/** + * Creates filePath in directory for given fileName. In case if file already exists appends " N" suffix, + * e.g. "file 2.txt", "file 3.txt". + * + * @param directory Directory part of filePath. + * @param fileName Name of the file to use in path. + * + * @return File path to file that does not exist. + */ ++ (nonnull NSString *)createNewFilePathInDirectory:(nonnull NSString *)directory fileName:(nonnull NSString *)fileName; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileTools.m b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileTools.m new file mode 100644 index 0000000..9911ad2 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileTools.m @@ -0,0 +1,77 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFileTools.h" + +@implementation OCTFileTools + +#pragma mark - Public + ++ (nonnull NSString *)createNewFilePathInDirectory:(nonnull NSString *)directory fileName:(nonnull NSString *)fileName +{ + NSParameterAssert(directory); + NSParameterAssert(fileName); + + NSString *path = [directory stringByAppendingPathComponent:fileName]; + + if (! [self fileExistsAtPath:path]) { + return path; + } + + NSString *base; + NSString *pathExtension = [fileName pathExtension]; + NSInteger suffix; + + [self getBaseString:&base andSuffix:&suffix fromString:[fileName stringByDeletingPathExtension]]; + + while (YES) { + NSString *resultName = [base stringByAppendingFormat:@" %ld", (long)suffix]; + + if (pathExtension.length > 0) { + resultName = [resultName stringByAppendingPathExtension:pathExtension]; + } + + NSString *path = [directory stringByAppendingPathComponent:resultName]; + + if (! [self fileExistsAtPath:path]) { + return path; + } + + suffix++; + } +} + +#pragma mark - Private + ++ (BOOL)fileExistsAtPath:(NSString *)filePath +{ + return [[NSFileManager defaultManager] fileExistsAtPath:filePath]; +} + ++ (void)getBaseString:(NSString **)baseString andSuffix:(NSInteger *)suffix fromString:(NSString *)original +{ + NSString *tempBase = original; + NSInteger tempSuffix = 1; + + NSArray *components = [tempBase componentsSeparatedByString:@" "]; + + if (components.count > 1) { + NSString *lastComponent = components.lastObject; + NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:lastComponent]; + + if ([[NSCharacterSet decimalDigitCharacterSet] isSupersetOfSet:set]) { + tempSuffix = [lastComponent integerValue]; + + // -1 for space. + NSInteger index = tempBase.length - lastComponent.length - 1; + tempBase = [tempBase substringToIndex:index]; + } + } + tempSuffix++; + + *baseString = tempBase; + *suffix = tempSuffix; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileUploadOperation.h b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileUploadOperation.h new file mode 100644 index 0000000..66252e8 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileUploadOperation.h @@ -0,0 +1,39 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFileBaseOperation.h" + +@protocol OCTFileInputProtocol; + +@interface OCTFileUploadOperation : OCTFileBaseOperation + +@property (strong, nonatomic, readonly, nonnull) id input; + +/** + * Create operation. + * + * @param fileInput Input to use as a source for file transfer. + * + * For other parameters description see OCTFileBaseOperation. + */ +- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox + fileInput:(nonnull id)fileInput + friendNumber:(OCTToxFriendNumber)friendNumber + fileNumber:(OCTToxFileNumber)fileNumber + fileSize:(OCTToxFileSize)fileSize + userInfo:(nullable NSDictionary *)userInfo + progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock + etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock + successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock + failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock; + +/** + * Call this method to request next chunk. + * + * @param position The file or stream position from which to continue reading. + * @param length The number of bytes requested for the current chunk. + */ +- (void)chunkRequestWithPosition:(OCTToxFileSize)position length:(size_t)length; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileUploadOperation.m b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileUploadOperation.m new file mode 100644 index 0000000..4d79b95 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Files/OCTFileUploadOperation.m @@ -0,0 +1,109 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFileUploadOperation.h" +#import "OCTFileBaseOperation+Private.h" +#import "OCTFileInputProtocol.h" +#import "OCTLogging.h" +#import "NSError+OCTFile.h" + +@interface OCTFileUploadOperation () + +@end + +@implementation OCTFileUploadOperation + +#pragma mark - Public + +- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox + fileInput:(nonnull id)fileInput + friendNumber:(OCTToxFriendNumber)friendNumber + fileNumber:(OCTToxFileNumber)fileNumber + fileSize:(OCTToxFileSize)fileSize + userInfo:(nullable NSDictionary *)userInfo + progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock + etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock + successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock + failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock +{ + NSParameterAssert(fileInput); + + self = [super initWithTox:tox + friendNumber:friendNumber + fileNumber:fileNumber + fileSize:fileSize + userInfo:userInfo + progressBlock:progressBlock + etaUpdateBlock:etaUpdateBlock + successBlock:successBlock + failureBlock:failureBlock]; + + if (! self) { + return nil; + } + + _input = fileInput; + + return self; +} + +#pragma mark - Public + +- (void)chunkRequestWithPosition:(OCTToxFileSize)position length:(size_t)length +{ + if (length == 0) { + [self finishWithSuccess]; + return; + } + + NSData *data = [self.input bytesWithPosition:position length:length]; + + if (! data) { + [self finishWithError:[NSError sendFileErrorCannotReadFile]]; + return; + } + + NSError *error; + + BOOL result; + do { + result = [self.tox fileSendChunkForFileNumber:self.fileNumber + friendNumber:self.friendNumber + position:position + data:data + error:&error]; + + if (! result) { + [NSThread sleepForTimeInterval:0.01]; + } + } + while (! result && error.code == OCTToxErrorFileSendChunkSendq); + + if (! result) { + OCTLogWarn(@"upload error %@", error); + + [self.tox fileSendControlForFileNumber:self.fileNumber + friendNumber:self.friendNumber + control:OCTToxFileControlCancel + error:nil]; + + [self finishWithError:[NSError acceptFileErrorFromToxFileSendChunkError:error.code]]; + return; + } + + [self updateBytesDone:position + length]; +} + +#pragma mark - Override + +- (void)operationStarted +{ + [super operationStarted]; + + if (! [self.input prepareToRead]) { + [self finishWithError:[NSError sendFileErrorCannotReadFile]]; + } +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Messages/OCTSendMessageOperation.h b/local_pod_repo/objcTox/Classes/Private/Manager/Messages/OCTSendMessageOperation.h new file mode 100644 index 0000000..d1bdd97 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Messages/OCTSendMessageOperation.h @@ -0,0 +1,42 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTToxConstants.h" + +@class OCTTox; + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^OCTSendMessageOperationSuccessBlock)(OCTToxMessageId messageId); +typedef void (^OCTSendMessageOperationFailureBlock)(NSError *error); + +@interface OCTSendMessageOperation : NSOperation + +/** + * Create operation. + * + * @param tox Tox object to send to. + * @param friendNumber Number of friend to send to. + * @param messageType Type of the message to send. + * @param message Message to send. + * @param msgv3HashHex the messageV3 Hash Hexstring or nil. + * @param successBlock Block called on operation success. Block will be called on main thread. + * @param failureBlock Block called on loading error. Block will be called on main thread. + */ +- (instancetype)initWithTox:(OCTTox *)tox + friendNumber:(OCTToxFriendNumber)friendNumber + messageType:(OCTToxMessageType)messageType + message:(NSString *)message + msgv3HashHex:(NSString *)msgv3HashHex + msgv3tssec:(UInt32)msgv3tssec + successBlock:(nullable OCTSendMessageOperationSuccessBlock)successBlock + failureBlock:(nullable OCTSendMessageOperationFailureBlock)failureBlock; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Messages/OCTSendMessageOperation.m b/local_pod_repo/objcTox/Classes/Private/Manager/Messages/OCTSendMessageOperation.m new file mode 100644 index 0000000..233e3fe --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Messages/OCTSendMessageOperation.m @@ -0,0 +1,80 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSendMessageOperation.h" +#import "OCTTox.h" + +@interface OCTSendMessageOperation () + +@property (weak, nonatomic, readonly) OCTTox *tox; + +@property (assign, nonatomic, readonly) OCTToxFriendNumber friendNumber; +@property (assign, nonatomic, readonly) OCTToxMessageType messageType; +@property (copy, nonatomic, readonly) NSString *message; +@property (copy, nonatomic, readonly) NSString *msgv3HashHex; +@property (assign, nonatomic, readonly) UInt32 msgv3tssec; +@property (copy, nonatomic, readonly) OCTSendMessageOperationSuccessBlock successBlock; +@property (copy, nonatomic, readonly) OCTSendMessageOperationFailureBlock failureBlock; + +@end + +@implementation OCTSendMessageOperation + +- (instancetype)initWithTox:(OCTTox *)tox + friendNumber:(OCTToxFriendNumber)friendNumber + messageType:(OCTToxMessageType)messageType + message:(NSString *)message + msgv3HashHex:(NSString *)msgv3HashHex + msgv3tssec:(UInt32)msgv3tssec + successBlock:(nullable OCTSendMessageOperationSuccessBlock)successBlock + failureBlock:(nullable OCTSendMessageOperationFailureBlock)failureBlock +{ + self = [super init]; + + if (! self) { + return nil; + } + + _tox = tox; + _friendNumber = friendNumber; + _messageType = messageType; + _message = [message copy]; + _msgv3HashHex = [msgv3HashHex copy]; + _msgv3tssec = msgv3tssec; + _successBlock = [successBlock copy]; + _failureBlock = [failureBlock copy]; + + return self; +} + +- (void)main +{ + if (self.cancelled) { + return; + } + + NSError *error; + + OCTToxMessageId messageId = [self.tox sendMessageWithFriendNumber:self.friendNumber + type:self.messageType + message:self.message + msgv3HashHex:self.msgv3HashHex + msgv3tssec:self.msgv3tssec + error:&error]; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.cancelled) { + return; + } + + if (error && self.failureBlock) { + self.failureBlock(error); + } + else if (! error && self.successBlock) { + self.successBlock(messageId); + } + }); +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/OCTManagerConstants.m b/local_pod_repo/objcTox/Classes/Private/Manager/OCTManagerConstants.m new file mode 100644 index 0000000..9d5ca38 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/OCTManagerConstants.m @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTManagerConstants.h" + +NSString *const kOCTManagerErrorDomain = @"me.dvor.objcTox.OCTManagerErrorDomain"; diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/OCTManagerFactory.m b/local_pod_repo/objcTox/Classes/Private/Manager/OCTManagerFactory.m new file mode 100644 index 0000000..ec2b6ed --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/OCTManagerFactory.m @@ -0,0 +1,475 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTManagerFactory.h" +#import "OCTManagerImpl.h" +#import "OCTManagerConfiguration.h" +#import "OCTRealmManager.h" +#import "OCTTox.h" +#import "OCTToxEncryptSave.h" +#import "OCTToxEncryptSaveConstants.h" + +typedef NS_ENUM(NSInteger, OCTDecryptionErrorFileType) { + OCTDecryptionErrorFileTypeDatabaseKey, + OCTDecryptionErrorFileTypeToxFile, +}; + +static const NSUInteger kEncryptedKeyLength = 64; + +@implementation OCTManagerFactory + ++ (void)managerWithConfiguration:(OCTManagerConfiguration *)configuration + encryptPassword:(nonnull NSString *)encryptPassword + successBlock:(void (^)(id manager))successBlock + failureBlock:(void (^)(NSError *error))failureBlock +{ + [self validateConfiguration:configuration]; + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_group_t group = dispatch_group_create(); + + // Decrypting Realm. + __block NSData *realmEncryptionKey = nil; + __block NSError *decryptRealmError = nil; + + dispatch_group_async(group, queue, ^{ + realmEncryptionKey = [self realmEncryptionKeyWithConfiguration:configuration + password:encryptPassword + error:&decryptRealmError]; + }); + + + // Decrypting Tox save. + __block NSError *decryptToxError = nil; + __block OCTToxEncryptSave *encryptSave = nil; + __block NSData *toxSave = nil; + + dispatch_group_async(group, queue, ^{ + if (! [self importToxSaveIfNeeded:configuration error:&decryptToxError]) { + return; + } + + NSData *savedData = [self getSavedDataFromPath:configuration.fileStorage.pathForToxSaveFile]; + + encryptSave = [self toxEncryptSaveWithToxPassword:encryptPassword savedData:savedData error:&decryptToxError]; + + if (! encryptSave) { + return; + } + + savedData = [self decryptSavedData:savedData encryptSave:encryptSave error:&decryptToxError]; + + if (! savedData) { + return; + } + + toxSave = savedData; + }); + + dispatch_async(queue, ^{ + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + dispatch_async(dispatch_get_main_queue(), ^{ + NSError *error = decryptRealmError ?: decryptToxError; + + if (error) { + if (failureBlock) { + failureBlock(error); + } + return; + } + + OCTTox *tox = [self createToxWithOptions:configuration.options toxData:toxSave error:&error]; + + if (! tox) { + if (failureBlock) { + failureBlock(error); + } + return; + } + + NSURL *databaseFileURL = [NSURL fileURLWithPath:configuration.fileStorage.pathForDatabase]; + OCTRealmManager *realmManager = [[OCTRealmManager alloc] initWithDatabaseFileURL:databaseFileURL encryptionKey:realmEncryptionKey]; + + OCTManagerImpl *manager = [[OCTManagerImpl alloc] initWithConfiguration:configuration + tox:tox + toxEncryptSave:encryptSave + realmManager:realmManager]; + + if (successBlock) { + successBlock(manager); + } + }); + }); +} + +#pragma mark - Private + ++ (void)validateConfiguration:(OCTManagerConfiguration *)configuration +{ + NSParameterAssert(configuration.fileStorage); + NSParameterAssert(configuration.fileStorage.pathForDownloadedFilesDirectory); + NSParameterAssert(configuration.fileStorage.pathForUploadedFilesDirectory); + NSParameterAssert(configuration.fileStorage.pathForTemporaryFilesDirectory); + + NSParameterAssert(configuration.options); +} + ++ (NSData *)realmEncryptionKeyWithConfiguration:(OCTManagerConfiguration *)configuration + password:(NSString *)databasePassword + error:(NSError **)error +{ + NSString *databasePath = configuration.fileStorage.pathForDatabase; + NSString *encryptedKeyPath = configuration.fileStorage.pathForDatabaseEncryptionKey; + + BOOL databaseExists = [[NSFileManager defaultManager] fileExistsAtPath:databasePath]; + BOOL encryptedKeyExists = [[NSFileManager defaultManager] fileExistsAtPath:encryptedKeyPath]; + + if (! databaseExists && ! encryptedKeyExists) { + // First run, create key and database. + if (! [self createEncryptedKeyAtPath:encryptedKeyPath withPassword:databasePassword]) { + [self fillError:error withInitErrorCode:OCTManagerInitErrorDatabaseKeyCannotCreateKey]; + return nil; + } + } + + if (databaseExists && ! encryptedKeyExists) { + // It seems that we found old unencrypted database, let's migrate to encrypted one. + NSError *migrationError; + + BOOL result = [self migrateToEncryptedDatabase:databasePath + encryptionKeyPath:encryptedKeyPath + withPassword:databasePassword + error:&migrationError]; + + if (! result) { + if (error) { + *error = [NSError errorWithDomain:kOCTManagerErrorDomain code:OCTManagerInitErrorDatabaseKeyMigrationToEncryptedFailed userInfo:@{ + NSLocalizedDescriptionKey : migrationError.localizedDescription, + NSLocalizedFailureReasonErrorKey : migrationError.localizedFailureReason, + }]; + } + + return nil; + } + } + + NSData *encryptedKey = [NSData dataWithContentsOfFile:encryptedKeyPath]; + + if (! encryptedKey) { + [self fillError:error withInitErrorCode:OCTManagerInitErrorDatabaseKeyCannotReadKey]; + return nil; + } + + NSError *decryptError; + NSData *key = [OCTToxEncryptSave decryptData:encryptedKey withPassphrase:databasePassword error:&decryptError]; + + if (! key) { + [self fillError:error withDecryptionError:decryptError.code fileType:OCTDecryptionErrorFileTypeDatabaseKey]; + return nil; + } + + return key; +} + ++ (BOOL)createEncryptedKeyAtPath:(NSString *)path withPassword:(NSString *)password +{ + NSMutableData *key = [NSMutableData dataWithLength:kEncryptedKeyLength]; + SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes); + + NSData *encryptedKey = [OCTToxEncryptSave encryptData:key withPassphrase:password error:nil]; + + return [encryptedKey writeToFile:path options:NSDataWritingAtomic error:nil]; +} + ++ (BOOL)fillError:(NSError **)error withDecryptionError:(OCTToxEncryptSaveDecryptionError)code fileType:(OCTDecryptionErrorFileType)fileType +{ + if (! error) { + return NO; + } + + NSDictionary *mapping; + + switch (fileType) { + case OCTDecryptionErrorFileTypeDatabaseKey: + mapping = @{ + @(OCTToxEncryptSaveDecryptionErrorNull) : @(OCTManagerInitErrorDatabaseKeyDecryptNull), + @(OCTToxEncryptSaveDecryptionErrorBadFormat) : @(OCTManagerInitErrorDatabaseKeyDecryptBadFormat), + @(OCTToxEncryptSaveDecryptionErrorFailed) : @(OCTManagerInitErrorDatabaseKeyDecryptFailed), + }; + break; + case OCTDecryptionErrorFileTypeToxFile: + mapping = @{ + @(OCTToxEncryptSaveDecryptionErrorNull) : @(OCTManagerInitErrorToxFileDecryptNull), + @(OCTToxEncryptSaveDecryptionErrorBadFormat) : @(OCTManagerInitErrorToxFileDecryptBadFormat), + @(OCTToxEncryptSaveDecryptionErrorFailed) : @(OCTManagerInitErrorToxFileDecryptFailed), + }; + break; + } + + OCTManagerInitError initErrorCode = [mapping[@(code)] integerValue]; + [self fillError:error withInitErrorCode:initErrorCode]; + + return YES; +} + ++ (BOOL)fillError:(NSError **)error withInitErrorCode:(OCTManagerInitError)code +{ + if (! error) { + return NO; + } + + NSString *failureReason = nil; + + switch (code) { + case OCTManagerInitErrorPassphraseFailed: + failureReason = @"Cannot create symmetric key from given passphrase."; + break; + case OCTManagerInitErrorCannotImportToxSave: + failureReason = @"Cannot copy tox save at `importToxSaveFromPath` path."; + break; + case OCTManagerInitErrorDatabaseKeyCannotCreateKey: + failureReason = @"Cannot create encryption key."; + break; + case OCTManagerInitErrorDatabaseKeyCannotReadKey: + failureReason = @"Cannot read encryption key."; + break; + case OCTManagerInitErrorDatabaseKeyMigrationToEncryptedFailed: + // Nothing to do here, this error will be created elsewhere. + break; + case OCTManagerInitErrorDatabaseKeyDecryptNull: + failureReason = @"Cannot decrypt database key file. Some input data was empty."; + break; + case OCTManagerInitErrorDatabaseKeyDecryptBadFormat: + failureReason = @"Cannot decrypt database key file. Data has bad format."; + break; + case OCTManagerInitErrorDatabaseKeyDecryptFailed: + failureReason = @"Cannot decrypt database key file. The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect."; + break; + case OCTManagerInitErrorToxFileDecryptNull: + failureReason = @"Cannot decrypt tox save file. Some input data was empty."; + break; + case OCTManagerInitErrorToxFileDecryptBadFormat: + failureReason = @"Cannot decrypt tox save file. Data has bad format."; + break; + case OCTManagerInitErrorToxFileDecryptFailed: + failureReason = @"Cannot decrypt tox save file. The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect."; + break; + case OCTManagerInitErrorCreateToxUnknown: + failureReason = @"Cannot create tox. Unknown error occurred."; + break; + case OCTManagerInitErrorCreateToxMemoryError: + failureReason = @"Cannot create tox. Was unable to allocate enough memory to store the internal structures for the Tox object."; + break; + case OCTManagerInitErrorCreateToxPortAlloc: + failureReason = @"Cannot create tox. Was unable to bind to a port."; + break; + case OCTManagerInitErrorCreateToxProxyBadType: + failureReason = @"Cannot create tox. Proxy type was invalid."; + break; + case OCTManagerInitErrorCreateToxProxyBadHost: + failureReason = @"Cannot create tox. proxyAddress had an invalid format or was nil (while proxyType was set)."; + break; + case OCTManagerInitErrorCreateToxProxyBadPort: + failureReason = @"Cannot create tox. Proxy port was invalid."; + break; + case OCTManagerInitErrorCreateToxProxyNotFound: + failureReason = @"Cannot create tox. The proxy host passed could not be resolved."; + break; + case OCTManagerInitErrorCreateToxEncrypted: + failureReason = @"Cannot create tox. The saved data to be loaded contained an encrypted save."; + break; + case OCTManagerInitErrorCreateToxBadFormat: + failureReason = @"Cannot create tox. Data has bad format."; + break; + } + + *error = [NSError errorWithDomain:kOCTManagerErrorDomain code:code userInfo:@{ + NSLocalizedDescriptionKey : @"Cannot create OCTManager", + NSLocalizedFailureReasonErrorKey : failureReason + }]; + + return YES; +} + ++ (BOOL)migrateToEncryptedDatabase:(NSString *)databasePath + encryptionKeyPath:(NSString *)encryptionKeyPath + withPassword:(NSString *)password + error:(NSError **)error +{ + NSParameterAssert(databasePath); + NSParameterAssert(encryptionKeyPath); + NSParameterAssert(password); + + if ([[NSFileManager defaultManager] fileExistsAtPath:encryptionKeyPath]) { + if (error) { + *error = [NSError errorWithDomain:kOCTManagerErrorDomain code:100 userInfo:@{ + NSLocalizedDescriptionKey : @"Cannot migrate unencrypted database to encrypted", + NSLocalizedFailureReasonErrorKey : @"Database is already encrypted", + }]; + } + return NO; + } + + NSString *tempKeyPath = [encryptionKeyPath stringByAppendingPathExtension:@"tmp"]; + + if (! [self createEncryptedKeyAtPath:tempKeyPath withPassword:password]) { + if (error) { + *error = [NSError errorWithDomain:kOCTManagerErrorDomain code:101 userInfo:@{ + NSLocalizedDescriptionKey : @"Cannot migrate unencrypted database to encrypted", + NSLocalizedFailureReasonErrorKey : @"Cannot create encryption key", + }]; + } + return NO; + } + + NSData *encryptedKey = [NSData dataWithContentsOfFile:tempKeyPath]; + + if (! encryptedKey) { + if (error) { + *error = [NSError errorWithDomain:kOCTManagerErrorDomain code:102 userInfo:@{ + NSLocalizedDescriptionKey : @"Cannot migrate unencrypted database to encrypted", + NSLocalizedFailureReasonErrorKey : @"Cannot find encryption key", + }]; + } + return NO; + } + + NSData *key = [OCTToxEncryptSave decryptData:encryptedKey withPassphrase:password error:error]; + + if (! key) { + return NO; + } + + if (! [OCTRealmManager migrateToEncryptedDatabase:databasePath encryptionKey:key error:error]) { + return NO; + } + + if (! [[NSFileManager defaultManager] moveItemAtPath:tempKeyPath toPath:encryptionKeyPath error:error]) { + return NO; + } + + return YES; +} + ++ (BOOL)importToxSaveIfNeeded:(OCTManagerConfiguration *)configuration error:(NSError **)error +{ + BOOL result = YES; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if (configuration.importToxSaveFromPath && [fileManager fileExistsAtPath:configuration.importToxSaveFromPath]) { + result = [fileManager copyItemAtPath:configuration.importToxSaveFromPath + toPath:configuration.fileStorage.pathForToxSaveFile + error:nil]; + } + + if (! result) { + [self fillError:error withInitErrorCode:OCTManagerInitErrorCannotImportToxSave]; + } + + return result; +} + ++ (OCTToxEncryptSave *)toxEncryptSaveWithToxPassword:(NSString *)toxPassword + savedData:(NSData *)savedData + error:(NSError **)error +{ + OCTToxEncryptSave *encryptSave; + + if (savedData && [OCTToxEncryptSave isDataEncrypted:savedData]) { + encryptSave = [[OCTToxEncryptSave alloc] initWithPassphrase:toxPassword toxData:savedData error:nil]; + } + else { + // Save data wasn't encrypted. Passing nil as toxData parameter to encrypt it next time. + encryptSave = [[OCTToxEncryptSave alloc] initWithPassphrase:toxPassword toxData:nil error:nil]; + } + + if (! encryptSave) { + [self fillError:error withInitErrorCode:OCTManagerInitErrorPassphraseFailed]; + return nil; + } + + return encryptSave; +} + ++ (NSData *)decryptSavedData:(NSData *)data encryptSave:(OCTToxEncryptSave *)encryptSave error:(NSError **)error +{ + NSParameterAssert(encryptSave); + + if (! data) { + return data; + } + + if (! [OCTToxEncryptSave isDataEncrypted:data]) { + // Tox data wasn't encrypted, nothing to do here. + return data; + } + + NSError *decryptError = nil; + + NSData *result = [encryptSave decryptData:data error:&decryptError]; + + if (result) { + return result; + } + + [self fillError:error withDecryptionError:decryptError.code fileType:OCTDecryptionErrorFileTypeToxFile]; + + return nil; +} + ++ (NSData *)getSavedDataFromPath:(NSString *)path +{ + return [[NSFileManager defaultManager] fileExistsAtPath:path] ? + ([NSData dataWithContentsOfFile:path]) : + nil; +} + ++ (OCTTox *)createToxWithOptions:(OCTToxOptions *)options toxData:(NSData *)toxData error:(NSError **)error +{ + NSError *toxError = nil; + + OCTTox *tox = [[OCTTox alloc] initWithOptions:options savedData:toxData error:&toxError]; + + if (tox) { + return tox; + } + + OCTToxErrorInitCode code = toxError.code; + + switch (code) { + case OCTToxErrorInitCodeUnknown: + [self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxUnknown]; + break; + case OCTToxErrorInitCodeMemoryError: + [self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxMemoryError]; + break; + case OCTToxErrorInitCodePortAlloc: + [self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxPortAlloc]; + break; + case OCTToxErrorInitCodeProxyBadType: + [self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxProxyBadType]; + break; + case OCTToxErrorInitCodeProxyBadHost: + [self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxProxyBadHost]; + break; + case OCTToxErrorInitCodeProxyBadPort: + [self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxProxyBadPort]; + break; + case OCTToxErrorInitCodeProxyNotFound: + [self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxProxyNotFound]; + break; + case OCTToxErrorInitCodeEncrypted: + [self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxEncrypted]; + break; + case OCTToxErrorInitCodeLoadBadFormat: + [self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxBadFormat]; + break; + } + + return nil; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/OCTManagerImpl.h b/local_pod_repo/objcTox/Classes/Private/Manager/OCTManagerImpl.h new file mode 100644 index 0000000..1e40947 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/OCTManagerImpl.h @@ -0,0 +1,27 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTManager.h" + +NS_ASSUME_NONNULL_BEGIN + +@class OCTManagerConfiguration; +@class OCTTox; +@class OCTToxEncryptSave; +@class OCTRealmManager; + +@interface OCTManagerImpl : NSObject + +- (instancetype)initWithConfiguration:(OCTManagerConfiguration *)configuration + tox:(OCTTox *)tox + toxEncryptSave:(OCTToxEncryptSave *)toxEncryptSave + realmManager:(OCTRealmManager *)realmManager; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/OCTManagerImpl.m b/local_pod_repo/objcTox/Classes/Private/Manager/OCTManagerImpl.m new file mode 100644 index 0000000..645f574 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/OCTManagerImpl.m @@ -0,0 +1,347 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTManagerImpl.h" +#import "OCTTox.h" +#import "OCTToxEncryptSave.h" +#import "OCTManagerConfiguration.h" +#import "OCTManagerFactory.h" +#import "OCTSubmanagerBootstrapImpl.h" +#import "OCTSubmanagerCallsImpl.h" +#import "OCTSubmanagerChatsImpl.h" +#import "OCTSubmanagerFilesImpl.h" +#import "OCTSubmanagerFriendsImpl.h" +#import "OCTSubmanagerObjectsImpl.h" +#import "OCTSubmanagerUserImpl.h" +#import "OCTRealmManager.h" + +@interface OCTManagerImpl () + +@property (copy, nonatomic, readonly) OCTManagerConfiguration *currentConfiguration; + +@property (strong, nonatomic, readonly) OCTTox *tox; +@property (strong, nonatomic, readonly) NSObject *toxSaveFileLock; + +@property (strong, nonatomic, nonnull) OCTToxEncryptSave *encryptSave; + +@property (strong, nonatomic, readonly) OCTRealmManager *realmManager; +@property (strong, atomic) NSNotificationCenter *notificationCenter; + +@property (strong, nonatomic, readwrite) OCTSubmanagerBootstrapImpl *bootstrap; +@property (strong, nonatomic, readwrite) OCTSubmanagerCallsImpl *calls; +@property (strong, nonatomic, readwrite) OCTSubmanagerChatsImpl *chats; +@property (strong, nonatomic, readwrite) OCTSubmanagerFilesImpl *files; +@property (strong, nonatomic, readwrite) OCTSubmanagerFriendsImpl *friends; +@property (strong, nonatomic, readwrite) OCTSubmanagerObjectsImpl *objects; +@property (strong, nonatomic, readwrite) OCTSubmanagerUserImpl *user; + +@end + +@implementation OCTManagerImpl + +#pragma mark - Lifecycle + +- (instancetype)initWithConfiguration:(OCTManagerConfiguration *)configuration + tox:(OCTTox *)tox + toxEncryptSave:(OCTToxEncryptSave *)toxEncryptSave + realmManager:(OCTRealmManager *)realmManager +{ + self = [super init]; + + if (! self) { + return nil; + } + + _currentConfiguration = [configuration copy]; + + _tox = tox; + _tox.delegate = self; + _toxSaveFileLock = [NSObject new]; + + _encryptSave = toxEncryptSave; + + _realmManager = realmManager; + _notificationCenter = [[NSNotificationCenter alloc] init]; + + [_tox start]; + [self saveTox]; + + [self createSubmanagers]; + + return self; +} + +- (void)dealloc +{ + [self killSubmanagers]; + [self.tox stop]; +} + +#pragma mark - Public + +- (OCTManagerConfiguration *)configuration +{ + return [self.currentConfiguration copy]; +} + +- (NSString *)exportToxSaveFileAndReturnError:(NSError **)error +{ + @synchronized(self.toxSaveFileLock) { + NSString *savedDataPath = self.currentConfiguration.fileStorage.pathForToxSaveFile; + NSString *tempPath = self.currentConfiguration.fileStorage.pathForTemporaryFilesDirectory; + tempPath = [tempPath stringByAppendingPathComponent:[savedDataPath lastPathComponent]]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if ([fileManager fileExistsAtPath:tempPath]) { + [fileManager removeItemAtPath:tempPath error:error]; + } + + if (! [fileManager copyItemAtPath:savedDataPath toPath:tempPath error:error]) { + return nil; + } + + return tempPath; + } +} + +- (BOOL)changeEncryptPassword:(nonnull NSString *)newPassword oldPassword:(nonnull NSString *)oldPassword +{ + OCTToxEncryptSave *encryptSave = [self changeToxPassword:newPassword oldPassword:oldPassword]; + if (encryptSave == nil) { + return NO; + } + + if (! [self changeDatabasePassword:newPassword oldPassword:oldPassword]) { + return NO; + } + + self.encryptSave = encryptSave; + [self saveTox]; + + return YES; +} + +- (BOOL)isManagerEncryptedWithPassword:(nonnull NSString *)password +{ + NSString *toxFilePath = self.currentConfiguration.fileStorage.pathForToxSaveFile; + + return [self isDataAtPath:toxFilePath encryptedWithPassword:password]; +} + +#pragma mark - OCTSubmanagerDataSource + +- (OCTTox *)managerGetTox +{ + return self.tox; +} + +- (BOOL)managerIsToxConnected +{ + return (self.user.connectionStatus != OCTToxConnectionStatusNone); +} + +- (void)managerSaveTox +{ + return [self saveTox]; +} + +- (OCTRealmManager *)managerGetRealmManager +{ + return self.realmManager; +} + +- (id)managerGetFileStorage +{ + return self.currentConfiguration.fileStorage; +} + +- (NSNotificationCenter *)managerGetNotificationCenter +{ + return self.notificationCenter; +} + +- (BOOL)managerUseFauxOfflineMessaging +{ + return self.currentConfiguration.useFauxOfflineMessaging; +} + +#pragma mark - Private + +- (NSData *)getSavedDataFromPath:(NSString *)path +{ + return [[NSFileManager defaultManager] fileExistsAtPath:path] ? + ([NSData dataWithContentsOfFile:path]) : + nil; +} + +- (BOOL)isDataAtPath:(NSString *)path encryptedWithPassword:(NSString *)password +{ + NSData *savedData = [self getSavedDataFromPath:path]; + + if (! savedData) { + return NO; + } + + if ([OCTToxEncryptSave isDataEncrypted:savedData]) { + return [OCTToxEncryptSave decryptData:savedData withPassphrase:password error:nil] != nil; + } + + return NO; +} + +- (void)createSubmanagers +{ + _bootstrap = [self createSubmanagerWithClass:[OCTSubmanagerBootstrapImpl class]]; + _chats = [self createSubmanagerWithClass:[OCTSubmanagerChatsImpl class]]; + _files = [self createSubmanagerWithClass:[OCTSubmanagerFilesImpl class]]; + _friends = [self createSubmanagerWithClass:[OCTSubmanagerFriendsImpl class]]; + _objects = [self createSubmanagerWithClass:[OCTSubmanagerObjectsImpl class]]; + _user = [self createSubmanagerWithClass:[OCTSubmanagerUserImpl class]]; + + OCTSubmanagerCallsImpl *calls = [[OCTSubmanagerCallsImpl alloc] initWithTox:_tox]; + calls.dataSource = self; + _calls = calls; + [_calls setupAndReturnError:nil]; +} + +- (void)killSubmanagers +{ + self.bootstrap = nil; + self.calls = nil; + self.chats = nil; + self.files = nil; + self.friends = nil; + self.objects = nil; + self.user = nil; +} + +- (id)createSubmanagerWithClass:(Class)class +{ + id submanager = [[class alloc] init]; + submanager.dataSource = self; + + if ([submanager respondsToSelector:@selector(configure)]) { + [submanager configure]; + } + + return submanager; +} + +- (BOOL)respondsToSelector:(SEL)aSelector +{ + id submanager = [self forwardingTargetForSelector:aSelector]; + + if (submanager) { + return YES; + } + + return [super respondsToSelector:aSelector]; +} + +- (id)forwardingTargetForSelector:(SEL)aSelector +{ + struct objc_method_description description = protocol_getMethodDescription(@protocol(OCTToxDelegate), aSelector, NO, YES); + + if (description.name == NULL) { + // We forward methods only from OCTToxDelegate protocol. + return nil; + } + + NSArray *submanagers = @[ + self.bootstrap, + self.chats, + self.files, + self.friends, + self.objects, + self.user, + ]; + + for (id delegate in submanagers) { + if ([delegate respondsToSelector:aSelector]) { + return delegate; + } + } + + return nil; +} + +- (void)saveTox +{ + @synchronized(self.toxSaveFileLock) { + void (^throwException)(NSError *) = ^(NSError *error) { + NSDictionary *userInfo = nil; + + if (error) { + userInfo = @{ @"NSError" : error }; + } + + @throw [NSException exceptionWithName:@"saveToxException" reason:error.debugDescription userInfo:userInfo]; + }; + + NSData *data = [self.tox save]; + + NSError *error; + + data = [self.encryptSave encryptData:data error:&error]; + + if (! data) { + throwException(error); + } + + if (! [data writeToFile:self.currentConfiguration.fileStorage.pathForToxSaveFile options:NSDataWritingAtomic error:&error]) { + throwException(error); + } + } +} + +// On success returns encryptSave with new password. +- (OCTToxEncryptSave *)changeToxPassword:(NSString *)newPassword oldPassword:(NSString *)oldPassword +{ + NSString *toxFilePath = self.currentConfiguration.fileStorage.pathForToxSaveFile; + + if (! [self isDataAtPath:toxFilePath encryptedWithPassword:oldPassword]) { + return nil; + } + + __block OCTToxEncryptSave *newEncryptSave; + + @synchronized(self.toxSaveFileLock) { + // Passing nil as tox data as we are setting new password. + newEncryptSave = [[OCTToxEncryptSave alloc] initWithPassphrase:newPassword toxData:nil error:nil]; + } + + return newEncryptSave; +} + +- (BOOL)changeDatabasePassword:(NSString *)newPassword oldPassword:(NSString *)oldPassword +{ + NSParameterAssert(newPassword); + NSParameterAssert(oldPassword); + + NSString *encryptedKeyPath = self.currentConfiguration.fileStorage.pathForDatabaseEncryptionKey; + NSData *encryptedKey = [NSData dataWithContentsOfFile:encryptedKeyPath]; + + if (! encryptedKey) { + return NO; + } + + NSData *key = [OCTToxEncryptSave decryptData:encryptedKey withPassphrase:oldPassword error:nil]; + + if (! key) { + return NO; + } + + NSData *newEncryptedKey = [OCTToxEncryptSave encryptData:key withPassphrase:newPassword error:nil]; + + if (! newEncryptedKey) { + return NO; + } + + return [newEncryptedKey writeToFile:encryptedKeyPath options:NSDataWritingAtomic error:nil]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCall+Utilities.h b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCall+Utilities.h new file mode 100644 index 0000000..1ee1569 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCall+Utilities.h @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTCall.h" + +@interface OCTCall (Utilities) + +- (BOOL)isPaused; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCall+Utilities.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCall+Utilities.m new file mode 100644 index 0000000..aa5fcab --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCall+Utilities.m @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTCall+Utilities.h" + +@implementation OCTCall (Utilities) + +- (BOOL)isPaused +{ + return (self.pausedStatus != OCTCallPausedStatusNone); +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCall.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCall.m new file mode 100644 index 0000000..fb5bff2 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCall.m @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTCall+Utilities.h" +#import "OCTToxAVConstants.h" + +@interface OCTCall () + +@end + +@implementation OCTCall + +- (BOOL)isOutgoing +{ + return (self.caller == nil); +} + +- (NSDate *)onHoldDate +{ + if (self.onHoldStartInterval <= 0) { + return nil; + } + + return [NSDate dateWithTimeIntervalSince1970:self.onHoldStartInterval]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCallTimer.h b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCallTimer.h new file mode 100644 index 0000000..d8f270d --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCallTimer.h @@ -0,0 +1,25 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +@class OCTRealmManager; +@class OCTCall; + +@interface OCTCallTimer : NSObject + +- (instancetype)initWithRealmManager:(OCTRealmManager *)realmManager; + +/** + * Starts the timer for the specified call. + * Note that there can only be one active call. + * @param call Call to update. + */ +- (void)startTimerForCall:(OCTCall *)call; + +/** + * Stops the timer for the current call in session. + */ +- (void)stopTimer; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCallTimer.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCallTimer.m new file mode 100644 index 0000000..da5df5b --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTCallTimer.m @@ -0,0 +1,87 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTCallTimer.h" +#import "OCTRealmManager.h" +#import "OCTCall.h" +#import "OCTLogging.h" + +@interface OCTCallTimer () + +@property (strong, nonatomic) dispatch_source_t timer; +@property (strong, nonatomic) OCTRealmManager *realmManager; +@property (strong, nonatomic) OCTCall *call; + +@end + +@implementation OCTCallTimer + +- (instancetype)initWithRealmManager:(OCTRealmManager *)realmManager +{ + self = [super init]; + + if (! self) { + return nil; + } + + _realmManager = realmManager; + + return self; +} + +- (void)startTimerForCall:(OCTCall *)call +{ + @synchronized(self) { + if (self.timer) { + NSAssert(! self.timer, @"There is already a timer in progress!"); + } + + self.call = call; + + // dispatch_queue_t queue = dispatch_queue_create("me.dvor.objcTox.OCTCallQueue", DISPATCH_QUEUE_SERIAL); + // Main queue is used temporarily for now since we are getting 'Realm accessed from incorrect thread'. + // Should really be using the queue above.. + + self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); + uint64_t interval = NSEC_PER_SEC; + uint64_t leeway = NSEC_PER_SEC / 1000; + dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, interval, leeway); + + __weak OCTCallTimer *weakSelf = self; + + dispatch_source_set_event_handler(self.timer, ^{ + OCTCallTimer *strongSelf = weakSelf; + if (! strongSelf) { + dispatch_source_cancel(self.timer); + OCTLogError(@"Error: Attempt to update timer with no strong pointer to OCTCallTimer"); + return; + } + + [strongSelf.realmManager updateObject:strongSelf.call withBlock:^(OCTCall *callToUpdate) { + callToUpdate.callDuration += 1.0; + }]; + + OCTLogInfo(@"Call: %@ duration at %f seconds", strongSelf.call, strongSelf.call.callDuration); + }); + + dispatch_resume(self.timer); + } +} + +- (void)stopTimer +{ + @synchronized(self) { + if (! self.timer) { + return; + } + + OCTLogInfo(@"Timer for call %@ has stopped at duration %f", self.call, self.call.callDuration); + + dispatch_source_cancel(self.timer); + self.timer = nil; + self.call = nil; + } +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTChat.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTChat.m new file mode 100644 index 0000000..d97b0ec --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTChat.m @@ -0,0 +1,39 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTChat.h" +#import "OCTMessageAbstract.h" + +@interface OCTChat () + +@end + +@implementation OCTChat + +#pragma mark - Public + +- (NSDate *)lastReadDate +{ + if (self.lastReadDateInterval <= 0) { + return nil; + } + + return [NSDate dateWithTimeIntervalSince1970:self.lastReadDateInterval]; +} + +- (NSDate *)lastActivityDate +{ + if (self.lastActivityDateInterval <= 0) { + return nil; + } + + return [NSDate dateWithTimeIntervalSince1970:self.lastActivityDateInterval]; +} + +- (BOOL)hasUnreadMessages +{ + return (self.lastMessage.dateInterval > self.lastReadDateInterval); +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTFriend.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTFriend.m new file mode 100644 index 0000000..33977fd --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTFriend.m @@ -0,0 +1,41 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFriend.h" + +@interface OCTFriend () + +@end + +@implementation OCTFriend + +#pragma mark - Class methods + ++ (NSArray *)requiredProperties +{ + NSMutableArray *properties = [NSMutableArray arrayWithArray:[super requiredProperties]]; + + [properties addObject:NSStringFromSelector(@selector(nickname))]; + [properties addObject:NSStringFromSelector(@selector(publicKey))]; + + return [properties copy]; +} + +#pragma mark - Public + +- (NSDate *)lastSeenOnline +{ + if (self.lastSeenOnlineInterval <= 0) { + return nil; + } + + return [NSDate dateWithTimeIntervalSince1970:self.lastSeenOnlineInterval]; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"OCTFriend with friendNumber %u, name %@", self.friendNumber, self.name]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTFriendRequest.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTFriendRequest.m new file mode 100644 index 0000000..441923c --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTFriendRequest.m @@ -0,0 +1,33 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFriendRequest.h" + +@implementation OCTFriendRequest + +#pragma mark - Class methods + ++ (NSArray *)requiredProperties +{ + NSMutableArray *properties = [NSMutableArray arrayWithArray:[super requiredProperties]]; + + [properties addObject:NSStringFromSelector(@selector(publicKey))]; + + return [properties copy]; +} + +#pragma mark - Public + +- (NSDate *)date +{ + return [NSDate dateWithTimeIntervalSince1970:self.dateInterval]; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"OCTFriendRequest with publicKey %@...\nmessage length %lu", + [self.publicKey substringToIndex:5], (unsigned long)self.message.length]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTMessageAbstract.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTMessageAbstract.m new file mode 100644 index 0000000..37b4f20 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTMessageAbstract.m @@ -0,0 +1,49 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTMessageAbstract.h" +#import "OCTMessageText.h" +#import "OCTMessageFile.h" +#import "OCTMessageCall.h" + +@interface OCTMessageAbstract () + +@end + +@implementation OCTMessageAbstract + +#pragma mark - Public + +- (NSDate *)date +{ + if (self.dateInterval <= 0) { + return nil; + } + + return [NSDate dateWithTimeIntervalSince1970:self.dateInterval]; +} + +- (BOOL)isOutgoing +{ + return (self.senderUniqueIdentifier == nil); +} + +- (NSString *)description +{ + NSString *string = nil; + + if (self.messageText) { + string = [self.messageText description]; + } + else if (self.messageFile) { + string = [self.messageFile description]; + } + else if (self.messageCall) { + string = [self.messageCall description]; + } + + return [NSString stringWithFormat:@"OCTMessageAbstract with date %@, %@", self.date, string]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTMessageCall.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTMessageCall.m new file mode 100644 index 0000000..fe290f4 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTMessageCall.m @@ -0,0 +1,34 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTMessageCall.h" + +@implementation OCTMessageCall + +#pragma mark - Public + +- (NSString *)description +{ + NSString *description = [super description]; + + return [description stringByAppendingString:[self typeDescription]]; +} + +#pragma mark - Private + +- (NSString *)typeDescription +{ + NSString *description; + switch (self.callEvent) { + case OCTMessageCallEventAnswered: + description = [[NSString alloc] initWithFormat:@"Call lasted %f seconds", self.callDuration]; + break; + case OCTMessageCallEventUnanswered: + description = @"Call unanswered"; + break; + } + return description; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTMessageFile.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTMessageFile.m new file mode 100644 index 0000000..b5a4629 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTMessageFile.m @@ -0,0 +1,36 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTMessageFile.h" + +@interface OCTMessageFile () + +@end + +@implementation OCTMessageFile + +#pragma mark - Public + +- (nullable NSString *)filePath +{ + return [self.internalFilePath stringByExpandingTildeInPath]; +} + +- (void)internalSetFilePath:(NSString *)path +{ + self.internalFilePath = [path stringByAbbreviatingWithTildeInPath]; +} + +- (NSString *)description +{ + NSString *description = [super description]; + + const NSUInteger maxSymbols = 3; + NSString *fileName = self.fileName.length > maxSymbols ? ([self.fileName substringToIndex:maxSymbols]) : @""; + + return [description stringByAppendingFormat:@"OCTMessageFile with fileName = %@..., fileSize = %llu", + fileName, self.fileSize]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTMessageText.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTMessageText.m new file mode 100644 index 0000000..6d4aa00 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTMessageText.m @@ -0,0 +1,31 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTMessageText.h" + +@interface OCTMessageText () + +@end + +@implementation OCTMessageText + +#pragma mark - Class methods + ++ (NSArray *)indexedProperties { + return @[@"msgv3HashHex", @"isDelivered", @"sentPush"]; +} + +#pragma mark - Public + +- (NSString *)description +{ + NSString *description = [super description]; + + const NSUInteger maxSymbols = 3; + NSString *text = self.text.length > maxSymbols ? ([self.text substringToIndex:maxSymbols]) : @""; + + return [description stringByAppendingFormat:@"OCTMessageText %@..., length %lu", text, (unsigned long)self.text.length]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTNode.h b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTNode.h new file mode 100644 index 0000000..49762cd --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTNode.h @@ -0,0 +1,30 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTToxConstants.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCTNode : NSObject + +@property (copy, nonatomic, readonly, nullable) NSString *ipv4Host; +@property (copy, nonatomic, readonly, nullable) NSString *ipv6Host; +@property (assign, nonatomic, readonly) OCTToxPort udpPort; +@property (copy, nonatomic, readonly) NSArray *tcpPorts; +@property (copy, nonatomic, readonly) NSString *publicKey; + +- (instancetype)initWithIpv4Host:(nullable NSString *)ipv4Host + ipv6Host:(nullable NSString *)ipv6Host + udpPort:(OCTToxPort)udpPort + tcpPorts:(NSArray *)tcpPorts + publicKey:(NSString *)publicKey; + +- (BOOL)isEqual:(id)object; +- (NSUInteger)hash; + +@end + +NS_ASSUME_NONNULL_END diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTNode.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTNode.m new file mode 100644 index 0000000..6928143 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTNode.m @@ -0,0 +1,71 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTNode.h" + +@implementation OCTNode + +#pragma mark - Lifecycle + +- (instancetype)initWithIpv4Host:(nullable NSString *)ipv4Host + ipv6Host:(nullable NSString *)ipv6Host + udpPort:(OCTToxPort)udpPort + tcpPorts:(NSArray *)tcpPorts + publicKey:(NSString *)publicKey +{ + self = [super init]; + + if (! self) { + return nil; + } + + _ipv4Host = [ipv4Host copy]; + _ipv6Host = [ipv6Host copy]; + _udpPort = udpPort; + _tcpPorts = [tcpPorts copy]; + _publicKey = [publicKey copy]; + + return self; +} + +- (BOOL)isEqual:(id)object +{ + if (! [object isKindOfClass:[OCTNode class]]) { + return NO; + } + + OCTNode *another = object; + + return [self compareString:self.ipv4Host with:another.ipv4Host] && + [self compareString:self.ipv6Host with:another.ipv6Host] && + (self.udpPort == another.udpPort) && + [self.tcpPorts isEqual:another.tcpPorts] && + [self.publicKey isEqual:another.publicKey]; +} + +- (NSUInteger)hash +{ + const NSUInteger prime = 31; + NSUInteger result = 1; + + result = prime * result + [self.ipv4Host hash]; + result = prime * result + [self.ipv6Host hash]; + result = prime * result + self.udpPort; + result = prime * result + [self.tcpPorts hash]; + result = prime * result + [self.publicKey hash]; + + return result; +} + +- (BOOL)compareString:(NSString *)first with:(NSString *)second +{ + if (first && second) { + return [first isEqual:second]; + } + + BOOL bothNil = ! first && ! second; + return bothNil; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTObject.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTObject.m new file mode 100644 index 0000000..83b5939 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTObject.m @@ -0,0 +1,55 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTObject.h" + +@implementation OCTObject + +#pragma mark - Class methods + ++ (NSString *)primaryKey +{ + return NSStringFromSelector(@selector(uniqueIdentifier)); +} + ++ (NSDictionary *)defaultPropertyValues +{ + return @{ + NSStringFromSelector(@selector(uniqueIdentifier)) : [[NSUUID UUID] UUIDString], + }; +} + ++ (NSArray *)requiredProperties +{ + return @[NSStringFromSelector(@selector(uniqueIdentifier))]; +} + +#pragma mark - Public + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ with uniqueIdentifier %@", [self class], self.uniqueIdentifier]; +} + +- (BOOL)isEqual:(id)object +{ + if (object == self) { + return YES; + } + + if (! [object isKindOfClass:[self class]]) { + return NO; + } + + OCTObject *o = object; + + return [self.uniqueIdentifier isEqualToString:o.uniqueIdentifier]; +} + +- (NSUInteger)hash +{ + return [self.uniqueIdentifier hash]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTSettingsStorageObject.h b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTSettingsStorageObject.h new file mode 100644 index 0000000..9400894 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTSettingsStorageObject.h @@ -0,0 +1,22 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTObject.h" + +@interface OCTSettingsStorageObject : OCTObject + +@property BOOL bootstrapDidConnect; + +/** + * UIImage with avatar of user. + */ +@property NSData *userAvatarData; + +/** + * Generic data to be used by user of the library. + * It shouldn't be used by objcTox itself. + */ +@property NSData *genericSettingsData; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTSettingsStorageObject.m b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTSettingsStorageObject.m new file mode 100644 index 0000000..b17ad92 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Objects/OCTSettingsStorageObject.m @@ -0,0 +1,17 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSettingsStorageObject.h" + +@implementation OCTSettingsStorageObject + ++ (NSDictionary *)defaultPropertyValues +{ + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[super defaultPropertyValues]]; + + dict[@"bootstrapDidConnect"] = @NO; + return [dict copy]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerBootstrapImpl.h b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerBootstrapImpl.h new file mode 100644 index 0000000..91df2c0 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerBootstrapImpl.h @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerBootstrap.h" +#import "OCTSubmanagerProtocol.h" + +@interface OCTSubmanagerBootstrapImpl : NSObject + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerBootstrapImpl.m b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerBootstrapImpl.m new file mode 100644 index 0000000..957d638 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerBootstrapImpl.m @@ -0,0 +1,255 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerBootstrapImpl.h" +#import "OCTNode.h" +#import "OCTTox.h" +#import "OCTLogging.h" +#import "OCTRealmManager.h" +#import "OCTSettingsStorageObject.h" + +static const NSTimeInterval kDidConnectDelay = 2.0; // in seconds +static const NSTimeInterval kIterationTime = 5.0; // in seconds +static const NSUInteger kNodesPerIteration = 20; + +@interface OCTSubmanagerBootstrapImpl () + +@property (strong, nonatomic) NSMutableSet *addedNodes; + +@property (assign, nonatomic) BOOL isBootstrapping; + +@property (strong, nonatomic) NSObject *bootstrappingLock; + +@property (assign, nonatomic) NSTimeInterval didConnectDelay; +@property (assign, nonatomic) NSTimeInterval iterationTime; + +@end + +@implementation OCTSubmanagerBootstrapImpl +@synthesize dataSource = _dataSource; + +#pragma mark - Lifecycle + +- (instancetype)init +{ + self = [super init]; + + if (! self) { + return nil; + } + + _addedNodes = [NSMutableSet new]; + _bootstrappingLock = [NSObject new]; + + _didConnectDelay = kDidConnectDelay; + _iterationTime = kIterationTime; + + return self; +} + +#pragma mark - Public + +- (void)addNodeWithIpv4Host:(nullable NSString *)ipv4Host + ipv6Host:(nullable NSString *)ipv6Host + udpPort:(OCTToxPort)udpPort + tcpPorts:(NSArray *)tcpPorts + publicKey:(NSString *)publicKey +{ + OCTNode *node = [[OCTNode alloc] initWithIpv4Host:ipv4Host + ipv6Host:ipv6Host + udpPort:udpPort + tcpPorts:tcpPorts + publicKey:publicKey]; + + @synchronized(self.addedNodes) { + [self.addedNodes addObject:node]; + } +} + +- (void)addPredefinedNodes +{ + NSString *file = [[self objcToxBundle] pathForResource:@"nodes" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:file]; + + NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + NSAssert(dictionary, @"Nodes json file is corrupted."); + + for (NSDictionary *node in dictionary[@"nodes"]) { + NSUInteger lastPing = [node[@"last_ping"] unsignedIntegerValue]; + + if (lastPing == 0) { + // Skip nodes that weren't seen online. + continue; + } + + NSString *ipv4 = node[@"ipv4"]; + NSString *ipv6 = node[@"ipv6"]; + OCTToxPort udpPort = [node[@"port"] unsignedShortValue]; + NSArray *tcpPorts = node[@"tcp_ports"]; + NSString *publicKey = node[@"public_key"]; + + // Check if addresses are valid. + if (ipv4.length <= 2) { + ipv4 = nil; + } + if (ipv6.length <= 2) { + ipv6 = nil; + } + + NSAssert(ipv4, @"Nodes json file is corrupted"); + NSAssert(udpPort > 0, @"Nodes json file is corrupted"); + NSAssert(publicKey, @"Nodes json file is corrupted"); + + [self addNodeWithIpv4Host:ipv4 ipv6Host:ipv6 udpPort:udpPort tcpPorts:tcpPorts publicKey:publicKey]; + } +} + +- (void)bootstrap +{ + @synchronized(self.bootstrappingLock) { + if (self.isBootstrapping) { + OCTLogWarn(@"bootstrap method called while already bootstrapping"); + return; + } + self.isBootstrapping = YES; + } + + OCTLogVerbose(@"bootstrapping with %lu nodes", (unsigned long)self.addedNodes.count); + + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + if (realmManager.settingsStorage.bootstrapDidConnect) { + OCTLogVerbose(@"did connect before, waiting %g seconds", self.didConnectDelay); + [self tryToBootstrapAfter:self.didConnectDelay]; + } + else { + [self tryToBootstrap]; + } +} + +#pragma mark - Private + +- (void)tryToBootstrapAfter:(NSTimeInterval)after +{ + __weak OCTSubmanagerBootstrapImpl *weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, after * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + __strong OCTSubmanagerBootstrapImpl *strongSelf = weakSelf; + + if (! strongSelf) { + OCTLogInfo(@"OCTSubmanagerBootstrap is dead, seems that OCTManager was killed, quiting."); + return; + } + + [strongSelf tryToBootstrap]; + }); +} + +- (void)tryToBootstrap +{ + if ([self.dataSource managerIsToxConnected]) { + OCTLogInfo(@"trying to bootstrap... tox is connected, exiting"); + + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + [realmManager updateObject:realmManager.settingsStorage withBlock:^(OCTSettingsStorageObject *object) { + object.bootstrapDidConnect = YES; + }]; + + [self finishBootstrapping]; + + return; + } + + NSArray *selectedNodes = [self selectedNodesForIteration]; + + if (! selectedNodes.count) { + OCTLogInfo(@"trying to bootstrap... no nodes left, exiting"); + [self finishBootstrapping]; + return; + } + + OCTLogInfo(@"trying to bootstrap... picked %lu nodes", (unsigned long)selectedNodes.count); + + for (OCTNode *node in selectedNodes) { + // HINT: do not do this async on a thread. since "node" will loose its value + // dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + [self safeBootstrapFromHost:node.ipv4Host port:node.udpPort publicKey:node.publicKey]; + [self safeBootstrapFromHost:node.ipv6Host port:node.udpPort publicKey:node.publicKey]; + + for (NSNumber *tcpPort in node.tcpPorts) { + [self safeAddTcpRelayWithHost:node.ipv4Host port:tcpPort.intValue publicKey:node.publicKey]; + [self safeAddTcpRelayWithHost:node.ipv6Host port:tcpPort.intValue publicKey:node.publicKey]; + } + // }); + } + + [self tryToBootstrapAfter:self.iterationTime]; +} + +- (void)safeBootstrapFromHost:(NSString *)host port:(OCTToxPort)port publicKey:(NSString *)publicKey +{ + if (! host) { + return; + } + + NSError *error; + + if (! [[self.dataSource managerGetTox] bootstrapFromHost:host port:port publicKey:publicKey error:&error]) { + OCTLogWarn(@"trying to bootstrap... bootstrap failed with address %@, error %@", host, error); + } +} + +- (void)safeAddTcpRelayWithHost:(NSString *)host port:(OCTToxPort)port publicKey:(NSString *)publicKey +{ + if (! host) { + return; + } + + NSError *error; + + if (! [[self.dataSource managerGetTox] addTCPRelayWithHost:host port:port publicKey:publicKey error:&error]) { + OCTLogWarn(@"trying to bootstrap... tcp relay failed with address %@, error %@", host, error); + } +} + +- (void)finishBootstrapping +{ + @synchronized(self.bootstrappingLock) { + self.isBootstrapping = NO; + } +} + +- (NSArray *)selectedNodesForIteration +{ + NSMutableArray *allNodes; + NSMutableArray *selectedNodes = [NSMutableArray new]; + + @synchronized(self.addedNodes) { + allNodes = [[self.addedNodes allObjects] mutableCopy]; + } + + while (allNodes.count && (selectedNodes.count < kNodesPerIteration)) { + NSUInteger index = arc4random_uniform((u_int32_t)allNodes.count); + + [selectedNodes addObject:allNodes[index]]; + [allNodes removeObjectAtIndex:index]; + } + + @synchronized(self.addedNodes) { + [self.addedNodes minusSet:[NSSet setWithArray:selectedNodes]]; + } + + return [selectedNodes copy]; +} + +- (NSBundle *)objcToxBundle +{ + NSBundle *mainBundle = [NSBundle bundleForClass:[self class]]; + NSBundle *objcToxBundle = [NSBundle bundleWithPath:[mainBundle pathForResource:@"objcTox" ofType:@"bundle"]]; + + // objcToxBundle is used when installed with CocoaPods. If we run tests/demo app mainBundle would be used. + return objcToxBundle ?: mainBundle; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerCallsImpl.h b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerCallsImpl.h new file mode 100644 index 0000000..463e616 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerCallsImpl.h @@ -0,0 +1,27 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerCalls.h" +#import "OCTSubmanagerProtocol.h" +#import "OCTToxAV.h" +#import "OCTManagerConstants.h" +#import "OCTAudioEngine.h" +#import "OCTVideoEngine.h" +#import "OCTRealmManager.h" +#import "OCTCall+Utilities.h" +#import "OCTCallTimer.h" + +@class OCTTox; + +@interface OCTSubmanagerCallsImpl : NSObject + +/** + * Initialize the OCTSubmanagerCall + */ +- (instancetype)initWithTox:(OCTTox *)tox; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerCallsImpl.m b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerCallsImpl.m new file mode 100644 index 0000000..8dfb03f --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerCallsImpl.m @@ -0,0 +1,564 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerCallsImpl.h" +#import "OCTLogging.h" +#import "OCTTox.h" + +const OCTToxAVAudioBitRate kDefaultAudioBitRate = OCTToxAVAudioBitRate48; +const OCTToxAVVideoBitRate kDefaultVideoBitRate = 2500; + +@interface OCTSubmanagerCallsImpl () + +@property (strong, nonatomic) OCTToxAV *toxAV; +@property (strong, nonatomic) OCTAudioEngine *audioEngine; +@property (strong, nonatomic) OCTVideoEngine *videoEngine; +@property (strong, nonatomic) OCTCallTimer *timer; +@property (nonatomic, assign) dispatch_once_t setupOnceToken; + +@end + +@implementation OCTSubmanagerCallsImpl : NSObject + @synthesize delegate = _delegate; +@synthesize dataSource = _dataSource; + +#pragma mark - Lifecycle + +- (instancetype)initWithTox:(OCTTox *)tox +{ + self = [super init]; + + if (! self) { + return nil; + } + + _toxAV = [[OCTToxAV alloc] initWithTox:tox error:nil]; + _toxAV.delegate = self; + [_toxAV start]; + + return self; +} + +- (void)dealloc +{ + [self endAllCalls]; +} + +#pragma mark - Public + +- (BOOL)setupAndReturnError:(NSError **)error +{ + NSAssert(self.dataSource, @"dataSource is needed before setup of OCTSubmanagerCalls"); + __block BOOL status = NO; + dispatch_once(&_setupOnceToken, ^{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + self.timer = [[OCTCallTimer alloc] initWithRealmManager:realmManager]; + + self.audioEngine = [OCTAudioEngine new]; + self.audioEngine.toxav = self.toxAV; + self.videoEngine = [OCTVideoEngine new]; + self.videoEngine.toxav = self.toxAV; + + status = [self.videoEngine setupAndReturnError:error]; + }); + + return status; +} + +- (OCTCall *)callToChat:(OCTChat *)chat enableAudio:(BOOL)enableAudio enableVideo:(BOOL)enableVideo error:(NSError **)error +{ + OCTToxAVAudioBitRate audioBitRate = (enableAudio) ? kDefaultAudioBitRate : OCTToxAVAudioBitRateDisabled; + OCTToxAVVideoBitRate videoBitRate = (enableVideo) ? kDefaultVideoBitRate : kOCTToxAVVideoBitRateDisable; + + + if (chat.friends.count == 1) { + OCTFriend *friend = chat.friends.lastObject; + self.audioEngine.friendNumber = friend.friendNumber; + + if (! [self.toxAV callFriendNumber:friend.friendNumber + audioBitRate:audioBitRate + videoBitRate:videoBitRate + error:error]) { + return nil; + } + + [self checkForCurrentActiveCallAndPause]; + + OCTCall *call = [self createCallWithFriend:friend status:OCTCallStatusDialing]; + + OCTRealmManager *manager = [self.dataSource managerGetRealmManager]; + [manager updateObject:call withBlock:^(OCTCall *callToUpdate) { + callToUpdate.status = OCTCallStatusDialing; + callToUpdate.videoIsEnabled = enableVideo; + }]; + + self.enableMicrophone = YES; + + return call; + } + else { + // TO DO: Group Calls + return nil; + } + return nil; +} + +- (BOOL)enableVideoSending:(BOOL)enable forCall:(OCTCall *)call error:(NSError **)error +{ + OCTToxAVVideoBitRate bitrate = (enable) ? kDefaultVideoBitRate : kOCTToxAVVideoBitRateDisable; + if (! [self setVideoBitrate:bitrate forCall:call error:error]) { + return NO; + } + + if (enable && (! [call isPaused])) { + OCTFriend *friend = [call.chat.friends firstObject]; + self.videoEngine.friendNumber = friend.friendNumber; + [self.videoEngine startSendingVideo]; + } + else { + [self.videoEngine stopSendingVideo]; + } + + OCTRealmManager *manager = [self.dataSource managerGetRealmManager]; + [manager updateObject:call withBlock:^(OCTCall *callToUpdate) { + callToUpdate.videoIsEnabled = enable; + }]; + + return YES; +} + +- (BOOL)answerCall:(OCTCall *)call enableAudio:(BOOL)enableAudio enableVideo:(BOOL)enableVideo error:(NSError **)error +{ + OCTToxAVAudioBitRate audioBitRate = (enableAudio) ? kDefaultAudioBitRate : OCTToxAVAudioBitRateDisabled; + OCTToxAVVideoBitRate videoBitRate = (enableVideo) ? kDefaultVideoBitRate : kOCTToxAVVideoBitRateDisable; + + if (call.chat.friends.count == 1) { + + OCTFriend *friend = call.chat.friends.firstObject; + + if (! [self.toxAV answerIncomingCallFromFriend:friend.friendNumber + audioBitRate:audioBitRate + videoBitRate:videoBitRate + error:error]) { + return NO; + } + + [self checkForCurrentActiveCallAndPause]; + + OCTRealmManager *manager = [self.dataSource managerGetRealmManager]; + [manager updateObject:call withBlock:^(OCTCall *callToUpdate) { + call.status = OCTCallStatusActive; + callToUpdate.videoIsEnabled = enableVideo; + }]; + + self.enableMicrophone = YES; + [self startEnginesAndTimer:YES forCall:call]; + + return YES; + } + else { + // TO DO: Group Calls + return NO; + } +} + +- (BOOL)enableMicrophone +{ + return self.audioEngine.enableMicrophone; +} + +- (void)setEnableMicrophone:(BOOL)enableMicrophone +{ + self.audioEngine.enableMicrophone = enableMicrophone; +} + +- (BOOL)sendCallControl:(OCTToxAVCallControl)control toCall:(OCTCall *)call error:(NSError **)error +{ + if (call.chat.friends.count == 1) { + + OCTFriend *friend = call.chat.friends.firstObject; + + if (! [self.toxAV sendCallControl:control toFriendNumber:friend.friendNumber error:error]) { + return NO; + } + + switch (control) { + case OCTToxAVCallControlResume: + [self checkForCurrentActiveCallAndPause]; + [self putOnPause:NO call:call]; + break; + case OCTToxAVCallControlCancel: + [self addMessageAndDeleteCall:call]; + + if ((self.audioEngine.friendNumber == friend.friendNumber) && + ([self.audioEngine isAudioRunning:nil])) { + [self startEnginesAndTimer:NO forCall:call]; + } + + break; + case OCTToxAVCallControlPause: + [self putOnPause:YES call:call]; + break; + case OCTToxAVCallControlUnmuteAudio: + break; + case OCTToxAVCallControlMuteAudio: + break; + case OCTToxAVCallControlHideVideo: + break; + case OCTToxAVCallControlShowVideo: + break; + } + return YES; + } + else { + return NO; + } +} + +- (OCTView *)videoFeed +{ + return [self.videoEngine videoFeed]; +} + +- (void)getVideoCallPreview:(void (^)(CALayer *))completionBlock +{ + [self.videoEngine getVideoCallPreview:completionBlock]; +} + +- (BOOL)setAudioBitrate:(int)bitrate forCall:(OCTCall *)call error:(NSError **)error +{ + if (call.chat.friends.count == 1) { + + OCTFriend *friend = call.chat.friends.firstObject; + + return [self.toxAV setAudioBitRate:bitrate force:NO forFriend:friend.friendNumber error:error]; + } + else { + // TO DO: Group Calls + return NO; + } +} + +#pragma mark - Setting IO devices + +#if ! TARGET_OS_IPHONE + +- (BOOL)setAudioInputDevice:(NSString *)deviceUniqueID error:(NSError **)error +{ + return [self.audioEngine setInputDeviceID:deviceUniqueID error:error]; +} + +- (BOOL)setAudioOutputDevice:(NSString *)deviceUniqueID error:(NSError **)error +{ + return [self.audioEngine setOutputDeviceID:deviceUniqueID error:error]; +} + +- (BOOL)setVideoInputDevice:(NSString *)deviceUniqueID error:(NSError **)error +{ + return [self.videoEngine switchToCamera:deviceUniqueID error:error]; +} + +#else + +- (BOOL)routeAudioToSpeaker:(BOOL)speaker error:(NSError **)error +{ + return [self.audioEngine routeAudioToSpeaker:speaker error:error]; +} + +- (BOOL)switchToCameraFront:(BOOL)front error:(NSError **)error +{ + return [self.videoEngine useFrontCamera:front error:error]; +} + +#endif + +#pragma mark Private methods + +- (OCTCall *)createCallWithFriend:(OCTFriend *)friend status:(OCTCallStatus)status +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend]; + + return [realmManager createCallWithChat:chat status:status]; +} + +- (OCTCall *)getCurrentCallForFriendNumber:(OCTToxFriendNumber)friendNumber +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend]; + + return [realmManager getCurrentCallForChat:chat]; +} + +- (void)updateCall:(OCTCall *)call withStatus:(OCTCallStatus)status +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + [realmManager updateObject:call withBlock:^(OCTCall *callToUpdate) { + callToUpdate.status = status; + }]; +} + +- (void)endAllCalls +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + [self startEnginesAndTimer:NO forCall:nil]; + + RLMResults *calls = [realmManager objectsWithClass:[OCTCall class] predicate:nil]; + + for (OCTCall *call in calls) { + OCTFriend *friend = call.chat.friends.firstObject; + [self.toxAV sendCallControl:OCTToxAVCallControlCancel toFriendNumber:friend.friendNumber error:nil]; + } + + [realmManager convertAllCallsToMessages]; +} + +- (void)putOnPause:(BOOL)pause call:(OCTCall *)call +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + BOOL wasPaused = call.pausedStatus != OCTCallPausedStatusNone; + + if (pause) { + if (! wasPaused) { + [self startEnginesAndTimer:NO forCall:call]; + } + } + else { + OCTFriend *friend = [call.chat.friends firstObject]; + self.audioEngine.friendNumber = friend.friendNumber; + + if (call.pausedStatus == OCTCallPausedStatusByUser) { + [self startEnginesAndTimer:YES forCall:call]; + } + } + + [realmManager updateObject:call withBlock:^(OCTCall *callToUpdate) { + if (pause) { + callToUpdate.pausedStatus |= OCTCallPausedStatusByUser; + callToUpdate.onHoldStartInterval = callToUpdate.onHoldStartInterval ?: [[NSDate date] timeIntervalSince1970]; + } + else { + callToUpdate.pausedStatus &= ~OCTCallPausedStatusByUser; + callToUpdate.onHoldStartInterval = 0; + } + }]; +} + +- (void)addMessageAndDeleteCall:(OCTCall *)call +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + [realmManager addMessageCall:call]; + + if (! [call isPaused]) { + [self.timer stopTimer]; + } + + // TODO: this one crashes when video was active, and is called directly + // there is a race condition somewhere -> fix me! + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [realmManager deleteObject:call]; + }); +} + +- (void)updateCall:(OCTCall *)call withState:(OCTToxAVCallState)state pausedStatus:(OCTCallPausedStatus)pausedStatus +{ + BOOL sendingAudio = NO, sendingVideo = NO, acceptingAudio = NO, acceptingVideo = NO; + + if (state & OCTToxAVFriendCallStateAcceptingAudio) { + acceptingAudio = YES; + } + + if (state & OCTToxAVFriendCallStateAcceptingVideo) { + acceptingVideo = YES; + } + + if (state & OCTToxAVFriendCallStateSendingAudio) { + sendingAudio = YES; + } + + if (state & OCTToxAVFriendCallStateSendingVideo) { + sendingVideo = YES; + } + + BOOL wasPaused = call.pausedStatus != OCTCallPausedStatusNone; + + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + [realmManager updateObject:call withBlock:^(OCTCall *callToUpdate) { + callToUpdate.friendAcceptingAudio = acceptingAudio; + callToUpdate.friendAcceptingVideo = acceptingVideo; + callToUpdate.friendSendingAudio = sendingAudio; + callToUpdate.friendSendingVideo = sendingVideo; + callToUpdate.pausedStatus = pausedStatus; + + if (! wasPaused && (state == OCTToxAVFriendCallStatePaused)) { + callToUpdate.onHoldStartInterval = [[NSDate date] timeIntervalSince1970]; + } + }]; +} + +- (void)checkForCurrentActiveCallAndPause +{ + if ([self.audioEngine isAudioRunning:nil] || [self.videoEngine isSendingVideo]) { + OCTCall *call = [self getCurrentCallForFriendNumber:self.audioEngine.friendNumber]; + [self sendCallControl:OCTToxAVCallControlPause toCall:call error:nil]; + } +} + +- (BOOL)setVideoBitrate:(int)bitrate forCall:(OCTCall *)call error:(NSError **)error +{ + if (call.chat.friends.count == 1) { + + OCTFriend *friend = call.chat.friends.firstObject; + + return [self.toxAV setVideoBitRate:bitrate force:NO forFriend:friend.friendNumber error:error]; + } + else { + // TO DO: Group Calls + return NO; + } +} + +- (void)startEnginesAndTimer:(BOOL)start forCall:(OCTCall *)call +{ + if (start) { + OCTFriend *friend = [call.chat.friends firstObject]; + + NSError *error; + if (! [self.audioEngine startAudioFlow:&error]) { + OCTLogVerbose(@"Error starting audio flow %@", error); + } + + + + if (call.videoIsEnabled) { + [self.videoEngine startSendingVideo]; + } + + self.audioEngine.friendNumber = friend.friendNumber; + self.videoEngine.friendNumber = friend.friendNumber; + + [self.timer startTimerForCall:call]; + } + else { + [self.audioEngine stopAudioFlow:nil]; + [self.videoEngine stopSendingVideo]; + [self.timer stopTimer]; + } +} + +#pragma mark OCTToxAV delegate methods + +- (void)toxAV:(OCTToxAV *)toxAV receiveCallAudioEnabled:(BOOL)audio videoEnabled:(BOOL)video friendNumber:(OCTToxFriendNumber)friendNumber +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + OCTCall *call = [self createCallWithFriend:friend status:OCTCallStatusRinging]; + + [realmManager updateObject:call withBlock:^(OCTCall *callToUpdate) { + callToUpdate.status = OCTCallStatusRinging; + callToUpdate.caller = friend; + callToUpdate.friendSendingAudio = audio; + callToUpdate.friendAcceptingAudio = audio; + callToUpdate.friendSendingVideo = video; + callToUpdate.friendAcceptingVideo = video; + }]; + + if ([self.delegate respondsToSelector:@selector(callSubmanager:receiveCall:audioEnabled:videoEnabled:)]) { + [self.delegate callSubmanager:self receiveCall:call audioEnabled:audio videoEnabled:video]; + } +} + +- (void)toxAV:(OCTToxAV *)toxAV callStateChanged:(OCTToxAVCallState)state friendNumber:(OCTToxFriendNumber)friendNumber +{ + OCTCall *call = [self getCurrentCallForFriendNumber:friendNumber]; + + if ((state & OCTToxAVFriendCallStateFinished) || (state & OCTToxAVFriendCallStateError)) { + + [self addMessageAndDeleteCall:call]; + + if ((self.audioEngine.friendNumber == friendNumber) && [self.audioEngine isAudioRunning:nil]) { + [self.audioEngine stopAudioFlow:nil]; + } + + if ((self.videoEngine.friendNumber == friendNumber) && [self.videoEngine isSendingVideo]) { + [self.videoEngine stopSendingVideo]; + } + + return; + } + + if (call.status == OCTCallStatusDialing) { + [self updateCall:call withStatus:OCTCallStatusActive]; + [self startEnginesAndTimer:YES forCall:call]; + } + + OCTCallPausedStatus pauseStatus = call.pausedStatus; + + if ((pauseStatus == OCTCallPausedStatusNone) && (state == OCTToxAVFriendCallStatePaused)) { + [self startEnginesAndTimer:NO forCall:call]; + } + + if ((pauseStatus == OCTCallPausedStatusByFriend) && (state != OCTToxAVFriendCallStatePaused)) { + [self startEnginesAndTimer:YES forCall:call]; + } + + if (state == OCTToxAVFriendCallStatePaused) { + pauseStatus |= OCTCallPausedStatusByFriend; + } + else { + pauseStatus &= ~OCTCallPausedStatusByFriend; + } + + [self updateCall:call withState:state pausedStatus:pauseStatus]; +} + +- (void) toxAV:(OCTToxAV *)toxAV + receiveAudio:(OCTToxAVPCMData *)pcm + sampleCount:(OCTToxAVSampleCount)sampleCount + channels:(OCTToxAVChannels)channels + sampleRate:(OCTToxAVSampleRate)sampleRate + friendNumber:(OCTToxFriendNumber)friendNumber +{ + // TOXAUDIO: -incoming-audio- + [self.audioEngine provideAudioFrames:pcm sampleCount:sampleCount channels:channels sampleRate:sampleRate fromFriend:friendNumber]; +} + +- (void)toxAV:(OCTToxAV *)toxAV audioBitRateStatus:(OCTToxAVAudioBitRate)audioBitRate forFriendNumber:(OCTToxFriendNumber)friendNumber +{ + // TODO https://github.com/Antidote-for-Tox/objcTox/issues/88 +} + +- (void)toxAV:(OCTToxAV *)toxAV videoBitRateStatus:(OCTToxAVVideoBitRate)audioBitRate forFriendNumber:(OCTToxFriendNumber)friendNumber +{ + // TODO https://github.com/Antidote-for-Tox/objcTox/issues/88 +} + +- (void) toxAV:(OCTToxAV *)toxAV + receiveVideoFrameWithWidth:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height + yPlane:(OCTToxAVPlaneData *)yPlane uPlane:(OCTToxAVPlaneData *)uPlane + vPlane:(OCTToxAVPlaneData *)vPlane + yStride:(OCTToxAVStrideData)yStride uStride:(OCTToxAVStrideData)uStride + vStride:(OCTToxAVStrideData)vStride + friendNumber:(OCTToxFriendNumber)friendNumber +{ + [self.videoEngine receiveVideoFrameWithWidth:width + height:height + yPlane:yPlane + uPlane:uPlane + vPlane:vPlane + yStride:yStride + uStride:uStride + vStride:vStride + friendNumber:friendNumber]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerChatsImpl.h b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerChatsImpl.h new file mode 100644 index 0000000..47e367a --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerChatsImpl.h @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerChats.h" +#import "OCTSubmanagerProtocol.h" + +@interface OCTSubmanagerChatsImpl : NSObject + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerChatsImpl.m b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerChatsImpl.m new file mode 100644 index 0000000..ab30388 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerChatsImpl.m @@ -0,0 +1,529 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerChatsImpl.h" +#import "OCTTox.h" +#import "OCTRealmManager.h" +#import "OCTMessageAbstract.h" +#import "OCTMessageText.h" +#import "OCTChat.h" +#import "OCTLogging.h" +#import "OCTSendMessageOperation.h" +#import "OCTTox+Private.h" +#import "OCTToxOptions+Private.h" +#import "Firebase.h" + +@interface OCTSubmanagerChatsImpl () + +@property (strong, nonatomic, readonly) NSOperationQueue *sendMessageQueue; + +@end + +@implementation OCTSubmanagerChatsImpl +@synthesize dataSource = _dataSource; + +- (instancetype)init +{ + self = [super init]; + + if (! self) { + return nil; + } + + _sendMessageQueue = [NSOperationQueue new]; + _sendMessageQueue.maxConcurrentOperationCount = 1; + + return self; +} + +- (void)dealloc +{ + [self.dataSource.managerGetNotificationCenter removeObserver:self]; +} + +- (void)configure +{ + [self.dataSource.managerGetNotificationCenter addObserver:self + selector:@selector(friendConnectionStatusChangeNotification:) + name:kOCTFriendConnectionStatusChangeNotification + object:nil]; +} + +#pragma mark - Public + +- (OCTChat *)getOrCreateChatWithFriend:(OCTFriend *)friend +{ + return [[self.dataSource managerGetRealmManager] getOrCreateChatWithFriend:friend]; +} + +- (void)removeMessages:(NSArray *)messages +{ + [[self.dataSource managerGetRealmManager] removeMessages:messages]; + [self.dataSource.managerGetNotificationCenter postNotificationName:kOCTScheduleFileTransferCleanupNotification object:nil]; +} + +- (void)removeAllMessagesInChat:(OCTChat *)chat removeChat:(BOOL)removeChat +{ + [[self.dataSource managerGetRealmManager] removeAllMessagesInChat:chat removeChat:removeChat]; + [self.dataSource.managerGetNotificationCenter postNotificationName:kOCTScheduleFileTransferCleanupNotification object:nil]; +} + +- (void)sendOwnPush +{ + NSLog(@"PUSH:sendOwnPush"); + NSString *token = [FIRMessaging messaging].FCMToken; + if (token.length > 0) + { + NSString *my_pushToken = [NSString stringWithFormat:@"https://tox.zoff.xyz/toxfcm/fcm.php?id=%@&type=1", token]; + // NSLog(@"token push url=%@", my_pushToken); + triggerPush(my_pushToken, nil, nil, nil); + } + else + { + NSLog(@"PUSH:sendOwnPush:no token"); + } +} + +- (void)sendMessagePushToChat:(OCTChat *)chat +{ + NSParameterAssert(chat); + NSLog(@"PUSH:sendMessagePushToChat"); + __weak OCTSubmanagerChatsImpl *weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + __strong OCTSubmanagerChatsImpl *strongSelf = weakSelf; + OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager]; + + OCTFriend *friend = [chat.friends firstObject]; + __block NSString *friend_pushToken = friend.pushToken; + + if (friend_pushToken == nil) + { + NSLog(@"sendMessagePushToChat:Friend has No Pushtoken"); + } + else + { + // HINT: only select outgoing messages (senderUniqueIdentifier == NULL) + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.isDelivered == 0 AND messageText.sentPush == 0 AND senderUniqueIdentifier == nil", chat.uniqueIdentifier]; + + RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate]; + OCTMessageAbstract *message_found = [results firstObject]; + if (message_found) { + + [realmManager updateObject:message_found withBlock:^(OCTMessageAbstract *theMessage) { + theMessage.messageText.sentPush = YES; + }]; + triggerPush(friend_pushToken, message_found.messageText.msgv3HashHex, strongSelf, chat); + } + } + }); +} + +triggerPush(NSString *used_pushToken, + NSString *msgv3HashHex, + OCTSubmanagerChatsImpl *strongSelf, + OCTChat *chat) +{ + // HINT: call push token (URL) here + // best in a background thread + // + NSLog(@"PUSH:triggerPush"); + if ((used_pushToken != nil) && (used_pushToken.length > 5)) { + + // check push url starts with allowed values + if ( + ([used_pushToken hasPrefix:@"https://tox.zoff.xyz/toxfcm/fcm.php?id="]) + || + ([used_pushToken hasPrefix:@"https://gotify1.unifiedpush.org/UP?token="]) + || + ([used_pushToken hasPrefix:@"https://ntfy.sh/"]) + ) { + + NSString *strong_pushToken = used_pushToken; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // NSString *strong_pushToken = weak_pushToken; + int PUSH_URL_TRIGGER_AGAIN_MAX_COUNT = 8; + int PUSH_URL_TRIGGER_AGAIN_SECONDS = 21; + + for (int i=0; i<(PUSH_URL_TRIGGER_AGAIN_MAX_COUNT + 1); i++) + { + if (chat == nil) { + __block UIApplicationState as = UIApplicationStateBackground; + dispatch_sync(dispatch_get_main_queue(), ^{ + as =[[UIApplication sharedApplication] applicationState]; + }); + + if (as == UIApplicationStateActive) { + NSLog(@"PUSH:fg->break:1"); + break; + } + } + NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:strong_pushToken]]; + NSString *userUpdate = [NSString stringWithFormat:@"&text=1", nil]; + [urlRequest setHTTPMethod:@"POST"]; + + NSData *data1 = [userUpdate dataUsingEncoding:NSUTF8StringEncoding]; + + [urlRequest setHTTPBody:data1]; + [urlRequest setCachePolicy:NSURLRequestReloadIgnoringCacheData]; + [urlRequest setTimeoutInterval:10]; // HINT: 10 seconds + NSString *userAgent = @"Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0"; + [urlRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + [urlRequest setValue:@"no-cache, no-store, must-revalidate" forHTTPHeaderField:@"Cache-Control"]; + [urlRequest setValue:@"no-cache" forHTTPHeaderField:@"Pragma"]; + [urlRequest setValue:@"0" forHTTPHeaderField:@"Expires"]; + + // NSLog(@"PUSH:for msgv3HashHex=%@", msgv3HashHex); + // NSLog(@"PUSH:for friend.pushToken=%@", strong_pushToken); + + NSURLSession *session = [NSURLSession sharedSession]; + NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if ((httpResponse.statusCode < 300) && (httpResponse.statusCode > 199)) { + NSLog(@"calling PUSH URL:CALL:SUCCESS"); + } + else { + NSLog(@"calling PUSH URL:-ERROR:01-"); + } + }]; + NSLog(@"calling PUSH URL:CALL:start"); + [dataTask resume]; + if (i < PUSH_URL_TRIGGER_AGAIN_MAX_COUNT) + { + NSLog(@"calling PUSH URL:WAIT:start"); + [NSThread sleepForTimeInterval:PUSH_URL_TRIGGER_AGAIN_SECONDS]; + NSLog(@"calling PUSH URL:WAIT:done"); + } + + if (chat == nil) { + __block UIApplicationState as = UIApplicationStateBackground; + dispatch_sync(dispatch_get_main_queue(), ^{ + as =[[UIApplication sharedApplication] applicationState]; + }); + + if (as == UIApplicationStateActive) { + NSLog(@"PUSH:fg->break:2"); + break; + } + } + + if (msgv3HashHex != nil) + { + OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager]; + __block BOOL msgIsDelivered = NO; + + NSLog(@"calling PUSH URL:DB check:start"); + dispatch_sync(dispatch_get_main_queue(), ^{ + // HINT: only select outgoing messages (senderUniqueIdentifier == NULL) + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.msgv3HashHex == %@ AND senderUniqueIdentifier == nil", + chat.uniqueIdentifier, msgv3HashHex]; + RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate]; + OCTMessageAbstract *message_found = [results firstObject]; + if (message_found) { + if (message_found.messageText) { + msgIsDelivered = message_found.messageText.isDelivered; + } + } + NSLog(@"calling PUSH URL:DB check:end_real"); + }); + NSLog(@"calling PUSH URL:DB check:end"); + + if (msgIsDelivered == YES) { + // OCTLogInfo(@"PUSH:for msgv3HashHex isDelivered=YES"); + NSLog(@"PUSH:for msgv3HashHex isDelivered=YES"); + break; + } + } + } + }); + } + else { + NSLog(@"unsafe PUSH URL not allowed:-ERROR:02-"); + } + } +} + +- (void)sendMessageToChat:(OCTChat *)chat + text:(NSString *)text + type:(OCTToxMessageType)type + successBlock:(void (^)(OCTMessageAbstract *message))userSuccessBlock + failureBlock:(void (^)(NSError *error))userFailureBlock +{ + NSParameterAssert(chat); + NSParameterAssert(text); + + OCTFriend *friend = [chat.friends firstObject]; + + uint8_t *message_v3_hash_bin = calloc(1, TOX_MSGV3_MSGID_LENGTH); + uint8_t *message_v3_hash_hexstr = calloc(1, (TOX_MSGV3_MSGID_LENGTH * 2) + 1); + + NSString *msgv3HashHex = nil; + UInt32 msgv3tssec = 0; + + if ((message_v3_hash_bin) && (message_v3_hash_hexstr)) + { + tox_messagev3_get_new_message_id(message_v3_hash_bin); + bin_to_hex((const char *)message_v3_hash_bin, (size_t)TOX_MSGV3_MSGID_LENGTH, message_v3_hash_hexstr); + + msgv3HashHex = [[NSString alloc] initWithBytes:message_v3_hash_hexstr length:(TOX_MSGV3_MSGID_LENGTH * 2) encoding:NSUTF8StringEncoding]; + + // HINT: set sent timestamp to now() as unixtimestamp value + msgv3tssec = [[NSNumber numberWithDouble: [[NSDate date] timeIntervalSince1970]] integerValue]; + + free(message_v3_hash_bin); + free(message_v3_hash_hexstr); + } + + __weak OCTSubmanagerChatsImpl *weakSelf = self; + OCTSendMessageOperationSuccessBlock successBlock = ^(OCTToxMessageId messageId) { + __strong OCTSubmanagerChatsImpl *strongSelf = weakSelf; + + BOOL sent_push = NO; + + if (messageId == -1) { + if ((friend.pushToken != nil) && (friend.pushToken.length > 5)) { + + // check push url starts with allowed values + if ( + ([friend.pushToken hasPrefix:@"https://tox.zoff.xyz/toxfcm/fcm.php?id="]) + || + ([friend.pushToken hasPrefix:@"https://gotify1.unifiedpush.org/UP?token="]) + || + ([friend.pushToken hasPrefix:@"https://ntfy.sh/"]) + ) { + sent_push = YES; + } + } + } + + OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager]; + OCTMessageAbstract *message = [realmManager addMessageWithText:text type:type chat:chat sender:nil messageId:messageId msgv3HashHex:msgv3HashHex sentPush:sent_push tssent:msgv3tssec tsrcvd:0]; + + if (userSuccessBlock) { + userSuccessBlock(message); + } + }; + + OCTSendMessageOperationFailureBlock failureBlock = ^(NSError *error) { + __strong OCTSubmanagerChatsImpl *strongSelf = weakSelf; + + if ((error.code == OCTToxErrorFriendSendMessageFriendNotConnected) && + [strongSelf.dataSource managerUseFauxOfflineMessaging]) { + NSString *friend_pushToken = friend.pushToken; + triggerPush(friend_pushToken, msgv3HashHex, strongSelf, chat); + successBlock(-1); + return; + } + + if (userFailureBlock) { + userFailureBlock(error); + } + }; + + OCTSendMessageOperation *operation = [[OCTSendMessageOperation alloc] initWithTox:[self.dataSource managerGetTox] + friendNumber:friend.friendNumber + messageType:type + message:text + msgv3HashHex:msgv3HashHex + msgv3tssec:msgv3tssec + successBlock:successBlock + failureBlock:failureBlock]; + [self.sendMessageQueue addOperation:operation]; +} + +- (BOOL)setIsTyping:(BOOL)isTyping inChat:(OCTChat *)chat error:(NSError **)error +{ + NSParameterAssert(chat); + + OCTFriend *friend = [chat.friends firstObject]; + OCTTox *tox = [self.dataSource managerGetTox]; + + return [tox setUserIsTyping:isTyping forFriendNumber:friend.friendNumber error:error]; +} + +#pragma mark - NSNotification + +- (void)friendConnectionStatusChangeNotification:(NSNotification *)notification +{ + OCTFriend *friend = notification.object; + + if (! friend) { + OCTLogWarn(@"no friend received in notification %@, exiting", notification); + return; + } + + if (friend.isConnected) { + [self resendUndeliveredMessagesToFriend:friend]; + } +} + +#pragma mark - Private + +- (void)resendUndeliveredMessagesToFriend:(OCTFriend *)friend +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend]; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@" + @" AND senderUniqueIdentifier == nil" + @" AND messageText.isDelivered == NO", + chat.uniqueIdentifier]; + + RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate]; + + for (OCTMessageAbstract *message in results) { + OCTLogInfo(@"Resending message to friend %@", friend); + + __weak OCTSubmanagerChatsImpl *weakSelf = self; + OCTSendMessageOperationSuccessBlock successBlock = ^(OCTToxMessageId messageId) { + __strong OCTSubmanagerChatsImpl *strongSelf = weakSelf; + + OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager]; + + [realmManager updateObject:message withBlock:^(OCTMessageAbstract *theMessage) { + theMessage.messageText.messageId = messageId; + }]; + }; + + OCTSendMessageOperationFailureBlock failureBlock = ^(NSError *error) { + OCTLogWarn(@"Cannot resend message to friend %@, error %@", friend, error); + }; + + OCTSendMessageOperation *operation = [[OCTSendMessageOperation alloc] initWithTox:[self.dataSource managerGetTox] + friendNumber:friend.friendNumber + messageType:message.messageText.type + message:message.messageText.text + msgv3HashHex:message.messageText.msgv3HashHex + msgv3tssec:message.tssent + successBlock:successBlock + failureBlock:failureBlock]; + [self.sendMessageQueue addOperation:operation]; + } +} + +#pragma mark - OCTToxDelegate + +/* + * send mesgV3 high level ACK message. + */ +- (void)tox:(OCTTox *)tox sendFriendHighlevelACK:(NSString *)message + friendNumber:(OCTToxFriendNumber)friendNumber + msgv3HashHex:(NSString *)msgv3HashHex + sendTimestamp:(uint32_t)sendTimestamp +{ + OCTSendMessageOperation *operation = [[OCTSendMessageOperation alloc] initWithTox:[self.dataSource managerGetTox] + friendNumber:friendNumber + messageType:OCTToxMessageTypeHighlevelack + message:message + msgv3HashHex:msgv3HashHex + msgv3tssec:sendTimestamp + successBlock:nil + failureBlock:nil]; + [self.sendMessageQueue addOperation:operation]; +} + +/* + * Process incoming text message from friend. + */ +- (void)tox:(OCTTox *)tox friendMessage:(NSString *)message + type:(OCTToxMessageType)type + friendNumber:(OCTToxFriendNumber)friendNumber + msgv3HashHex:(NSString *)msgv3HashHex + sendTimestamp:(uint32_t)sendTimestamp +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend]; + + if (msgv3HashHex != nil) + { + // HINT: check for double message, but only select incoming messages (senderUniqueIdentifier != NULL) + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.msgv3HashHex == %@ AND senderUniqueIdentifier != nil", + chat.uniqueIdentifier, msgv3HashHex]; + RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate]; + OCTMessageAbstract *message_found = [results firstObject]; + + if (message_found) { + OCTLogInfo(@"friendMessage ignoring double message i %@", chat.uniqueIdentifier); + OCTLogInfo(@"friendMessage ignoring double message f %@", friend); + return; + } + } + + [realmManager addMessageWithText:message type:type chat:chat sender:friend messageId:0 msgv3HashHex:msgv3HashHex sentPush:NO tssent:sendTimestamp tsrcvd:0]; +} + +- (void)tox:(OCTTox *)tox friendHighLevelACK:(NSString *)message + friendNumber:(OCTToxFriendNumber)friendNumber + msgv3HashHex:(NSString *)msgv3HashHex + sendTimestamp:(uint32_t)sendTimestamp +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend]; + + // HINT: only select outgoing messages + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.msgv3HashHex == %@ AND senderUniqueIdentifier == nil", + chat.uniqueIdentifier, msgv3HashHex]; + + // HINT: we still sort and use only 1 result row, just in case more than 1 row is returned. + // but if more than 1 row is returned that would actually be an error. + // we use the newest message with this Hash + RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate]; + results = [results sortedResultsUsingKeyPath:@"dateInterval" ascending:YES]; + + OCTMessageAbstract *message_found = [results firstObject]; + + if (! message_found) { + return; + } + + OCTLogInfo(@"friendHighLevelACK recevied from friend %@", friend); + + [realmManager updateObject:message_found withBlock:^(OCTMessageAbstract *theMessage) { + theMessage.messageText.isDelivered = YES; + }]; +} + + +- (void)tox:(OCTTox *)tox messageDelivered:(OCTToxMessageId)messageId friendNumber:(OCTToxFriendNumber)friendNumber +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + + if (friend.msgv3Capability == YES) + { + // HINT: if friend has msgV3 capability, we ignore the low level ACK and keep waiting for the high level ACK + OCTLogInfo(@"messageDelivered ignoring low level ACK %@", friend); + return; + } + + OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend]; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.messageId == %d", + chat.uniqueIdentifier, messageId]; + + // messageId is reset on every launch, so we want to update delivered status on latest message. + RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate]; + results = [results sortedResultsUsingKeyPath:@"dateInterval" ascending:NO]; + + OCTMessageAbstract *message = [results firstObject]; + + if (! message) { + return; + } + + [realmManager updateObject:message withBlock:^(OCTMessageAbstract *theMessage) { + theMessage.messageText.isDelivered = YES; + }]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerDataSource.h b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerDataSource.h new file mode 100644 index 0000000..b6739ce --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerDataSource.h @@ -0,0 +1,46 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@class OCTTox; +@class OCTRealmManager; +@protocol OCTFileStorageProtocol; + +/** + * Notification is send when connection status of friend has changed. + * + * - object OCTFriend whose status has changed. + * - userInfo nil + */ +static NSString *const kOCTFriendConnectionStatusChangeNotification = @"kOCTFriendConnectionStatusChangeNotification"; + +/** + * Notification is send on user avatar update. + * + * - object nil + * - userInfo nil + */ +static NSString *const kOCTUserAvatarWasUpdatedNotification = @"kOCTUserAvatarWasUpdatedNotification"; + +/** + * Send this notifications to schedule cleanup of uploaded/downloaded files. All files without OCTMessageFile + * will be removed. + * + * - object nil + * - userInfo nil + */ +static NSString *const kOCTScheduleFileTransferCleanupNotification = @"kOCTScheduleFileTransferCleanupNotification"; + +@protocol OCTSubmanagerDataSource + +- (OCTTox *)managerGetTox; +- (BOOL)managerIsToxConnected; +- (void)managerSaveTox; +- (OCTRealmManager *)managerGetRealmManager; +- (id)managerGetFileStorage; +- (NSNotificationCenter *)managerGetNotificationCenter; +- (BOOL)managerUseFauxOfflineMessaging; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerFilesImpl.h b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerFilesImpl.h new file mode 100644 index 0000000..591ad1a --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerFilesImpl.h @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerFiles.h" +#import "OCTSubmanagerProtocol.h" + +@interface OCTSubmanagerFilesImpl : NSObject + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerFilesImpl.m b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerFilesImpl.m new file mode 100644 index 0000000..b331ff8 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerFilesImpl.m @@ -0,0 +1,1000 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerFilesImpl.h" +#import "OCTSubmanagerFilesProgressSubscriber.h" +#import "OCTTox.h" +#import "OCTToxConstants.h" +#import "OCTFileDownloadOperation.h" +#import "OCTFileUploadOperation.h" +#import "OCTRealmManager.h" +#import "OCTLogging.h" +#import "OCTMessageAbstract.h" +#import "OCTMessageFile.h" +#import "OCTFriend.h" +#import "OCTChat.h" +#import "OCTFileStorageProtocol.h" +#import "OCTFilePathInput.h" +#import "OCTFilePathOutput.h" +#import "OCTFileDataInput.h" +#import "OCTFileDataOutput.h" +#import "OCTFileTools.h" +#import "OCTSettingsStorageObject.h" +#import "NSError+OCTFile.h" + +#if TARGET_OS_IPHONE +@import MobileCoreServices; +#endif + +static NSString *const kDownloadsTempDirectory = @"me.dvor.objcTox.downloads"; + +static NSString *const kProgressSubscribersKey = @"kProgressSubscribersKey"; +static NSString *const kMessageIdentifierKey = @"kMessageIdentifierKey"; + +@interface OCTSubmanagerFilesImpl () + +@property (strong, nonatomic, readonly) NSOperationQueue *queue; + +@property (strong, nonatomic, readonly) NSObject *filesCleanupLock; +@property (assign, nonatomic) BOOL filesCleanupInProgress; + +@end + +@implementation OCTSubmanagerFilesImpl +@synthesize dataSource = _dataSource; + +#pragma mark - Lifecycle + +- (instancetype)init +{ + self = [super init]; + + if (! self) { + return nil; + } + + _queue = [NSOperationQueue new]; + _filesCleanupLock = [NSObject new]; + + return self; +} + +- (void)dealloc +{ + [self.dataSource.managerGetNotificationCenter removeObserver:self]; +} + +- (void)configure +{ + [self.dataSource.managerGetNotificationCenter addObserver:self + selector:@selector(friendConnectionStatusChangeNotification:) + name:kOCTFriendConnectionStatusChangeNotification + object:nil]; + [self.dataSource.managerGetNotificationCenter addObserver:self + selector:@selector(userAvatarWasUpdatedNotification) + name:kOCTUserAvatarWasUpdatedNotification + object:nil]; + [self.dataSource.managerGetNotificationCenter addObserver:self + selector:@selector(scheduleFilesCleanup) + name:kOCTScheduleFileTransferCleanupNotification + object:nil]; + + OCTLogInfo(@"cancelling pending files..."); + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"fileType == %d OR fileType == %d OR fileType == %d", + OCTMessageFileTypeWaitingConfirmation, OCTMessageFileTypeLoading, OCTMessageFileTypePaused]; + + [realmManager updateObjectsWithClass:[OCTMessageFile class] predicate:predicate updateBlock:^(OCTMessageFile *file) { + file.fileType = OCTMessageFileTypeCanceled; + OCTLogInfo(@"cancelling file %@", file); + }]; + + OCTLogInfo(@"cancelling pending files... done"); + + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSString *downloads = [self downloadsTempDirectory]; + OCTLogInfo(@"clearing downloads temp directory %@\ncontents %@", + downloads, + [fileManager contentsOfDirectoryAtPath:downloads error:nil]); + [fileManager removeItemAtPath:downloads error:nil]; + + [self scheduleFilesCleanup]; +} + +#pragma mark - Public + +- (void)sendData:(nonnull NSData *)data + withFileName:(nonnull NSString *)fileName + toChat:(nonnull OCTChat *)chat + failureBlock:(nullable void (^)(NSError *__nonnull error))failureBlock +{ + NSParameterAssert(data); + NSParameterAssert(fileName); + NSParameterAssert(chat); + + NSString *filePath = [OCTFileTools createNewFilePathInDirectory:[self uploadsDirectory] fileName:fileName]; + + if (! [data writeToFile:filePath atomically:NO]) { + OCTLogWarn(@"cannot save data to uploads directory."); + if (failureBlock) { + failureBlock([NSError sendFileErrorCannotSaveFileToUploads]); + } + return; + } + + [self sendFileAtPath:filePath moveToUploads:NO toChat:chat failureBlock:failureBlock]; +} + +- (void)sendFileAtPath:(nonnull NSString *)filePath + moveToUploads:(BOOL)moveToUploads + toChat:(nonnull OCTChat *)chat + failureBlock:(nullable void (^)(NSError *__nonnull error))failureBlock +{ + NSParameterAssert(filePath); + NSParameterAssert(chat); + + NSString *fileName = [filePath lastPathComponent]; + NSError *error; + + if (moveToUploads) { + NSString *toPath = [OCTFileTools createNewFilePathInDirectory:[self uploadsDirectory] fileName:fileName]; + + if (! [[NSFileManager defaultManager] moveItemAtPath:filePath toPath:toPath error:&error]) { + OCTLogWarn(@"cannot move file to uploads %@", error); + if (failureBlock) { + failureBlock([NSError sendFileErrorCannotSaveFileToUploads]); + } + return; + } + + filePath = toPath; + } + + NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; + + if (! attributes) { + OCTLogWarn(@"cannot read file %@", filePath); + if (failureBlock) { + failureBlock([NSError sendFileErrorCannotReadFile]); + } + return; + } + + OCTToxFileSize fileSize = [attributes[NSFileSize] longLongValue]; + OCTFriend *friend = [chat.friends firstObject]; + + OCTToxFileNumber fileNumber = [[self.dataSource managerGetTox] fileSendWithFriendNumber:friend.friendNumber + kind:OCTToxFileKindData + fileSize:fileSize + fileId:nil + fileName:fileName + error:&error]; + + if (fileNumber == kOCTToxFileNumberFailure) { + OCTLogWarn(@"cannot send file %@", error); + if (failureBlock) { + failureBlock([NSError sendFileErrorFromToxFileSendError:error.code]); + } + return; + } + + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + OCTMessageAbstract *message = [realmManager addMessageWithFileNumber:fileNumber + fileType:OCTMessageFileTypeWaitingConfirmation + fileSize:fileSize + fileName:fileName + filePath:filePath + fileUTI:[self fileUTIFromFileName:fileName] + chat:chat + sender:nil]; + + NSDictionary *userInfo = [self fileOperationUserInfoWithMessage:message]; + OCTFilePathInput *input = [[OCTFilePathInput alloc] initWithFilePath:filePath]; + + OCTFileUploadOperation *operation = [[OCTFileUploadOperation alloc] initWithTox:[self.dataSource managerGetTox] + fileInput:input + friendNumber:friend.friendNumber + fileNumber:fileNumber + fileSize:fileSize + userInfo:userInfo + progressBlock:[self fileProgressBlockWithMessage:message] + etaUpdateBlock:[self fileEtaUpdateBlockWithMessage:message] + successBlock:[self fileSuccessBlockWithMessage:message] + failureBlock:[self fileFailureBlockWithMessage:message + userFailureBlock:failureBlock]]; + + [self.queue addOperation:operation]; +} + +- (void)acceptFileTransfer:(OCTMessageAbstract *)message + failureBlock:(nullable void (^)(NSError *__nonnull error))failureBlock +{ + if (! message.senderUniqueIdentifier) { + OCTLogWarn(@"specified wrong message: no sender. %@", message); + if (failureBlock) { + failureBlock([NSError acceptFileErrorWrongMessage:message]); + } + return; + } + + if (! message.messageFile) { + OCTLogWarn(@"specified wrong message: no messageFile. %@", message); + if (failureBlock) { + failureBlock([NSError acceptFileErrorWrongMessage:message]); + } + return; + } + + if (message.messageFile.fileType != OCTMessageFileTypeWaitingConfirmation) { + OCTLogWarn(@"specified wrong message: wrong file type, should be WaitingConfirmation. %@", message); + if (failureBlock) { + failureBlock([NSError acceptFileErrorWrongMessage:message]); + } + return; + } + + OCTFilePathOutput *output = [[OCTFilePathOutput alloc] initWithTempFolder:[self downloadsTempDirectory] + resultFolder:[self downloadsDirectory] + fileName:message.messageFile.fileName]; + + NSDictionary *userInfo = [self fileOperationUserInfoWithMessage:message]; + + OCTFriend *friend = [[self.dataSource managerGetRealmManager] objectWithUniqueIdentifier:message.senderUniqueIdentifier + class:[OCTFriend class]]; + + OCTFileDownloadOperation *operation = [[OCTFileDownloadOperation alloc] + initWithTox:self.dataSource.managerGetTox + fileOutput:output + friendNumber:friend.friendNumber + fileNumber:message.messageFile.internalFileNumber + fileSize:message.messageFile.fileSize + userInfo:userInfo + progressBlock:[self fileProgressBlockWithMessage:message] + etaUpdateBlock:[self fileEtaUpdateBlockWithMessage:message] + successBlock:[self fileSuccessBlockWithMessage:message] + failureBlock:[self fileFailureBlockWithMessage:message + userFailureBlock:failureBlock]]; + + [self.queue addOperation:operation]; + + [self updateMessageFile:message withBlock:^(OCTMessageFile *file) { + file.fileType = OCTMessageFileTypeLoading; + [file internalSetFilePath:output.resultFilePath]; + }]; +} + +- (BOOL)cancelFileTransfer:(OCTMessageAbstract *)message error:(NSError **)error +{ + if (! message.messageFile) { + OCTLogWarn(@"specified wrong message: no messageFile. %@", message); + if (error) { + *error = [NSError fileTransferErrorWrongMessage:message]; + } + return NO; + } + + OCTFriend *friend = [self friendForMessage:message]; + + [self.dataSource.managerGetTox fileSendControlForFileNumber:message.messageFile.internalFileNumber + friendNumber:friend.friendNumber + control:OCTToxFileControlCancel + error:nil]; + + OCTFileBaseOperation *operation = [self operationWithFileNumber:message.messageFile.internalFileNumber + friendNumber:friend.friendNumber]; + [operation cancel]; + + [self updateMessageFile:message withBlock:^(OCTMessageFile *file) { + file.fileType = OCTMessageFileTypeCanceled; + }]; + + return YES; +} + +- (void)retrySendingFile:(nonnull OCTMessageAbstract *)message + failureBlock:(nullable void (^)(NSError *__nonnull error))failureBlock +{ + NSParameterAssert(message); + + if (! message.messageFile || (message.messageFile.fileType != OCTMessageFileTypeCanceled)) { + OCTLogWarn(@"specified wrong message: no messageFile. %@", message); + if (failureBlock) { + failureBlock([NSError fileTransferErrorWrongMessage:message]); + } + return; + } + + NSString *fileName = message.messageFile.fileName; + NSString *filePath = [message.messageFile filePath]; + + NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; + + if (! attributes) { + OCTLogWarn(@"cannot read file %@", filePath); + if (failureBlock) { + failureBlock([NSError sendFileErrorCannotReadFile]); + } + return; + } + + OCTToxFileSize fileSize = [attributes[NSFileSize] longLongValue]; + OCTFriend *friend = [self friendForMessage:message]; + NSError *error; + + OCTToxFileNumber fileNumber = [[self.dataSource managerGetTox] fileSendWithFriendNumber:friend.friendNumber + kind:OCTToxFileKindData + fileSize:fileSize + fileId:nil + fileName:fileName + error:&error]; + + if (fileNumber == kOCTToxFileNumberFailure) { + OCTLogWarn(@"cannot send file %@", error); + if (failureBlock) { + failureBlock([NSError sendFileErrorFromToxFileSendError:error.code]); + } + return; + } + + [self updateMessageFile:message withBlock:^(OCTMessageFile *messageFile) { + messageFile.internalFileNumber = fileNumber; + messageFile.fileType = OCTMessageFileTypeWaitingConfirmation; + messageFile.fileSize = fileSize; + }]; + + NSDictionary *userInfo = [self fileOperationUserInfoWithMessage:message]; + OCTFilePathInput *input = [[OCTFilePathInput alloc] initWithFilePath:filePath]; + + OCTFileUploadOperation *operation = [[OCTFileUploadOperation alloc] initWithTox:[self.dataSource managerGetTox] + fileInput:input + friendNumber:friend.friendNumber + fileNumber:fileNumber + fileSize:fileSize + userInfo:userInfo + progressBlock:[self fileProgressBlockWithMessage:message] + etaUpdateBlock:[self fileEtaUpdateBlockWithMessage:message] + successBlock:[self fileSuccessBlockWithMessage:message] + failureBlock:[self fileFailureBlockWithMessage:message + userFailureBlock:failureBlock]]; + + [self.queue addOperation:operation]; +} + +- (BOOL)pauseFileTransfer:(BOOL)pause message:(nonnull OCTMessageAbstract *)message error:(NSError **)error +{ + if (! message.messageFile) { + OCTLogWarn(@"specified wrong message: no messageFile. %@", message); + if (error) { + *error = [NSError fileTransferErrorWrongMessage:message]; + } + return NO; + } + + OCTToxFileControl control; + OCTMessageFileType type; + OCTMessageFilePausedBy pausedBy = message.messageFile.pausedBy; + + if (pause) { + BOOL pausedByFriend = message.messageFile.pausedBy & OCTMessageFilePausedByFriend; + + if ((message.messageFile.fileType != OCTMessageFileTypeLoading) && ! pausedByFriend) { + OCTLogWarn(@"message in wrong state %ld", (long)message.messageFile.fileType); + return YES; + } + + control = OCTToxFileControlPause; + type = OCTMessageFileTypePaused; + pausedBy |= OCTMessageFilePausedByUser; + } + else { + BOOL pausedByUser = message.messageFile.pausedBy & OCTMessageFilePausedByUser; + if ((message.messageFile.fileType != OCTMessageFileTypePaused) && ! pausedByUser) { + OCTLogWarn(@"message in wrong state %ld", (long)message.messageFile.fileType); + return YES; + } + + control = OCTToxFileControlResume; + pausedBy &= ~OCTMessageFilePausedByUser; + + type = (pausedBy == OCTMessageFilePausedByNone) ? OCTMessageFileTypeLoading : OCTMessageFileTypePaused; + } + + OCTFriend *friend = [self friendForMessage:message]; + + [self.dataSource.managerGetTox fileSendControlForFileNumber:message.messageFile.internalFileNumber + friendNumber:friend.friendNumber + control:control + error:nil]; + + [self updateMessageFile:message withBlock:^(OCTMessageFile *file) { + file.fileType = type; + file.pausedBy = pausedBy; + }]; + + return YES; +} + +- (BOOL)addProgressSubscriber:(nonnull id)subscriber + forFileTransfer:(nonnull OCTMessageAbstract *)message + error:(NSError **)error +{ + if (! message.messageFile) { + if (error) { + *error = [NSError fileTransferErrorWrongMessage:message]; + } + return NO; + } + + OCTFriend *friend = [self friendForMessage:message]; + + OCTFileBaseOperation *operation = [self operationWithFileNumber:message.messageFile.internalFileNumber + friendNumber:friend.friendNumber]; + + if (! operation) { + return YES; + } + + NSString *identifier = operation.userInfo[kMessageIdentifierKey]; + if (! [identifier isEqualToString:message.uniqueIdentifier]) { + return YES; + } + + [subscriber submanagerFilesOnProgressUpdate:operation.progress message:message]; + [subscriber submanagerFilesOnEtaUpdate:operation.eta bytesPerSecond:operation.bytesPerSecond message:message]; + + NSHashTable *progressSubscribers = operation.userInfo[kProgressSubscribersKey]; + [progressSubscribers addObject:subscriber]; + + return YES; +} + +- (BOOL)removeProgressSubscriber:(nonnull id)subscriber + forFileTransfer:(nonnull OCTMessageAbstract *)message + error:(NSError **)error +{ + if (! message.messageFile) { + if (error) { + *error = [NSError fileTransferErrorWrongMessage:message]; + } + return NO; + } + + OCTFriend *friend = [self friendForMessage:message]; + + OCTFileBaseOperation *operation = [self operationWithFileNumber:message.messageFile.internalFileNumber + friendNumber:friend.friendNumber]; + + if (! operation) { + return YES; + } + + NSString *identifier = operation.userInfo[kMessageIdentifierKey]; + if (! [identifier isEqualToString:message.uniqueIdentifier]) { + return YES; + } + + NSHashTable *progressSubscribers = operation.userInfo[kProgressSubscribersKey]; + [progressSubscribers removeObject:subscriber]; + + return YES; +} + +#pragma mark - OCTToxDelegate + +- (void) tox:(OCTTox *)tox fileReceiveControl:(OCTToxFileControl)control + friendNumber:(OCTToxFriendNumber)friendNumber + fileNumber:(OCTToxFileNumber)fileNumber +{ + OCTFileBaseOperation *operation = [self operationWithFileNumber:fileNumber friendNumber:friendNumber]; + + NSString *identifier = operation.userInfo[kMessageIdentifierKey]; + OCTMessageAbstract *message; + + if (identifier) { + message = [self.dataSource.managerGetRealmManager objectWithUniqueIdentifier:identifier + class:[OCTMessageAbstract class]]; + } + + switch (control) { + case OCTToxFileControlResume: { + [self updateMessageFile:message withBlock:^(OCTMessageFile *file) { + file.pausedBy &= ~OCTMessageFilePausedByFriend; + file.fileType = (file.pausedBy == OCTMessageFilePausedByNone) ? OCTMessageFileTypeLoading : OCTMessageFileTypePaused; + }]; + break; + } + case OCTToxFileControlPause: { + [self updateMessageFile:message withBlock:^(OCTMessageFile *file) { + file.pausedBy |= OCTMessageFilePausedByFriend; + file.fileType = OCTMessageFileTypePaused; + }]; + break; + } + case OCTToxFileControlCancel: { + [operation cancel]; + + [self updateMessageFile:message withBlock:^(OCTMessageFile *file) { + file.fileType = OCTMessageFileTypeCanceled; + }]; + break; + } + } +} + +- (void) tox:(OCTTox *)tox fileChunkRequestForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + position:(OCTToxFileSize)position + length:(size_t)length +{ + OCTFileBaseOperation *operation = [self operationWithFileNumber:fileNumber friendNumber:friendNumber]; + + if ([operation isKindOfClass:[OCTFileUploadOperation class]]) { + [(OCTFileUploadOperation *)operation chunkRequestWithPosition:position length:length]; + } + else { + OCTLogWarn(@"operation not found with fileNumber %d friendNumber %d", fileNumber, friendNumber); + [self.dataSource.managerGetTox fileSendControlForFileNumber:fileNumber friendNumber:friendNumber control:OCTToxFileControlCancel error:nil]; + } +} + +- (void) tox:(OCTTox *)tox fileReceiveForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + kind:(OCTToxFileKind)kind + fileSize:(OCTToxFileSize)fileSize + fileName:(NSString *)fileName +{ + switch (kind) { + case OCTToxFileKindData: + [self dataFileReceiveForFileNumber:fileNumber + friendNumber:friendNumber + kind:kind + fileSize:fileSize + fileName:fileName]; + break; + case OCTToxFileKindAvatar: + [self avatarFileReceiveForFileNumber:fileNumber + friendNumber:friendNumber + kind:kind + fileSize:fileSize + fileName:fileName]; + break; + } +} + +- (void) tox:(OCTTox *)tox fileReceiveChunk:(NSData *)chunk + fileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + position:(OCTToxFileSize)position +{ + OCTFileBaseOperation *operation = [self operationWithFileNumber:fileNumber friendNumber:friendNumber]; + + if ([operation isKindOfClass:[OCTFileDownloadOperation class]]) { + [(OCTFileDownloadOperation *)operation receiveChunk:chunk position:position]; + } + else { + OCTLogWarn(@"operation not found with fileNumber %d friendNumber %d", fileNumber, friendNumber); + [self.dataSource.managerGetTox fileSendControlForFileNumber:fileNumber friendNumber:friendNumber control:OCTToxFileControlCancel error:nil]; + } +} + +#pragma mark - NSNotification + +- (void)friendConnectionStatusChangeNotification:(NSNotification *)notification +{ + OCTFriend *friend = notification.object; + + if (! friend) { + OCTLogWarn(@"no friend received in notification %@, exiting", notification); + return; + } + + [self sendAvatarToFriend:friend]; +} + +- (void)userAvatarWasUpdatedNotification +{ + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"connectionStatus != %d", OCTToxConnectionStatusNone]; + RLMResults *onlineFriends = [self.dataSource.managerGetRealmManager objectsWithClass:[OCTFriend class] predicate:predicate]; + + for (OCTFriend *friend in onlineFriends) { + [self sendAvatarToFriend:friend]; + } +} + +#pragma mark - Private + +- (void)scheduleFilesCleanup +{ + @synchronized(self.filesCleanupLock) { + if (self.filesCleanupInProgress) { + return; + } + self.filesCleanupInProgress = YES; + } + + OCTLogInfo(@"cleanup: starting files cleanup"); + + NSString *uploads = [self uploadsDirectory]; + NSString *downloads = [self downloadsDirectory]; + + __weak OCTSubmanagerFilesImpl *weakSelf = self; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSMutableSet *allFiles = [NSMutableSet new]; + + NSError *error; + NSArray *uploadsContents = [fileManager contentsOfDirectoryAtPath:uploads error:&error]; + if (uploadsContents) { + for (NSString *file in uploadsContents) { + [allFiles addObject:[uploads stringByAppendingPathComponent:file]]; + } + } + else { + OCTLogWarn(@"cleanup: cannot read contents of uploads directory %@, error %@", uploads, error); + } + + NSArray *downloadsContents = [fileManager contentsOfDirectoryAtPath:downloads error:&error]; + if (downloadsContents) { + for (NSString *file in downloadsContents) { + [allFiles addObject:[downloads stringByAppendingPathComponent:file]]; + } + } + else { + OCTLogWarn(@"cleanup: cannot read contents of download directory %@, error %@", downloads, error); + } + + OCTLogInfo(@"cleanup: total number of files %lu", (unsigned long)allFiles.count); + OCTToxFileSize freedSpace = 0; + + for (NSString *path in allFiles) { + __strong OCTSubmanagerFilesImpl *strongSelf = weakSelf; + if (! strongSelf) { + OCTLogWarn(@"cleanup: submanager was killed, quiting"); + } + + __block BOOL exists = NO; + + dispatch_sync(dispatch_get_main_queue(), ^{ + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"internalFilePath == %@", + [path stringByAbbreviatingWithTildeInPath]]; + + OCTRealmManager *realmManager = strongSelf.dataSource.managerGetRealmManager; + RLMResults *results = [realmManager objectsWithClass:[OCTMessageFile class] predicate:predicate]; + + exists = (results.count > 0); + }); + + if (exists) { + continue; + } + + OCTLogInfo(@"cleanup: found unbounded file, removing it. Path %@", path); + + NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; + if (! attributes) { + OCTLogWarn(@"cleanup: cannot read file at path %@, error %@", path, error); + } + + if ([fileManager removeItemAtPath:path error:&error]) { + freedSpace += [attributes[NSFileSize] longLongValue]; + } + else { + OCTLogWarn(@"cleanup: cannot remove file at path %@, error %@", path, error); + } + } + + OCTLogInfo(@"cleanup: done. Freed %lld bytes.", freedSpace); + + __strong OCTSubmanagerFilesImpl *strongSelf = weakSelf; + @synchronized(strongSelf.filesCleanupLock) { + strongSelf.filesCleanupInProgress = NO; + } + }); +} + +- (OCTFileBaseOperation *)operationWithFileNumber:(OCTToxFileNumber)fileNumber friendNumber:(OCTToxFriendNumber)friendNumber +{ + NSString *operationId = [OCTFileBaseOperation operationIdFromFileNumber:fileNumber friendNumber:friendNumber]; + + for (OCTFileBaseOperation *operation in [self.queue operations]) { + if ([operation.operationId isEqualToString:operationId]) { + return operation; + } + } + + return nil; +} + +- (void)createDirectoryIfNeeded:(NSString *)path +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + + BOOL isDirectory; + BOOL exists = [fileManager fileExistsAtPath:path isDirectory:&isDirectory]; + + if (exists && ! isDirectory) { + [fileManager removeItemAtPath:path error:nil]; + exists = NO; + } + + if (! exists) { + [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; + } +} + +- (NSString *)downloadsDirectory +{ + id fileStorage = self.dataSource.managerGetFileStorage; + + NSString *path = fileStorage.pathForDownloadedFilesDirectory; + [self createDirectoryIfNeeded:path]; + + return path; +} + +- (NSString *)uploadsDirectory +{ + id fileStorage = self.dataSource.managerGetFileStorage; + + NSString *path = fileStorage.pathForUploadedFilesDirectory; + [self createDirectoryIfNeeded:path]; + + return path; +} + +- (NSString *)downloadsTempDirectory +{ + id fileStorage = self.dataSource.managerGetFileStorage; + + NSString *path = [fileStorage.pathForTemporaryFilesDirectory stringByAppendingPathComponent:kDownloadsTempDirectory]; + [self createDirectoryIfNeeded:path]; + + return path; +} + +- (NSString *)fileUTIFromFileName:(NSString *)fileName +{ + NSString *extension = [fileName pathExtension]; + + if (! extension) { + return nil; + } + + return (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag( + kUTTagClassFilenameExtension, + (__bridge CFStringRef)extension, + NULL); +} + +- (void)updateMessageFile:(OCTMessageAbstract *)message withBlock:(void (^)(OCTMessageFile *))block +{ + if (! message) { + DDLogWarn(@"no message to update"); + return; + } + + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + [realmManager updateObject:message.messageFile withBlock:block]; + + // Workaround to force Realm to update OCTMessageAbstract when OCTMessageFile was updated. + [realmManager updateObject:message withBlock:^(OCTMessageAbstract *message) { + message.dateInterval = message.dateInterval; + }]; +} + +- (OCTFileBaseOperationProgressBlock)fileProgressBlockWithMessage:(OCTMessageAbstract *)message +{ + return ^(OCTFileBaseOperation *__nonnull operation) { + NSHashTable *progressSubscribers = operation.userInfo[kProgressSubscribersKey]; + + for (id subscriber in progressSubscribers) { + [subscriber submanagerFilesOnProgressUpdate:operation.progress message:message]; + } + }; +} + +- (OCTFileBaseOperationProgressBlock)fileEtaUpdateBlockWithMessage:(OCTMessageAbstract *)message +{ + return ^(OCTFileBaseOperation *__nonnull operation) { + NSHashTable *progressSubscribers = operation.userInfo[kProgressSubscribersKey]; + + for (id subscriber in progressSubscribers) { + [subscriber submanagerFilesOnEtaUpdate:operation.eta + bytesPerSecond:operation.bytesPerSecond + message:message]; + } + }; +} + +- (OCTFileBaseOperationSuccessBlock)fileSuccessBlockWithMessage:(OCTMessageAbstract *)message +{ + __weak OCTSubmanagerFilesImpl *weakSelf = self; + + return ^(OCTFileBaseOperation *__nonnull operation) { + __strong OCTSubmanagerFilesImpl *strongSelf = weakSelf; + [strongSelf updateMessageFile:message withBlock:^(OCTMessageFile *file) { + + file.fileType = OCTMessageFileTypeReady; + }]; + }; +} + +- (OCTFileBaseOperationFailureBlock)fileFailureBlockWithMessage:(OCTMessageAbstract *)message + userFailureBlock:(void (^)(NSError *))userFailureBlock +{ + __weak OCTSubmanagerFilesImpl *weakSelf = self; + + return ^(OCTFileBaseOperation *__nonnull operation, NSError *__nonnull error) { + __strong OCTSubmanagerFilesImpl *strongSelf = weakSelf; + [strongSelf updateMessageFile:message withBlock:^(OCTMessageFile *file) { + file.fileType = OCTMessageFileTypeCanceled; + + if (userFailureBlock) { + userFailureBlock(error); + } + }]; + }; +} + +- (NSDictionary *)fileOperationUserInfoWithMessage:(OCTMessageAbstract *)message +{ + return @{ + kProgressSubscribersKey : [NSHashTable weakObjectsHashTable], + kMessageIdentifierKey : message.uniqueIdentifier, + }; +} + +- (void)sendAvatarToFriend:(OCTFriend *)friend +{ + NSParameterAssert(friend); + + NSData *avatar = self.dataSource.managerGetRealmManager.settingsStorage.userAvatarData; + + if (! avatar) { + [[self.dataSource managerGetTox] fileSendWithFriendNumber:friend.friendNumber + kind:OCTToxFileKindAvatar + fileSize:0 + fileId:nil + fileName:nil + error:nil]; + return; + } + + OCTToxFileSize fileSize = avatar.length; + NSData *hash = [self.dataSource.managerGetTox hashData:avatar]; + + NSError *error; + OCTToxFileNumber fileNumber = [[self.dataSource managerGetTox] fileSendWithFriendNumber:friend.friendNumber + kind:OCTToxFileKindAvatar + fileSize:fileSize + fileId:hash + fileName:nil + error:&error]; + + if (fileNumber == kOCTToxFileNumberFailure) { + OCTLogWarn(@"cannot send file %@", error); + return; + } + + OCTFileDataInput *input = [[OCTFileDataInput alloc] initWithData:avatar]; + + OCTFileUploadOperation *operation = [[OCTFileUploadOperation alloc] initWithTox:[self.dataSource managerGetTox] + fileInput:input + friendNumber:friend.friendNumber + fileNumber:fileNumber + fileSize:fileSize + userInfo:nil + progressBlock:nil + etaUpdateBlock:nil + successBlock:nil + failureBlock:nil]; + + [self.queue addOperation:operation]; +} + +- (void)dataFileReceiveForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + kind:(OCTToxFileKind)kind + fileSize:(OCTToxFileSize)fileSize + fileName:(NSString *)fileName +{ + if (fileSize == 0) { + OCTLogWarn(@"Received file with size 0, ignoring it."); + [self.dataSource.managerGetTox fileSendControlForFileNumber:fileNumber friendNumber:friendNumber control:OCTToxFileControlCancel error:nil]; + return; + } + + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend]; + + [realmManager addMessageWithFileNumber:fileNumber + fileType:OCTMessageFileTypeWaitingConfirmation + fileSize:fileSize + fileName:fileName + filePath:nil + fileUTI:[self fileUTIFromFileName:fileName] + chat:chat + sender:friend]; +} + +- (void)avatarFileReceiveForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + kind:(OCTToxFileKind)kind + fileSize:(OCTToxFileSize)fileSize + fileName:(NSString *)fileName +{ + void (^cancelBlock)() = ^() { + [self.dataSource.managerGetTox fileSendControlForFileNumber:fileNumber friendNumber:friendNumber control:OCTToxFileControlCancel error:nil]; + }; + + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [[self.dataSource managerGetRealmManager] friendWithPublicKey:publicKey]; + + if (fileSize == 0) { + if (friend.avatarData) { + [[self.dataSource managerGetRealmManager] updateObject:friend withBlock:^(OCTFriend *theFriend) { + theFriend.avatarData = nil; + }]; + } + + cancelBlock(); + return; + } + + if (fileSize > kOCTManagerMaxAvatarSize) { + OCTLogWarn(@"received avatar is too big, ignoring it, size %lld", fileSize); + cancelBlock(); + return; + } + + NSData *hash = [self.dataSource.managerGetTox hashData:friend.avatarData]; + NSData *remoteHash = [self.dataSource.managerGetTox fileGetFileIdForFileNumber:fileNumber + friendNumber:friendNumber + error:nil]; + + if (remoteHash && [hash isEqual:remoteHash]) { + OCTLogInfo(@"received same avatar, ignoring it"); + cancelBlock(); + return; + } + + OCTFileDataOutput *output = [OCTFileDataOutput new]; + __weak OCTSubmanagerFilesImpl *weakSelf = self; + + OCTFileDownloadOperation *operation = [[OCTFileDownloadOperation alloc] initWithTox:self.dataSource.managerGetTox + fileOutput:output + friendNumber:friendNumber + fileNumber:fileNumber + fileSize:fileSize + userInfo:nil + progressBlock:nil + etaUpdateBlock:nil + successBlock:^(OCTFileBaseOperation *__nonnull operation) { + __strong OCTSubmanagerFilesImpl *strongSelf = weakSelf; + + NSString *publicKey = [[strongSelf.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [[strongSelf.dataSource managerGetRealmManager] friendWithPublicKey:publicKey]; + + [[strongSelf.dataSource managerGetRealmManager] updateObject:friend withBlock:^(OCTFriend *theFriend) { + theFriend.avatarData = output.resultData; + }]; + } failureBlock:nil]; + + [self.queue addOperation:operation]; +} + +- (OCTFriend *)friendForMessage:(OCTMessageAbstract *)message +{ + OCTChat *chat = [[self.dataSource managerGetRealmManager] objectWithUniqueIdentifier:message.chatUniqueIdentifier + class:[OCTChat class]]; + return [chat.friends firstObject]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerFriendsImpl.h b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerFriendsImpl.h new file mode 100644 index 0000000..b711812 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerFriendsImpl.h @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerFriends.h" +#import "OCTSubmanagerProtocol.h" + +@interface OCTSubmanagerFriendsImpl : NSObject + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerFriendsImpl.m b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerFriendsImpl.m new file mode 100644 index 0000000..b29d87b --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerFriendsImpl.m @@ -0,0 +1,361 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerFriendsImpl.h" +#import "OCTLogging.h" +#import "OCTTox.h" +#import "OCTFriend.h" +#import "OCTFriendRequest.h" +#import "OCTRealmManager.h" +#import "Firebase.h" + +@implementation OCTSubmanagerFriendsImpl +@synthesize dataSource = _dataSource; + +#pragma mark - Public + +- (BOOL)sendFriendRequestToAddress:(NSString *)address message:(NSString *)message error:(NSError **)error +{ + NSParameterAssert(address); + NSParameterAssert(message); + + OCTTox *tox = [self.dataSource managerGetTox]; + + OCTToxFriendNumber friendNumber = [tox addFriendWithAddress:address message:message error:error]; + + if (friendNumber == kOCTToxFriendNumberFailure) { + return NO; + } + + [self.dataSource managerSaveTox]; + + return [self createFriendWithFriendNumber:friendNumber error:error]; +} + +- (BOOL)approveFriendRequest:(OCTFriendRequest *)friendRequest error:(NSError **)error +{ + NSParameterAssert(friendRequest); + + OCTTox *tox = [self.dataSource managerGetTox]; + + OCTToxFriendNumber friendNumber = [tox addFriendWithNoRequestWithPublicKey:friendRequest.publicKey error:error]; + + if (friendNumber == kOCTToxFriendNumberFailure) { + return NO; + } + + [self.dataSource managerSaveTox]; + + [[self.dataSource managerGetRealmManager] deleteObject:friendRequest]; + + return [self createFriendWithFriendNumber:friendNumber error:error]; +} + +- (void)removeFriendRequest:(OCTFriendRequest *)friendRequest +{ + NSParameterAssert(friendRequest); + + [[self.dataSource managerGetRealmManager] deleteObject:friendRequest]; +} + +- (BOOL)removeFriend:(OCTFriend *)friend error:(NSError **)error +{ + NSParameterAssert(friend); + + OCTTox *tox = [self.dataSource managerGetTox]; + + if (! [tox deleteFriendWithFriendNumber:friend.friendNumber error:error]) { + return NO; + } + + [self.dataSource managerSaveTox]; + + [[self.dataSource managerGetRealmManager] deleteObject:friend]; + + return YES; +} + +#pragma mark - Private category + +- (void)configure +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + OCTTox *tox = [self.dataSource managerGetTox]; + + [realmManager updateObjectsWithClass:[OCTFriend class] predicate:nil updateBlock:^(OCTFriend *friend) { + // Tox may change friendNumber after relaunch, resetting them. + friend.friendNumber = kOCTToxFriendNumberFailure; + }]; + + for (NSNumber *friendNumber in [tox friendsArray]) { + OCTToxFriendNumber number = [friendNumber intValue]; + NSError *error; + + NSString *publicKey = [tox publicKeyFromFriendNumber:number error:&error]; + + if (! publicKey) { + @throw [NSException exceptionWithName:@"Cannot find publicKey for existing friendNumber, Tox save data is broken" + reason:error.debugDescription + userInfo:nil]; + } + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"publicKey == %@", publicKey]; + RLMResults *results = [realmManager objectsWithClass:[OCTFriend class] predicate:predicate]; + + if (results.count == 0) { + // It seems that friend is in Tox but isn't in Realm. Let's add it. + [self createFriendWithFriendNumber:number error:nil]; + continue; + } + + OCTFriend *friend = [results firstObject]; + + // Reset some fields for friends. + [realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) { + theFriend.friendNumber = number; + theFriend.status = OCTToxUserStatusNone; + theFriend.isConnected = NO; + theFriend.connectionStatus = OCTToxConnectionStatusNone; + theFriend.isTyping = NO; + NSDate *dateOffline = [tox friendGetLastOnlineWithFriendNumber:number error:nil]; + theFriend.lastSeenOnlineInterval = [dateOffline timeIntervalSince1970]; + }]; + } + + // Remove all OCTFriend's which aren't bounded to tox. User cannot interact with them anyway. + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"friendNumber == %d", kOCTToxFriendNumberFailure]; + RLMResults *results = [realmManager objectsWithClass:[OCTFriend class] predicate:predicate]; + + for (OCTFriend *friend in results) { + [realmManager deleteObject:friend]; + } +} + +#pragma mark - OCTToxDelegate + +- (void)tox:(OCTTox *)tox friendRequestWithMessage:(NSString *)message publicKey:(NSString *)publicKey +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"publicKey == %@", publicKey]; + RLMResults *results = [realmManager objectsWithClass:[OCTFriendRequest class] predicate:predicate]; + if (results.count > 0) { + // friendRequest already exists + return; + } + + results = [realmManager objectsWithClass:[OCTFriend class] predicate:predicate]; + if (results.count > 0) { + // friend with such publicKey already exists + return; + } + + OCTFriendRequest *request = [OCTFriendRequest new]; + request.publicKey = publicKey; + request.message = message; + request.dateInterval = [[NSDate date] timeIntervalSince1970]; + + [realmManager addObject:request]; +} + +- (void)tox:(OCTTox *)tox friendNameUpdate:(NSString *)name friendNumber:(OCTToxFriendNumber)friendNumber +{ + [self.dataSource managerSaveTox]; + + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + + [realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) { + theFriend.name = name; + + if (name.length && [theFriend.nickname isEqualToString:theFriend.publicKey]) { + theFriend.nickname = name; + } + }]; +} + +- (void)tox:(OCTTox *)tox friendPushTokenUpdate:(NSString *)pushToken friendNumber:(OCTToxFriendNumber)friendNumber +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + + [realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) { + theFriend.pushToken = pushToken; + }]; +} + +- (void)tox:(OCTTox *)tox friendStatusMessageUpdate:(NSString *)statusMessage friendNumber:(OCTToxFriendNumber)friendNumber +{ + [self.dataSource managerSaveTox]; + + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + + [realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) { + theFriend.statusMessage = statusMessage; + }]; +} + +- (void)tox:(OCTTox *)tox friendStatusUpdate:(OCTToxUserStatus)status friendNumber:(OCTToxFriendNumber)friendNumber +{ + [self.dataSource managerSaveTox]; + + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + + [realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) { + theFriend.status = status; + }]; +} + +- (void)tox:(OCTTox *)tox friendIsTypingUpdate:(BOOL)isTyping friendNumber:(OCTToxFriendNumber)friendNumber +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + + [realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) { + theFriend.isTyping = isTyping; + }]; +} + +- (void)tox:(OCTTox *)tox friendSetMsgv3Capability:(BOOL)msgv3Capability friendNumber:(OCTToxFriendNumber)friendNumber +{ + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + + if (friend.msgv3Capability != msgv3Capability) + { + [realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) { + theFriend.msgv3Capability = msgv3Capability; + }]; + } +} + +- (void)tox:(OCTTox *)tox friendConnectionStatusChanged:(OCTToxConnectionStatus)status friendNumber:(OCTToxFriendNumber)friendNumber +{ + [self.dataSource managerSaveTox]; + + OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager]; + NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil]; + OCTFriend *friend = [realmManager friendWithPublicKey:publicKey]; + + [realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) { + + if ((status != OCTToxConnectionStatusNone) + && (theFriend.connectionStatus == OCTToxConnectionStatusNone)) + { + // Friend is coming online now + + OCTToxCapabilities f_caps = [tox friendGetCapabilitiesWithFriendNumber:friendNumber]; + OCTLogVerbose(@"f_caps=%lu", f_caps); + NSString* cap_string = [NSString stringWithFormat:@"%lu", f_caps]; + theFriend.capabilities2 = cap_string; + + NSString *token = [FIRMessaging messaging].FCMToken; + if (token.length > 0) + { + // HINT: prepend a dummy "A" char as placeholder for Tox Packet ID. + // it will be replaced in sendLosslessPacketWithFriendNumber by pktid + NSString *data = [NSString stringWithFormat:@"Ahttps://tox.zoff.xyz/toxfcm/fcm.php?id=%@&type=1", token]; + // NSLog(@"token push url=%@", data); + NSError *error; + + // HINT: pktid 181 is for sending push urls to friends + BOOL result = [tox sendLosslessPacketWithFriendNumber:friendNumber + pktid:181 + data:data + error:&error]; + } + } + theFriend.isConnected = (status != OCTToxConnectionStatusNone); + theFriend.connectionStatus = status; + + if (! theFriend.isConnected) { + // Friend is offline now + NSDate *dateOffline = [tox friendGetLastOnlineWithFriendNumber:friendNumber error:nil]; + NSTimeInterval timeSince = [dateOffline timeIntervalSince1970]; + theFriend.lastSeenOnlineInterval = timeSince; + } + }]; + + [[self.dataSource managerGetNotificationCenter] postNotificationName:kOCTFriendConnectionStatusChangeNotification object:friend]; +} + +#pragma mark - Private + +- (BOOL)createFriendWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)userError +{ + OCTTox *tox = [self.dataSource managerGetTox]; + NSError *error; + + OCTFriend *friend = [OCTFriend new]; + + friend.friendNumber = friendNumber; + + friend.publicKey = [tox publicKeyFromFriendNumber:friendNumber error:&error]; + if ([self checkForError:error andAssignTo:userError]) { + return NO; + } + + friend.name = [tox friendNameWithFriendNumber:friendNumber error:&error]; + if ([self checkForError:error andAssignTo:userError]) { + return NO; + } + + friend.statusMessage = [tox friendStatusMessageWithFriendNumber:friendNumber error:&error]; + if ([self checkForError:error andAssignTo:userError]) { + return NO; + } + + friend.status = [tox friendStatusWithFriendNumber:friendNumber error:&error]; + if ([self checkForError:error andAssignTo:userError]) { + return NO; + } + + friend.connectionStatus = [tox friendConnectionStatusWithFriendNumber:friendNumber error:&error]; + if ([self checkForError:error andAssignTo:userError]) { + return NO; + } + + NSDate *lastSeenOnline = [tox friendGetLastOnlineWithFriendNumber:friendNumber error:&error]; + friend.lastSeenOnlineInterval = [lastSeenOnline timeIntervalSince1970]; + if ([self checkForError:error andAssignTo:userError]) { + return NO; + } + + friend.isTyping = [tox isFriendTypingWithFriendNumber:friendNumber error:&error]; + if ([self checkForError:error andAssignTo:userError]) { + return NO; + } + + friend.isConnected = (friend.connectionStatus != OCTToxConnectionStatusNone); + friend.nickname = friend.name.length ? friend.name : friend.publicKey; + + [[self.dataSource managerGetRealmManager] addObject:friend]; + + return YES; +} + +- (BOOL)checkForError:(NSError *)toCheck andAssignTo:(NSError **)toAssign +{ + if (! toCheck) { + return NO; + } + + if (toAssign) { + *toAssign = toCheck; + } + + return YES; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerObjectsImpl.h b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerObjectsImpl.h new file mode 100644 index 0000000..e09a16a --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerObjectsImpl.h @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerObjects.h" +#import "OCTSubmanagerProtocol.h" + +@interface OCTSubmanagerObjectsImpl : NSObject + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerObjectsImpl.m b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerObjectsImpl.m new file mode 100644 index 0000000..3149181 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerObjectsImpl.m @@ -0,0 +1,103 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerObjectsImpl.h" +#import "OCTRealmManager.h" +#import "OCTFriend.h" +#import "OCTFriendRequest.h" +#import "OCTChat.h" +#import "OCTCall.h" +#import "OCTMessageAbstract.h" +#import "OCTSettingsStorageObject.h" + +@implementation OCTSubmanagerObjectsImpl +@synthesize dataSource = _dataSource; + +#pragma mark - Public + +- (void)setGenericSettingsData:(NSData *)data +{ + OCTRealmManager *manager = [self.dataSource managerGetRealmManager]; + + [manager updateObject:manager.settingsStorage withBlock:^(OCTSettingsStorageObject *object) { + object.genericSettingsData = data; + }]; +} + +- (NSData *)genericSettingsData +{ + OCTRealmManager *manager = [self.dataSource managerGetRealmManager]; + return manager.settingsStorage.genericSettingsData; +} + +- (RLMResults *)objectsForType:(OCTFetchRequestType)type predicate:(NSPredicate *)predicate +{ + OCTRealmManager *manager = [self.dataSource managerGetRealmManager]; + return [manager objectsWithClass:[self classForFetchRequestType:type] predicate:predicate]; +} + +- (OCTObject *)objectWithUniqueIdentifier:(NSString *)uniqueIdentifier forType:(OCTFetchRequestType)type +{ + OCTRealmManager *manager = [self.dataSource managerGetRealmManager]; + return [manager objectWithUniqueIdentifier:uniqueIdentifier class:[self classForFetchRequestType:type]]; +} + +#pragma mark - Friends + +- (void)changeFriend:(OCTFriend *)friend nickname:(NSString *)nickname +{ + OCTRealmManager *manager = [self.dataSource managerGetRealmManager]; + + [manager updateObject:friend withBlock:^(OCTFriend *theFriend) { + if (nickname.length) { + theFriend.nickname = nickname; + } + else if (theFriend.name.length) { + theFriend.nickname = theFriend.name; + } + else { + theFriend.nickname = theFriend.publicKey; + } + }]; +} + +#pragma mark - Chats + +- (void)changeChat:(OCTChat *)chat enteredText:(NSString *)enteredText +{ + OCTRealmManager *manager = [self.dataSource managerGetRealmManager]; + + [manager updateObject:chat withBlock:^(OCTChat *theChat) { + theChat.enteredText = enteredText; + }]; +} + +- (void)changeChat:(OCTChat *)chat lastReadDateInterval:(NSTimeInterval)lastReadDateInterval +{ + OCTRealmManager *manager = [self.dataSource managerGetRealmManager]; + + [manager updateObject:chat withBlock:^(OCTChat *theChat) { + theChat.lastReadDateInterval = lastReadDateInterval; + }]; +} + +#pragma mark - Private + +- (Class)classForFetchRequestType:(OCTFetchRequestType)type +{ + switch (type) { + case OCTFetchRequestTypeFriend: + return [OCTFriend class]; + case OCTFetchRequestTypeFriendRequest: + return [OCTFriendRequest class]; + case OCTFetchRequestTypeChat: + return [OCTChat class]; + case OCTFetchRequestTypeCall: + return [OCTCall class]; + case OCTFetchRequestTypeMessageAbstract: + return [OCTMessageAbstract class]; + } +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerProtocol.h b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerProtocol.h new file mode 100644 index 0000000..fc2c528 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerProtocol.h @@ -0,0 +1,16 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerDataSource.h" +#import "OCTToxDelegate.h" + +@protocol OCTSubmanagerProtocol + +@property (weak, nonatomic) id dataSource; + +@optional + +- (void)configure; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerUserImpl.h b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerUserImpl.h new file mode 100644 index 0000000..de2e16c --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerUserImpl.h @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerUser.h" +#import "OCTSubmanagerProtocol.h" + +@interface OCTSubmanagerUserImpl : NSObject + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerUserImpl.m b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerUserImpl.m new file mode 100644 index 0000000..e6b6034 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerUserImpl.m @@ -0,0 +1,132 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTSubmanagerUserImpl.h" +#import "OCTTox.h" +#import "OCTManagerConstants.h" +#import "OCTRealmManager.h" +#import "OCTSettingsStorageObject.h" + +@implementation OCTSubmanagerUserImpl +@synthesize delegate = _delegate; +@synthesize dataSource = _dataSource; + +#pragma mark - Properties + +- (OCTToxConnectionStatus)connectionStatus +{ + return [self.dataSource managerGetTox].connectionStatus; +} + +- (NSString *)userAddress +{ + return [self.dataSource managerGetTox].userAddress; +} + +- (OCTToxCapabilities)capabilities +{ + return [self.dataSource managerGetTox].capabilities; +} + +- (NSString *)publicKey +{ + return [self.dataSource managerGetTox].publicKey; +} + +#pragma mark - Public + +- (OCTToxNoSpam)nospam +{ + return [self.dataSource managerGetTox].nospam; +} + +- (void)setNospam:(OCTToxNoSpam)nospam +{ + [self.dataSource managerGetTox].nospam = nospam; + [self.dataSource managerSaveTox]; +} + +- (OCTToxUserStatus)userStatus +{ + return [self.dataSource managerGetTox].userStatus; +} + +- (void)setUserStatus:(OCTToxUserStatus)userStatus +{ + [self.dataSource managerGetTox].userStatus = userStatus; + [self.dataSource managerSaveTox]; +} + +- (BOOL)setUserName:(NSString *)name error:(NSError **)error +{ + if ([[self.dataSource managerGetTox] setNickname:name error:error]) { + [self.dataSource managerSaveTox]; + return YES; + } + + return NO; +} + +- (NSString *)userName +{ + return [[self.dataSource managerGetTox] userName]; +} + +- (BOOL)setUserStatusMessage:(NSString *)statusMessage error:(NSError **)error +{ + if ([[self.dataSource managerGetTox] setUserStatusMessage:statusMessage error:error]) { + [self.dataSource managerSaveTox]; + return YES; + } + + return NO; +} + +- (NSString *)userStatusMessage +{ + return [[self.dataSource managerGetTox] userStatusMessage]; +} + +- (BOOL)setUserAvatar:(NSData *)avatar error:(NSError **)error +{ + if (avatar && (avatar.length > kOCTManagerMaxAvatarSize)) { + if (error) { + *error = [NSError errorWithDomain:kOCTManagerErrorDomain + code:OCTSetUserAvatarErrorTooBig + userInfo:@{ + NSLocalizedDescriptionKey : @"Cannot set user avatar", + NSLocalizedFailureReasonErrorKey : @"Avatar is too big", + }]; + } + return NO; + } + + OCTRealmManager *realmManager = self.dataSource.managerGetRealmManager; + + [realmManager updateObject:realmManager.settingsStorage withBlock:^(OCTSettingsStorageObject *object) { + object.userAvatarData = avatar; + }]; + + [self.dataSource.managerGetNotificationCenter postNotificationName:kOCTUserAvatarWasUpdatedNotification object:nil]; + + return YES; +} + +- (NSData *)userAvatar +{ + return self.dataSource.managerGetRealmManager.settingsStorage.userAvatarData; +} + +#pragma mark - OCTToxDelegate + +- (void)tox:(OCTTox *)tox connectionStatus:(OCTToxConnectionStatus)connectionStatus +{ + if (connectionStatus != OCTToxConnectionStatusNone) { + [self.dataSource managerSaveTox]; + } + + [self.delegate submanagerUser:self connectionStatusUpdate:connectionStatus]; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTPixelBufferPool.h b/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTPixelBufferPool.h new file mode 100644 index 0000000..d91d0c9 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTPixelBufferPool.h @@ -0,0 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTToxAVConstants.h" +@import CoreVideo; + +/** + * This class helps with allocating and keeping CVPixelBuffers. + */ +@interface OCTPixelBufferPool : NSObject + +- (instancetype)initWithFormat:(OSType)format; + +/** + * Grab a pixel buffer from the pool. + * @param bufferRef Reference to the buffer ref. + * @return YES on success, NO otherwise. + */ +- (BOOL)createPixelBuffer:(CVPixelBufferRef *)bufferRef width:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTPixelBufferPool.m b/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTPixelBufferPool.m new file mode 100644 index 0000000..247b4d1 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTPixelBufferPool.m @@ -0,0 +1,103 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTPixelBufferPool.h" +#import "OCTLogging.h" + +@interface OCTPixelBufferPool () + +@property (nonatomic, assign) CVPixelBufferPoolRef pool; +@property (nonatomic, assign) OSType formatType; +@property (nonatomic, assign) OCTToxAVVideoWidth width; +@property (nonatomic, assign) OCTToxAVVideoHeight height; + +@end + +@implementation OCTPixelBufferPool + +#pragma mark - Lifecycle + +- (instancetype)initWithFormat:(OSType)format; +{ + self = [super init]; + + if (! self) { + return nil; + } + + _formatType = format; + + return self; +} + +- (void)dealloc +{ + if (self.pool) { + CFRelease(self.pool); + } +} + +#pragma mark - Public + +- (BOOL)createPixelBuffer:(CVPixelBufferRef *)bufferRef width:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height +{ + BOOL success = YES; + if (! self.pool) { + success = [self createPoolWithWidth:width height:height format:self.formatType]; + } + + if ((self.width != width) || (self.height != height)) { + success = [self createPoolWithWidth:width height:height format:self.formatType]; + } + + if (! success) { + return NO; + } + + return [self createPixelBuffer:bufferRef]; +} + +#pragma mark - Private + +- (BOOL)createPoolWithWidth:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height format:(OSType)format +{ + if (self.pool) { + CFRelease(self.pool); + } + + self.width = width; + self.height = height; + + NSDictionary *pixelBufferAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey : @{}, + (id)kCVPixelBufferHeightKey : @(height), + (id)kCVPixelBufferWidthKey : @(width), + (id)kCVPixelBufferPixelFormatTypeKey : @(format)}; + + CVReturn success = CVPixelBufferPoolCreate(kCFAllocatorDefault, + NULL, + (__bridge CFDictionaryRef)(pixelBufferAttributes), + &_pool); + + if (success != kCVReturnSuccess) { + OCTLogWarn(@"failed to create CVPixelBufferPool error:%d", success); + + } + + return (success == kCVReturnSuccess); +} + +- (BOOL)createPixelBuffer:(CVPixelBufferRef *)bufferRef +{ + CVReturn success = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, + self.pool, + bufferRef); + + if (success != kCVReturnSuccess) { + OCTLogWarn(@"Failed to create pixelBuffer error:%d", success); + } + + return (success == kCVReturnSuccess); +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTVideoEngine.h b/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTVideoEngine.h new file mode 100644 index 0000000..d307fa0 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTVideoEngine.h @@ -0,0 +1,118 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTView.h" + +#import "OCTToxAV.h" + +@interface OCTVideoEngine : NSObject + +@property (weak, nonatomic) OCTToxAV *toxav; + +/** + * Current friend number that video engine should + * process video data to and from. + */ +@property (nonatomic, assign) OCTToxFriendNumber friendNumber; + +/** + * This must be called prior to using the video session. + * @param error Pointer to error object. + * @return YES if successful, otherwise NO. + */ +- (BOOL)setupAndReturnError:(NSError **)error; + +/** + * Start sending video data. + * This will turn on processIncomingVideo to YES + */ +- (void)startSendingVideo; + +/** + * Stop sending video data. + * This will turn off processIncomingVideo to NO + */ +- (void)stopSendingVideo; + +/** + * Indicates if the video engine is sending video. + * @return YES if running, NO otherwise. + */ +- (BOOL)isSendingVideo; + +/** + * Generate a OCTView with the current incoming video feed. + */ +- (OCTView *)videoFeed; + +/** + * Layer of the preview video. + * Layer will be nil if videoSession is not running. + * @param completionBlock Block responsible for using the layer. This + * must not be nil. + */ +- (void)getVideoCallPreview:(void (^)(CALayer *layer))completionBlock; + +/** + * Provide video frames to video engine to process. + * @param width Width of the frame in pixels. + * @param height Height of the frame in pixels. + * @param yPlane + * @param uPlane + * @param vPlane Plane data. + * The size of plane data is derived from width and height where + * Y = MAX(width, abs(ystride)) * height, + * U = MAX(width/2, abs(ustride)) * (height/2) and + * V = MAX(width/2, abs(vstride)) * (height/2). + * @param yStride + * @param uStride + * @param vStride Strides data. Strides represent padding for each plane + * that may or may not be present. You must handle strides in + * your image processing code. Strides are negative if the + * image is bottom-up hence why you MUST abs() it when + * calculating plane buffer size. + * @param friendNumber The friend number of the friend who sent an audio frame. + * + */ +- (void)receiveVideoFrameWithWidth:(OCTToxAVVideoWidth)width + height:(OCTToxAVVideoHeight)height + yPlane:(OCTToxAVPlaneData *)yPlane + uPlane:(OCTToxAVPlaneData *)uPlane + vPlane:(OCTToxAVPlaneData *)vPlane + yStride:(OCTToxAVStrideData)yStride + uStride:(OCTToxAVStrideData)uStride + vStride:(OCTToxAVStrideData)vStride + friendNumber:(OCTToxFriendNumber)friendNumber; +@end + +#if ! TARGET_OS_IPHONE + +@interface OCTVideoEngine (MacDevice) + +/** + * Use a different camera for input. + * @param camera The camera's AVFoundation device ID. + * @param error Pointer to error object. + * @return YES on success, otherwise NO. + */ +- (BOOL)switchToCamera:(NSString *)camera error:(NSError **)error; + +@end + +#else + +@interface OCTVideoEngine (iOSDevice) + +/** + * Use a different camera for input. + * @param camera The camera's AVFoundation device ID. + * @param error Pointer to error object. + * @return YES on success, otherwise NO. + */ +- (BOOL)useFrontCamera:(BOOL)front error:(NSError **)error; + +@end + +#endif diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTVideoEngine.m b/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTVideoEngine.m new file mode 100644 index 0000000..332f9c3 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTVideoEngine.m @@ -0,0 +1,489 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTVideoEngine.h" +#import "OCTVideoView.h" +#import "OCTPixelBufferPool.h" +#import "OCTManagerConstants.h" +#import "OCTLogging.h" + +@import AVFoundation; + +static const OSType kPixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; + +@interface OCTVideoEngine () + +@property (nonatomic, strong) AVCaptureSession *captureSession; +@property (nonatomic, strong) AVCaptureVideoDataOutput *dataOutput; +@property (nonatomic, strong) dispatch_queue_t processingQueue; +@property (nonatomic, strong) OCTVideoView *videoView; +@property (nonatomic, weak) AVCaptureVideoPreviewLayer *previewLayer; +@property (nonatomic, assign) uint8_t *reusableUChromaPlane; +@property (nonatomic, assign) uint8_t *reusableVChromaPlane; +@property (nonatomic, assign) uint8_t *reusableYChromaPlane; +@property (strong, nonatomic) OCTPixelBufferPool *pixelPool; +@property (nonatomic, assign) NSUInteger sizeOfChromaPlanes; +@property (nonatomic, assign) NSUInteger sizeOfYPlane; + +@end + +@implementation OCTVideoEngine + +#pragma mark - Life cycle + +- (instancetype)init +{ + self = [super init]; + if (! self) { + return nil; + } + + OCTLogVerbose(@"init"); + + // Disabling captureSession for simulator due to bug in iOS 10. + // See https://forums.developer.apple.com/thread/62230 +#if ! TARGET_OS_SIMULATOR + _captureSession = [AVCaptureSession new]; + _captureSession.sessionPreset = AVCaptureSessionPresetMedium; + + if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset640x480]) { + _captureSession.sessionPreset = AVCaptureSessionPreset640x480; + } +#endif + + _dataOutput = [AVCaptureVideoDataOutput new]; + _processingQueue = dispatch_queue_create("me.dvor.objcTox.OCTVideoEngineQueue", NULL); + _pixelPool = [[OCTPixelBufferPool alloc] initWithFormat:kPixelFormat]; + + return self; +} + +- (void)dealloc +{ + if (self.reusableUChromaPlane) { + free(self.reusableUChromaPlane); + } + + if (self.reusableVChromaPlane) { + free(self.reusableVChromaPlane); + } + + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +#pragma mark - Public + +- (BOOL)setupAndReturnError:(NSError **)error +{ + OCTLogVerbose(@"setupAndReturnError"); +#if TARGET_OS_IPHONE + AVCaptureDevice *videoCaptureDevice = [self getDeviceForPosition:AVCaptureDevicePositionFront]; +#else + AVCaptureDevice *videoCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; +#endif + AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoCaptureDevice error:error]; + + if (! videoInput) { + return NO; + } + + if ([self.captureSession canAddInput:videoInput]) { + [self.captureSession addInput:videoInput]; + } + + self.dataOutput.alwaysDiscardsLateVideoFrames = YES; + self.dataOutput.videoSettings = @{ + (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kPixelFormat), + }; + [self.dataOutput setSampleBufferDelegate:self queue:self.processingQueue]; + + [self.captureSession addOutput:self.dataOutput]; + AVCaptureConnection *conn = [self.dataOutput connectionWithMediaType:AVMediaTypeVideo]; + + if (conn.supportsVideoOrientation) { + [self registerOrientationNotification]; + [self orientationChanged]; + } + + return YES; +} + +#if ! TARGET_OS_IPHONE + +- (BOOL)switchToCamera:(NSString *)camera error:(NSError **)error +{ + AVCaptureDevice *dev = nil; + + if (! camera) { + dev = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + } + else { + dev = [AVCaptureDevice deviceWithUniqueID:camera]; + } + + return [self actuallySetCamera:dev error:error]; +} + +#else + +- (BOOL)useFrontCamera:(BOOL)front error:(NSError **)error +{ + AVCaptureDevicePosition position = front ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack; + + AVCaptureDevice *dev = [self getDeviceForPosition:position]; + + return [self actuallySetCamera:dev error:error]; +} + +#endif + +- (BOOL)actuallySetCamera:(AVCaptureDevice *)dev error:(NSError **)error +{ + OCTLogVerbose(@"actuallySetCamera: %@", dev); + + NSArray *inputs = [self.captureSession inputs]; + + AVCaptureInput *current = [inputs firstObject]; + if ([current isKindOfClass:[AVCaptureDeviceInput class]]) { + AVCaptureDeviceInput *inputDevice = (AVCaptureDeviceInput *)current; + if ([inputDevice.device.uniqueID isEqualToString:dev.uniqueID]) { + return YES; + } + } + + for (AVCaptureInput *input in inputs) { + [self.captureSession removeInput:input]; + } + + AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:dev error:error]; + + if (! videoInput) { + return NO; + } + + if (! [self.captureSession canAddInput:videoInput]) { + return NO; + } + + [self.captureSession addInput:videoInput]; + + [self orientationChanged]; + + return YES; +} + +- (void)startSendingVideo +{ + OCTLogVerbose(@"startSendingVideo"); + + dispatch_async(self.processingQueue, ^{ + if ([self isSendingVideo]) { + return; + } + [self.captureSession startRunning]; + }); +} + +- (void)stopSendingVideo +{ + OCTLogVerbose(@"stopSendingVideo"); + + dispatch_async(self.processingQueue, ^{ + + if (! [self isSendingVideo]) { + return; + } + + [self.captureSession stopRunning]; + }); +} + +- (BOOL)isSendingVideo +{ + OCTLogVerbose(@"isSendingVideo"); + return self.captureSession.isRunning; +} + +- (void)getVideoCallPreview:(void (^)(CALayer *))completionBlock +{ + NSParameterAssert(completionBlock); + OCTLogVerbose(@"videoCallPreview"); + dispatch_async(self.processingQueue, ^{ + AVCaptureVideoPreviewLayer *previewLayer = self.previewLayer; + + if (! self.previewLayer) { + previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + completionBlock(previewLayer); + }); + + self.previewLayer = previewLayer; + }); +} + +- (OCTView *)videoFeed; +{ + OCTLogVerbose(@"videoFeed"); + + OCTVideoView *feed = self.videoView; + + if (! feed) { + feed = [OCTVideoView view]; + self.videoView = feed; + } + + return feed; +} + +- (void)receiveVideoFrameWithWidth:(OCTToxAVVideoWidth)width + height:(OCTToxAVVideoHeight)height + yPlane:(OCTToxAVPlaneData *)yPlane + uPlane:(OCTToxAVPlaneData *)uPlane + vPlane:(OCTToxAVPlaneData *)vPlane + yStride:(OCTToxAVStrideData)yStride + uStride:(OCTToxAVStrideData)uStride + vStride:(OCTToxAVStrideData)vStride + friendNumber:(OCTToxFriendNumber)friendNumber +{ + + if (! self.videoView) { + return; + } + + if (! yPlane) { + return; + } + + if (! uPlane) { + return; + } + + if (! vPlane) { + return; + } + + + size_t yBytesPerRow = MIN(width, abs(yStride)); + size_t uvBytesPerRow = MIN(width / 2, abs(uStride)); + + /** + * Create pixel buffers and copy YUV planes over + */ + CVPixelBufferRef bufferRef = NULL; + + if (! [self.pixelPool createPixelBuffer:&bufferRef width:width height:height]) { + return; + } + + CVPixelBufferLockBaseAddress(bufferRef, 0); + + OCTToxAVPlaneData *ySource = yPlane; + // if stride is negative, start reading from the left of the last row + if (yStride < 0) { + ySource = ySource + ((-yStride) * (height - 1)); + } + + uint8_t *yDestinationPlane = CVPixelBufferGetBaseAddressOfPlane(bufferRef, 0); + size_t yDestinationStride = CVPixelBufferGetBytesPerRowOfPlane(bufferRef, 0); + + /* Copy yPlane data */ + for (size_t yHeight = 0; yHeight < height; yHeight++) { + memcpy(yDestinationPlane, ySource, yBytesPerRow); + ySource += yStride; + yDestinationPlane += yDestinationStride; + } + + /* Interweave U and V */ + uint8_t *uvDestinationPlane = CVPixelBufferGetBaseAddressOfPlane(bufferRef, 1); + size_t uvDestinationStride = CVPixelBufferGetBytesPerRowOfPlane(bufferRef, 1); + + OCTToxAVPlaneData *uSource = uPlane; + if (uStride < 0) { + uSource = uSource + ((-uStride) * ((height / 2) - 1)); + } + + OCTToxAVPlaneData *vSource = vPlane; + if (vStride < 0) { + vSource = vSource + ((-vStride) * ((height / 2) - 1)); + } + + for (size_t yHeight = 0; yHeight < height / 2; yHeight++) { + for (size_t index = 0; index < uvBytesPerRow; index++) { + uvDestinationPlane[index * 2] = uSource[index]; + uvDestinationPlane[(index * 2) + 1] = vSource[index]; + } + uvDestinationPlane += uvDestinationStride; + uSource += uStride; + vSource += vStride; + } + + CVPixelBufferUnlockBaseAddress(bufferRef, 0); + + dispatch_async(self.processingQueue, ^{ + /* Create Core Image */ + CIImage *coreImage = [CIImage imageWithCVPixelBuffer:bufferRef]; + + CVPixelBufferRelease(bufferRef); + + self.videoView.image = coreImage; + }); +} + +#pragma mark - Buffer Delegate + +- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection +{ + + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + + if (! imageBuffer) { + return; + } + + CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly); + + size_t yHeight = CVPixelBufferGetHeightOfPlane(imageBuffer, 0); + size_t yBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0); + size_t yStride = MAX(CVPixelBufferGetWidthOfPlane(imageBuffer, 0), yBytesPerRow); + + size_t uvHeight = CVPixelBufferGetHeightOfPlane(imageBuffer, 1); + size_t uvBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1); + size_t uvStride = MAX(CVPixelBufferGetWidthOfPlane(imageBuffer, 1), uvBytesPerRow); + + size_t ySize = yBytesPerRow * yHeight; + size_t numberOfElementsForChroma = uvBytesPerRow * uvHeight / 2; + + /** + * Recreate the buffers if the original ones are too small + */ + if (numberOfElementsForChroma > self.sizeOfChromaPlanes) { + + if (self.reusableUChromaPlane) { + free(self.reusableUChromaPlane); + } + + if (self.reusableVChromaPlane) { + free(self.reusableVChromaPlane); + } + + self.reusableUChromaPlane = malloc(numberOfElementsForChroma * sizeof(OCTToxAVPlaneData)); + self.reusableVChromaPlane = malloc(numberOfElementsForChroma * sizeof(OCTToxAVPlaneData)); + + self.sizeOfChromaPlanes = numberOfElementsForChroma; + } + + if (ySize > self.sizeOfYPlane) { + if (self.reusableYChromaPlane) { + free(self.reusableYChromaPlane); + } + self.reusableYChromaPlane = malloc(ySize * sizeof(OCTToxAVPlaneData)); + self.sizeOfYPlane = ySize; + } + + /** + * Copy the Y plane data while skipping stride + */ + OCTToxAVPlaneData *yPlane = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0); + uint8_t *yDestination = self.reusableYChromaPlane; + for (size_t i = 0; i < yHeight; i++) { + memcpy(yDestination, yPlane, yBytesPerRow); + yPlane += yStride; + yDestination += yBytesPerRow; + } + + /** + * Deinterleaved the UV [uvuvuvuv] planes and place them to in the reusable arrays + */ + OCTToxAVPlaneData *uvPlane = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1); + uint8_t *uDestination = self.reusableUChromaPlane; + uint8_t *vDestination = self.reusableVChromaPlane; + + for (size_t height = 0; height < uvHeight; height++) { + + for (size_t i = 0; i < uvBytesPerRow; i += 2) { + uDestination[i / 2] = uvPlane[i]; + vDestination[i / 2] = uvPlane[i + 1]; + } + + uvPlane += uvStride; + uDestination += uvBytesPerRow / 2; + vDestination += uvBytesPerRow / 2; + + } + + CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + uDestination = nil; + vDestination = nil; + + NSError *error; + if (! [self.toxav sendVideoFrametoFriend:self.friendNumber + width:(OCTToxAVVideoWidth)yBytesPerRow + height:(OCTToxAVVideoHeight)yHeight + yPlane:self.reusableYChromaPlane + uPlane:self.reusableUChromaPlane + vPlane:self.reusableVChromaPlane + error:&error]) { + OCTLogWarn(@"error:%@ width:%zu height:%zu", error, yBytesPerRow, yHeight); + } +} + +#pragma mark - Private + +- (AVCaptureDevice *)getDeviceForPosition:(AVCaptureDevicePosition)position +{ + OCTLogVerbose(@"getDeviceForPosition"); + + NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + for (AVCaptureDevice *device in devices) { + if ([device position] == position) { + return device; + } + } + + return nil; +} + +- (void)registerOrientationNotification +{ +#if TARGET_OS_IPHONE + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(orientationChanged) + name:UIDeviceOrientationDidChangeNotification + object:nil]; +#endif +} + +- (void)orientationChanged +{ +#if TARGET_OS_IPHONE + UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation; + AVCaptureConnection *conn = [self.dataOutput connectionWithMediaType:AVMediaTypeVideo]; + AVCaptureVideoOrientation orientation; + + switch (deviceOrientation) { + case UIInterfaceOrientationPortraitUpsideDown: + orientation = AVCaptureVideoOrientationPortraitUpsideDown; + break; + case UIDeviceOrientationPortrait: + orientation = AVCaptureVideoOrientationPortrait; + break; + /* Landscapes are reversed, otherwise for some reason the video will be upside down */ + case UIDeviceOrientationLandscapeLeft: + orientation = AVCaptureVideoOrientationLandscapeRight; + break; + case UIDeviceOrientationLandscapeRight: + orientation = AVCaptureVideoOrientationLandscapeLeft; + break; + default: + return; + } + + conn.videoOrientation = orientation; + self.previewLayer.connection.videoOrientation = orientation; +#endif +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTVideoView.h b/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTVideoView.h new file mode 100644 index 0000000..92884c5 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTVideoView.h @@ -0,0 +1,22 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTView.h" + +@import GLKit; + +#if TARGET_OS_IPHONE +@interface OCTVideoView : GLKView +#else +@interface OCTVideoView : NSOpenGLView +#endif + +@property (strong, nonatomic) CIImage *image; + +/** + * Allocs and calls the platform-specific initializers. + */ ++ (instancetype)view; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTVideoView.m b/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTVideoView.m new file mode 100644 index 0000000..5527774 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Manager/Video/OCTVideoView.m @@ -0,0 +1,100 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTVideoView.h" +#import "OCTManagerConstants.h" +#import "OCTLogging.h" +@import Foundation; +@import AVFoundation; + +@interface OCTVideoView () + +@property (strong, nonatomic) CIContext *coreImageContext; + +@end + +@implementation OCTVideoView + ++ (instancetype)view +{ +#if TARGET_OS_IPHONE + OCTVideoView *videoView = [[self alloc] initWithFrame:CGRectZero]; +#else + OCTVideoView *videoView = [[self alloc] initWithFrame:CGRectZero pixelFormat:[self defaultPixelFormat]]; +#endif + [videoView finishInitializing]; + return videoView; +} + +- (void)dealloc +{ + OCTLogVerbose(@"dealloc"); +} + +- (void)finishInitializing +{ +#if TARGET_OS_IPHONE + __weak OCTVideoView *weakSelf = self; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + OCTVideoView *strongSelf = weakSelf; + strongSelf.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + strongSelf.coreImageContext = [CIContext contextWithEAGLContext:strongSelf.context]; + }); + + self.enableSetNeedsDisplay = NO; +#endif +} + +- (void)setImage:(CIImage *)image +{ + _image = image; +#if TARGET_OS_IPHONE + [self display]; +#else + [self setNeedsDisplay:YES]; +#endif +} + +#if ! TARGET_OS_IPHONE +// OS X: we need to correct the viewport when the view size changes +- (void)reshape +{ + glViewport(0, 0, self.bounds.size.width, self.bounds.size.height); +} +#endif + +- (void)drawRect:(CGRect)rect +{ +#if TARGET_OS_IPHONE + if (self.image) { + + glClearColor(0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + CGRect destRect = AVMakeRectWithAspectRatioInsideRect(self.image.extent.size, rect); + + float screenscale = self.window.screen.scale; + + destRect = CGRectApplyAffineTransform(destRect, CGAffineTransformMakeScale(screenscale, screenscale)); + + [self.coreImageContext drawImage:self.image inRect:destRect fromRect:self.image.extent]; + } +#else + [self.openGLContext makeCurrentContext]; + + if (self.image) { + CIContext *ctx = [CIContext contextWithCGLContext:self.openGLContext.CGLContextObj pixelFormat:self.openGLContext.pixelFormat.CGLPixelFormatObj colorSpace:nil options:nil]; + // The GL coordinate system goes from -1 to 1 on all axes by default. + // We didn't set a matrix so use that instead of bounds. + [ctx drawImage:self.image inRect:(CGRect) {-1, -1, 2, 2} fromRect:self.image.extent]; + } + else { + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + } + glFlush(); +#endif +} +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTLogging.h b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTLogging.h new file mode 100644 index 0000000..d3000fd --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTLogging.h @@ -0,0 +1,25 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "DDLog.h" +#undef LOG_LEVEL_DEF +#define LOG_LEVEL_DEF LOG_LEVEL_VERBOSE + +#define OCTLogError(frmt, ...) DDLogError((@"<%@ %p> " frmt), [self class], self, ## __VA_ARGS__) +#define OCTLogWarn(frmt, ...) DDLogWarn((@"<%@ %p> " frmt), [self class], self, ## __VA_ARGS__) +#define OCTLogInfo(frmt, ...) DDLogInfo((@"<%@ %p> " frmt), [self class], self, ## __VA_ARGS__) +#define OCTLogDebug(frmt, ...) DDLogDebug((@"<%@ %p> " frmt), [self class], self, ## __VA_ARGS__) +#define OCTLogVerbose(frmt, ...) DDLogVerbose((@"<%@ %p> " frmt), [self class], self, ## __VA_ARGS__) + +#define OCTLogCError(frmt, obj, ...) DDLogCError((@"<%@ %p> " frmt), [obj class], obj, ## __VA_ARGS__) +#define OCTLogCWarn(frmt, obj, ...) DDLogCWarn((@"<%@ %p> " frmt), [obj class], obj, ## __VA_ARGS__) +#define OCTLogCInfo(frmt, obj, ...) DDLogCInfo((@"<%@ %p> " frmt), [obj class], obj, ## __VA_ARGS__) +#define OCTLogCDebug(frmt, obj, ...) DDLogCDebug((@"<%@ %p> " frmt), [obj class], obj, ## __VA_ARGS__) +#define OCTLogCVerbose(frmt, obj, ...) DDLogCVerbose((@"<%@ %p> " frmt), [obj class], obj, ## __VA_ARGS__) + +#define OCTLogCCError(frmt, ...) DDLogCError((frmt), ## __VA_ARGS__) +#define OCTLogCCWarn(frmt, ...) DDLogCWarn((frmt), ## __VA_ARGS__) +#define OCTLogCCInfo(frmt, ...) DDLogCInfo((frmt), ## __VA_ARGS__) +#define OCTLogCCDebug(frmt, ...) DDLogCDebug((frmt), ## __VA_ARGS__) +#define OCTLogCCVerbose(frmt, ...) DDLogCVerbose((frmt), ## __VA_ARGS__) diff --git a/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTTox+Private.h b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTTox+Private.h new file mode 100644 index 0000000..ee6f321 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTTox+Private.h @@ -0,0 +1,63 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTTox.h" +#import + +/** + * Tox functions + */ +extern void (*_tox_self_get_public_key)(const Tox *tox, uint8_t *public_key); + +/** + * Callbacks + */ +tox_log_cb logCallback; +tox_self_connection_status_cb connectionStatusCallback; +tox_friend_name_cb friendNameCallback; +tox_friend_status_message_cb friendStatusMessageCallback; +tox_friend_status_cb friendStatusCallback; +tox_friend_connection_status_cb friendConnectionStatusCallback; +tox_friend_typing_cb friendTypingCallback; +tox_friend_read_receipt_cb friendReadReceiptCallback; +tox_friend_request_cb friendRequestCallback; +tox_friend_message_cb friendMessageCallback; +tox_friend_lossless_packet_cb friendLosslessPacketCallback; +tox_file_recv_control_cb fileReceiveControlCallback; +tox_file_chunk_request_cb fileChunkRequestCallback; +tox_file_recv_cb fileReceiveCallback; +tox_file_recv_chunk_cb fileReceiveChunkCallback; + +@interface OCTTox (Private) + +@property (assign, nonatomic) Tox *tox; + +- (OCTToxUserStatus)userStatusFromCUserStatus:(TOX_USER_STATUS)cStatus; +- (OCTToxConnectionStatus)userConnectionStatusFromCUserStatus:(TOX_CONNECTION)cStatus; +- (OCTToxMessageType)messageTypeFromCMessageType:(TOX_MESSAGE_TYPE)cType; +- (OCTToxFileControl)fileControlFromCFileControl:(TOX_FILE_CONTROL)cControl; +- (BOOL)fillError:(NSError **)error withCErrorInit:(TOX_ERR_NEW)cError; +- (BOOL)fillError:(NSError **)error withCErrorBootstrap:(TOX_ERR_BOOTSTRAP)cError; +- (BOOL)fillError:(NSError **)error withCErrorFriendAdd:(TOX_ERR_FRIEND_ADD)cError; +- (BOOL)fillError:(NSError **)error withCErrorFriendDelete:(TOX_ERR_FRIEND_DELETE)cError; +- (BOOL)fillError:(NSError **)error withCErrorFriendByPublicKey:(TOX_ERR_FRIEND_BY_PUBLIC_KEY)cError; +- (BOOL)fillError:(NSError **)error withCErrorFriendGetPublicKey:(TOX_ERR_FRIEND_GET_PUBLIC_KEY)cError; +- (BOOL)fillError:(NSError **)error withCErrorSetInfo:(TOX_ERR_SET_INFO)cError; +- (BOOL)fillError:(NSError **)error withCErrorFriendGetLastOnline:(TOX_ERR_FRIEND_GET_LAST_ONLINE)cError; +- (BOOL)fillError:(NSError **)error withCErrorFriendQuery:(TOX_ERR_FRIEND_QUERY)cError; +- (BOOL)fillError:(NSError **)error withCErrorSetTyping:(TOX_ERR_SET_TYPING)cError; +- (BOOL)fillError:(NSError **)error withCErrorFriendSendMessage:(TOX_ERR_FRIEND_SEND_MESSAGE)cError; +- (BOOL)fillError:(NSError **)error withCErrorFileControl:(TOX_ERR_FILE_CONTROL)cError; +- (BOOL)fillError:(NSError **)error withCErrorFileSeek:(TOX_ERR_FILE_SEEK)cError; +- (BOOL)fillError:(NSError **)error withCErrorFileGet:(TOX_ERR_FILE_GET)cError; +- (BOOL)fillError:(NSError **)error withCErrorFileSend:(TOX_ERR_FILE_SEND)cError; +- (BOOL)fillError:(NSError **)error withCErrorFileSendChunk:(TOX_ERR_FILE_SEND_CHUNK)cError; ++ (NSError *)createErrorWithCode:(NSUInteger)code + description:(NSString *)description + failureReason:(NSString *)failureReason; +- (struct Tox_Options) cToxOptionsFromOptions:(OCTToxOptions *)options; ++ (NSString *)binToHexString:(uint8_t *)bin length:(NSUInteger)length; ++ (uint8_t *)hexStringToBin:(NSString *)string; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTTox.m b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTTox.m new file mode 100644 index 0000000..908d54e --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTTox.m @@ -0,0 +1,2250 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTTox+Private.h" +#import "OCTToxOptions+Private.h" +#import "OCTLogging.h" + +void (*_tox_self_get_public_key)(const Tox *tox, uint8_t *public_key); + +@interface OCTTox () + +@property (assign, nonatomic) Tox *tox; + +@property (strong, nonatomic) dispatch_source_t timer; +@property (assign, nonatomic) uint64_t previousIterate; + +@end + +@implementation OCTTox + +static long long last_check_time = 0; +static long long TWO_MIN_IN_MILLIS = (2 * 60 * 1000); // 2 minutes in milliseconds + +#pragma mark - Class methods + ++ (NSString *)version +{ + return [NSString stringWithFormat:@"%lu.%lu.%lu", + (unsigned long)[self versionMajor], (unsigned long)[self versionMinor], (unsigned long)[self versionPatch]]; +} + ++ (NSUInteger)versionMajor +{ + return tox_version_major(); +} + ++ (NSUInteger)versionMinor +{ + return tox_version_minor(); +} + ++ (NSUInteger)versionPatch +{ + return tox_version_patch(); +} + +#pragma mark - Lifecycle + +- (instancetype)initWithOptions:(OCTToxOptions *)options savedData:(NSData *)data error:(NSError **)error +{ + NSParameterAssert(options); + + self = [super init]; + + OCTLogVerbose(@"OCTTox: loading with options %@", options); + + if (data) { + OCTLogVerbose(@"loading from data of length %lu", (unsigned long)data.length); + tox_options_set_savedata_type(options.options, TOX_SAVEDATA_TYPE_TOX_SAVE); + tox_options_set_savedata_data(options.options, data.bytes, data.length); + } + else { + tox_options_set_savedata_type(options.options, TOX_SAVEDATA_TYPE_NONE); + } + + tox_options_set_log_callback(options.options, logCallback); + + TOX_ERR_NEW cError; + + _tox = tox_new(options.options, &cError); + + [self fillError:error withCErrorInit:cError]; + + if (! _tox) { + return nil; + } + + [self setupCFunctions]; + [self setupCallbacks]; + + return self; +} + +- (void)dealloc +{ + [self stop]; + + if (self.tox) { + tox_kill(self.tox); + } + + OCTLogVerbose(@"dealloc called, tox killed"); +} + +- (NSData *)save +{ + OCTLogVerbose(@"saving..."); + + size_t size = tox_get_savedata_size(self.tox); + uint8_t *cData = malloc(size); + + tox_get_savedata(self.tox, cData); + + NSData *data = [NSData dataWithBytes:cData length:size]; + free(cData); + + OCTLogInfo(@"saved to data with length %lu", (unsigned long)data.length); + + return data; +} + +- (void)start +{ + OCTLogVerbose(@"start method called"); + + @synchronized(self) { + if (self.timer) { + OCTLogWarn(@"already started"); + return; + } + + dispatch_queue_t queue = dispatch_queue_create("me.dvor.objcTox.OCTToxQueue", NULL); + self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + + [self updateTimerIntervalIfNeeded]; + + // HINT: prevent bootstrapping here on startup. so add 2 minutes grace period + last_check_time = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0); + last_check_time = last_check_time + TWO_MIN_IN_MILLIS; + + __weak OCTTox *weakSelf = self; + dispatch_source_set_event_handler(self.timer, ^{ + OCTTox *strongSelf = weakSelf; + if (! strongSelf) { + return; + } + + tox_iterate(strongSelf.tox, (__bridge void *)self); + + long long current_time = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0); + + if (current_time > (last_check_time + (TWO_MIN_IN_MILLIS))) { + last_check_time = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0); + int cstatus = tox_self_get_connection_status(strongSelf.tox); + + OCTLogInfo(@"Tox checking online status: %d", cstatus); + + if (cstatus == 0) { + OCTLogInfo(@"Tox offline for a long time, bootstrapping again ..."); + + uint8_t *key_bin = hex_to_bin("CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707", + (TOX_PUBLIC_KEY_SIZE * 2)); + + if (key_bin != NULL) { + // ------------------------------------------------------------- + // HINT: fix me. how to access OCTSubmanagerBootstrapImpl here? + // add a hardcoded node to least make it come online + // after a long period of being offline. + // ------------------------------------------------------------- + tox_add_tcp_relay(strongSelf.tox, "46.101.197.175", 33445, key_bin, NULL); + tox_bootstrap(strongSelf.tox, "46.101.197.175", 33445, key_bin, NULL); + + tox_add_tcp_relay(strongSelf.tox, "2a03:b0c0:3:d0::ac:5001", 33445, key_bin, NULL); + tox_bootstrap(strongSelf.tox, "2a03:b0c0:3:d0::ac:5001", 33445, key_bin, NULL); + + OCTLogInfo(@"Tox offline for a long time, bootstrapping DONE"); + free(key_bin); + } + } + } + + [strongSelf updateTimerIntervalIfNeeded]; + }); + + dispatch_resume(self.timer); + } + + OCTLogInfo(@"started"); +} + +- (void)stop +{ + OCTLogVerbose(@"stop method called"); + + @synchronized(self) { + if (! self.timer) { + OCTLogWarn(@"tox isn't running, nothing to stop"); + return; + } + + dispatch_source_cancel(self.timer); + self.timer = nil; + } + + OCTLogInfo(@"stopped"); +} + +#pragma mark - Properties + +- (OCTToxConnectionStatus)connectionStatus +{ + return [self userConnectionStatusFromCUserStatus:tox_self_get_connection_status(self.tox)]; +} + +- (OCTToxCapabilities)capabilities +{ + OCTLogVerbose(@"get capabilities"); + + return tox_self_get_capabilities(); +} + +- (NSString *)userAddress +{ + OCTLogVerbose(@"get userAddress"); + + const NSUInteger length = TOX_ADDRESS_SIZE; + uint8_t *cAddress = malloc(length); + + tox_self_get_address(self.tox, cAddress); + + if (! cAddress) { + return nil; + } + + NSString *address = [OCTTox binToHexString:cAddress length:length]; + + free(cAddress); + + return address; +} + +- (NSString *)publicKey +{ + OCTLogVerbose(@"get publicKey"); + + uint8_t *cPublicKey = malloc(TOX_PUBLIC_KEY_SIZE); + + _tox_self_get_public_key(self.tox, cPublicKey); + + NSString *publicKey = [OCTTox binToHexString:cPublicKey length:TOX_PUBLIC_KEY_SIZE]; + free(cPublicKey); + + return publicKey; +} + +- (NSString *)secretKey +{ + OCTLogVerbose(@"get secretKey"); + + uint8_t *cSecretKey = malloc(TOX_SECRET_KEY_SIZE); + + tox_self_get_secret_key(self.tox, cSecretKey); + + NSString *secretKey = [OCTTox binToHexString:cSecretKey length:TOX_SECRET_KEY_SIZE]; + free(cSecretKey); + + return secretKey; +} + +- (void)setNospam:(OCTToxNoSpam)nospam +{ + OCTLogVerbose(@"set nospam"); + tox_self_set_nospam(self.tox, nospam); +} + +- (OCTToxNoSpam)nospam +{ + OCTLogVerbose(@"get nospam"); + return tox_self_get_nospam(self.tox); +} + +- (void)setUserStatus:(OCTToxUserStatus)status +{ + TOX_USER_STATUS cStatus = TOX_USER_STATUS_NONE; + + switch (status) { + case OCTToxUserStatusNone: + cStatus = TOX_USER_STATUS_NONE; + break; + case OCTToxUserStatusAway: + cStatus = TOX_USER_STATUS_AWAY; + break; + case OCTToxUserStatusBusy: + cStatus = TOX_USER_STATUS_BUSY; + break; + } + + tox_self_set_status(self.tox, cStatus); + + OCTLogInfo(@"set user status to %lu", (unsigned long)status); +} + +- (OCTToxUserStatus)userStatus +{ + return [self userStatusFromCUserStatus:tox_self_get_status(self.tox)]; +} + +#pragma mark - Methods + +/* + * Converts an ASCII character in hexadecimal (lower or upper case) into the corresponding decimal value. + * + * Returns decimal value on success. + * Returns -1 on failure. + */ +int char_to_int(char c) +{ + if (c >= '0' && c <= '9') + { + return c - '0'; + } + + if (c >= 'A' && c <= 'F') + { + return 10 + c - 'A'; + } + + if (c >= 'a' && c <= 'f') + { + return 10 + c - 'a'; + } + + return -1; +} + +/* + * Converts a hexidecimal string of length hex_string_len to binary format and puts the result in output. + * output_size must be exactly half of hex_string_len. + * + * Returns (uint8_t *) on success. the caller must free the buffer after use. + * Returns NULL on failure. + */ +uint8_t *hex_to_bin(const char *hex_string_buffer, size_t hex_string_len) +{ + if ((!hex_string_buffer) || (hex_string_len < 2)) + { + return NULL; + } + + if ((hex_string_len % 2) != 0) + { + return NULL; + } + + size_t len_bin = (hex_string_len / 2); + uint8_t *val = calloc(1, len_bin); + + for (size_t i = 0; i < len_bin; i++) + { + val[i] = (16 * char_to_int(hex_string_buffer[2 * i])) + (char_to_int(hex_string_buffer[2 * i + 1])); + } + + return val; +} + +/* + * Converts byte buffer into a hexidecimal string. + * + * Returns 0 on success. the caller must must provide a buffer with enough space to hold the hex string. + * Returns -1 on failure. + */ +int bin_to_hex(const char *bin_id, size_t bin_id_size, char *output) +{ + if ((!output) || (!bin_id) || (bin_id_size < 1)) + { + return -1; + } + + size_t i; + + for (i = 0; i < bin_id_size; i++) + { + snprintf(&output[i * 2], ((bin_id_size * 2) + 1) - (i * 2), "%02X", bin_id[i] & 0xff); + } + + return 0; +} + +size_t xnet_pack_u16(uint8_t *bytes, uint16_t v) +{ + bytes[0] = (v >> 8) & 0xff; + bytes[1] = v & 0xff; + return sizeof(v); +} + +size_t xnet_pack_u32(uint8_t *bytes, uint32_t v) +{ + uint8_t *p = bytes; + p += xnet_pack_u16(p, (v >> 16) & 0xffff); + p += xnet_pack_u16(p, v & 0xffff); + return p - bytes; +} + +size_t xnet_unpack_u16(const uint8_t *bytes, uint16_t *v) +{ + uint8_t hi = bytes[0]; + uint8_t lo = bytes[1]; + *v = ((uint16_t)hi << 8) | lo; + return sizeof(*v); +} + +size_t xnet_unpack_u32(const uint8_t *bytes, uint32_t *v) +{ + const uint8_t *p = bytes; + uint16_t hi; + uint16_t lo; + p += xnet_unpack_u16(p, &hi); + p += xnet_unpack_u16(p, &lo); + *v = ((uint32_t)hi << 16) | lo; + return p - bytes; +} + +- (BOOL)bootstrapFromHost:(NSString *)host port:(OCTToxPort)port publicKey:(NSString *)publicKey error:(NSError **)error +{ + NSParameterAssert(host); + NSParameterAssert(publicKey); + + OCTLogInfo(@"bootstrap with host %@ port %d publicKey %@", host, port, publicKey); + + const char *cAddress = host.UTF8String; + uint8_t *cPublicKey = [OCTTox hexStringToBin:publicKey]; + + TOX_ERR_BOOTSTRAP cError; + + bool result = tox_bootstrap(self.tox, cAddress, port, cPublicKey, &cError); + + [self fillError:error withCErrorBootstrap:cError]; + + free(cPublicKey); + + return (BOOL)result; +} + +- (BOOL)addTCPRelayWithHost:(NSString *)host port:(OCTToxPort)port publicKey:(NSString *)publicKey error:(NSError **)error +{ + NSParameterAssert(host); + NSParameterAssert(publicKey); + + OCTLogInfo(@"add TCP relay with host %@ port %d publicKey %@", host, port, publicKey); + + const char *cAddress = host.UTF8String; + uint8_t *cPublicKey = [OCTTox hexStringToBin:publicKey]; + + TOX_ERR_BOOTSTRAP cError; + + bool result = tox_add_tcp_relay(self.tox, cAddress, port, cPublicKey, &cError); + + [self fillError:error withCErrorBootstrap:cError]; + + free(cPublicKey); + + return (BOOL)result; +} + +- (OCTToxFriendNumber)addFriendWithAddress:(NSString *)address message:(NSString *)message error:(NSError **)error +{ + NSParameterAssert(address); + NSParameterAssert(message); + NSAssert(address.length == kOCTToxAddressLength, @"Address must be kOCTToxAddressLength length"); + + OCTLogVerbose(@"add friend with address.length %lu, message.length %lu", (unsigned long)address.length, (unsigned long)message.length); + + uint8_t *cAddress = [OCTTox hexStringToBin:address]; + const char *cMessage = [message cStringUsingEncoding:NSUTF8StringEncoding]; + size_t length = [message lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + TOX_ERR_FRIEND_ADD cError; + + OCTToxFriendNumber result = tox_friend_add(self.tox, cAddress, (const uint8_t *)cMessage, length, &cError); + + free(cAddress); + + [self fillError:error withCErrorFriendAdd:cError]; + + return result; +} + +- (OCTToxFriendNumber)addFriendWithNoRequestWithPublicKey:(NSString *)publicKey error:(NSError **)error +{ + NSParameterAssert(publicKey); + NSAssert(publicKey.length == kOCTToxPublicKeyLength, @"Public key must be kOCTToxPublicKeyLength length"); + + OCTLogVerbose(@"add friend with no request and publicKey.length %lu", (unsigned long)publicKey.length); + + uint8_t *cPublicKey = [OCTTox hexStringToBin:publicKey]; + + TOX_ERR_FRIEND_ADD cError; + + OCTToxFriendNumber result = tox_friend_add_norequest(self.tox, cPublicKey, &cError); + + free(cPublicKey); + + [self fillError:error withCErrorFriendAdd:cError]; + + return result; +} + +- (BOOL)deleteFriendWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + TOX_ERR_FRIEND_DELETE cError; + + bool result = tox_friend_delete(self.tox, friendNumber, &cError); + + [self fillError:error withCErrorFriendDelete:cError]; + + OCTLogVerbose(@"deleting friend with friendNumber %d, result %d", friendNumber, (result == 0)); + + return (BOOL)result; +} + +- (OCTToxFriendNumber)friendNumberWithPublicKey:(NSString *)publicKey error:(NSError **)error +{ + NSParameterAssert(publicKey); + NSAssert(publicKey.length == kOCTToxPublicKeyLength, @"Public key must be kOCTToxPublicKeyLength length"); + + OCTLogVerbose(@"get friend number with publicKey.length %lu", (unsigned long)publicKey.length); + + uint8_t *cPublicKey = [OCTTox hexStringToBin:publicKey]; + + TOX_ERR_FRIEND_BY_PUBLIC_KEY cError; + + OCTToxFriendNumber result = tox_friend_by_public_key(self.tox, cPublicKey, &cError); + + free(cPublicKey); + + [self fillError:error withCErrorFriendByPublicKey:cError]; + + return result; +} + +- (NSString *)publicKeyFromFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + OCTLogVerbose(@"get public key from friend number %d", friendNumber); + + uint8_t *cPublicKey = malloc(TOX_PUBLIC_KEY_SIZE); + + TOX_ERR_FRIEND_GET_PUBLIC_KEY cError; + + bool result = tox_friend_get_public_key(self.tox, friendNumber, cPublicKey, &cError); + + NSString *publicKey = nil; + + if (result) { + publicKey = [OCTTox binToHexString:cPublicKey length:TOX_PUBLIC_KEY_SIZE]; + } + + if (cPublicKey) { + free(cPublicKey); + } + + [self fillError:error withCErrorFriendGetPublicKey:cError]; + + return publicKey; +} + +- (BOOL)friendExistsWithFriendNumber:(OCTToxFriendNumber)friendNumber +{ + bool result = tox_friend_exists(self.tox, friendNumber); + + OCTLogVerbose(@"friend exists with friendNumber %d, result %d", friendNumber, result); + + return (BOOL)result; +} + +- (NSDate *)friendGetLastOnlineWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + TOX_ERR_FRIEND_GET_LAST_ONLINE cError; + + uint64_t timestamp = tox_friend_get_last_online(self.tox, friendNumber, &cError); + + [self fillError:error withCErrorFriendGetLastOnline:cError]; + + if (timestamp == UINT64_MAX) { + return nil; + } + + return [NSDate dateWithTimeIntervalSince1970:timestamp]; +} + + +- (OCTToxCapabilities)friendGetCapabilitiesWithFriendNumber:(OCTToxFriendNumber)friendNumber +{ + return tox_friend_get_capabilities(self.tox, friendNumber); +} + +- (OCTToxUserStatus)friendStatusWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + TOX_ERR_FRIEND_QUERY cError; + + TOX_USER_STATUS cStatus = tox_friend_get_status(self.tox, friendNumber, &cError); + + [self fillError:error withCErrorFriendQuery:cError]; + + return [self userStatusFromCUserStatus:cStatus]; +} + +- (OCTToxConnectionStatus)friendConnectionStatusWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + TOX_ERR_FRIEND_QUERY cError; + + TOX_CONNECTION cStatus = tox_friend_get_connection_status(self.tox, friendNumber, &cError); + + [self fillError:error withCErrorFriendQuery:cError]; + + return [self userConnectionStatusFromCUserStatus:cStatus]; +} + +- (BOOL)sendLosslessPacketWithFriendNumber:(OCTToxFriendNumber)friendNumber + pktid:(uint8_t)pktid + data:(NSString *)data + error:(NSError **)error +{ + // TODO: this now only works with UTF8 strings as data, make it work fully with byte arrays later + + NSParameterAssert(data); + + char *cData = [data cStringUsingEncoding:NSUTF8StringEncoding]; + size_t length = [data lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + cData[0] = pktid; + + TOX_ERR_FRIEND_CUSTOM_PACKET cError; + + bool result = tox_friend_send_lossless_packet(self.tox, friendNumber, (const uint8_t *)cData, length, &cError); + + // TODO: fill cError with errorcode + // [self fillError:error xxxxxxxxxx:cError]; + + return (BOOL)result; +} + +- (OCTToxMessageId)sendMessageWithFriendNumber:(OCTToxFriendNumber)friendNumber + type:(OCTToxMessageType)type + message:(NSString *)message + msgv3HashHex:(NSString *)msgv3HashHex + msgv3tssec:(UInt32)msgv3tssec + error:(NSError **)error +{ + NSParameterAssert(message); + + char *cMessage = [message cStringUsingEncoding:NSUTF8StringEncoding]; + size_t length = [message lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + TOX_MESSAGE_TYPE cType; + switch (type) { + case OCTToxMessageTypeNormal: + cType = TOX_MESSAGE_TYPE_NORMAL; + break; + case OCTToxMessageTypeAction: + cType = TOX_MESSAGE_TYPE_ACTION; + break; + case OCTToxMessageTypeHighlevelack: + cType = TOX_MESSAGE_TYPE_HIGH_LEVEL_ACK; + break; + } + + TOX_ERR_FRIEND_SEND_MESSAGE cError; + + char *cMessage2 = cMessage; + size_t length2 = length; + char *cMessage2_alloc = NULL; + uint8_t *hash_buffer_c = NULL; + + if (msgv3HashHex != nil) + { + char *msgv3HashHex_cstr = [msgv3HashHex cStringUsingEncoding:NSUTF8StringEncoding]; + size_t msgv3HashHex_length = [msgv3HashHex lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + if (msgv3HashHex_length >= (TOX_MSGV3_MSGID_LENGTH * 2)) + { + size_t length_orig_corrected = length; + if (length > TOX_MSGV3_MAX_MESSAGE_LENGTH) + { + length_orig_corrected = TOX_MSGV3_MAX_MESSAGE_LENGTH; + } + + cMessage2_alloc = (char *)calloc(1, (size_t)(length_orig_corrected + + TOX_MSGV3_GUARD + TOX_MSGV3_MSGID_LENGTH + TOX_MSGV3_TIMESTAMP_LENGTH)); + hash_buffer_c = hex_to_bin(msgv3HashHex_cstr, (TOX_MSGV3_MSGID_LENGTH * 2)); + + if ((cMessage2_alloc) && (hash_buffer_c)) + { + uint32_t timestamp_unix = (uint32_t)msgv3tssec; + uint32_t timestamp_unix_buf = 0; + // NSLog(@"mmm:timestamp_unix %d", timestamp_unix); + xnet_pack_u32((uint8_t *)×tamp_unix_buf, timestamp_unix); + // NSLog(@"mmm:timestamp_unix_buf %d", timestamp_unix_buf); + + uint8_t* position = cMessage2_alloc; + memcpy(position, cMessage, (size_t)(length_orig_corrected)); + position = position + length_orig_corrected; + position = position + TOX_MSGV3_GUARD; + memcpy(position, hash_buffer_c, (size_t)(TOX_MSGV3_MSGID_LENGTH)); + position = position + TOX_MSGV3_MSGID_LENGTH; + memcpy(position, ×tamp_unix_buf, (size_t)(TOX_MSGV3_TIMESTAMP_LENGTH)); + + length2 = length_orig_corrected + TOX_MSGV3_GUARD + TOX_MSGV3_MSGID_LENGTH + TOX_MSGV3_TIMESTAMP_LENGTH; + cMessage2 = cMessage2_alloc; + } + } + } + + OCTToxMessageId result = tox_friend_send_message(self.tox, friendNumber, cType, (const uint8_t *)cMessage2, length2, &cError); + + if (cMessage2_alloc) + { + free(cMessage2_alloc); + } + + if (hash_buffer_c) + { + free(hash_buffer_c); + } + + [self fillError:error withCErrorFriendSendMessage:cError]; + + return result; +} + +- (BOOL)setNickname:(NSString *)name error:(NSError **)error +{ + NSParameterAssert(name); + + const char *cName = [name cStringUsingEncoding:NSUTF8StringEncoding]; + size_t length = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + TOX_ERR_SET_INFO cError; + + bool result = tox_self_set_name(self.tox, (const uint8_t *)cName, length, &cError); + + [self fillError:error withCErrorSetInfo:cError]; + + OCTLogInfo(@"set userName to %@, result %d", name, result); + + return (BOOL)result; +} + +- (NSString *)userName +{ + size_t length = tox_self_get_name_size(self.tox); + + if (! length) { + return nil; + } + + uint8_t *cName = malloc(length); + tox_self_get_name(self.tox, cName); + + NSString *name = [[NSString alloc] initWithBytes:cName length:length encoding:NSUTF8StringEncoding]; + + free(cName); + + return name; +} + +- (NSString *)friendNameWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + TOX_ERR_FRIEND_QUERY cError; + size_t size = tox_friend_get_name_size(self.tox, friendNumber, &cError); + + [self fillError:error withCErrorFriendQuery:cError]; + + if (cError != TOX_ERR_FRIEND_QUERY_OK) { + return nil; + } + + uint8_t *cName = malloc(size); + bool result = tox_friend_get_name(self.tox, friendNumber, cName, &cError); + + NSString *name = nil; + + if (result) { + name = [[NSString alloc] initWithBytes:cName length:size encoding:NSUTF8StringEncoding]; + } + + if (cName) { + free(cName); + } + + [self fillError:error withCErrorFriendQuery:cError]; + + return name; +} + +- (BOOL)setUserStatusMessage:(NSString *)statusMessage error:(NSError **)error +{ + NSParameterAssert(statusMessage); + + const char *cStatusMessage = [statusMessage cStringUsingEncoding:NSUTF8StringEncoding]; + size_t length = [statusMessage lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + TOX_ERR_SET_INFO cError; + + bool result = tox_self_set_status_message(self.tox, (const uint8_t *)cStatusMessage, length, &cError); + + [self fillError:error withCErrorSetInfo:cError]; + + OCTLogInfo(@"set user status message to %@, result %d", statusMessage, result); + + return (BOOL)result; +} + +- (NSString *)userStatusMessage +{ + size_t length = tox_self_get_status_message_size(self.tox); + + if (! length) { + return nil; + } + + uint8_t *cBuffer = malloc(length); + + tox_self_get_status_message(self.tox, cBuffer); + + NSString *message = [[NSString alloc] initWithBytes:cBuffer length:length encoding:NSUTF8StringEncoding]; + free(cBuffer); + + return message; +} + +- (NSString *)friendStatusMessageWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + TOX_ERR_FRIEND_QUERY cError; + + size_t size = tox_friend_get_status_message_size(self.tox, friendNumber, &cError); + + [self fillError:error withCErrorFriendQuery:cError]; + + if (cError != TOX_ERR_FRIEND_QUERY_OK) { + return nil; + } + + uint8_t *cBuffer = malloc(size); + + bool result = tox_friend_get_status_message(self.tox, friendNumber, cBuffer, &cError); + + NSString *message = nil; + + if (result) { + message = [[NSString alloc] initWithBytes:cBuffer length:size encoding:NSUTF8StringEncoding]; + } + + if (cBuffer) { + free(cBuffer); + } + + [self fillError:error withCErrorFriendQuery:cError]; + + return message; +} + +- (BOOL)setUserIsTyping:(BOOL)isTyping forFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + TOX_ERR_SET_TYPING cError; + + bool result = tox_self_set_typing(self.tox, friendNumber, (bool)isTyping, &cError); + + [self fillError:error withCErrorSetTyping:cError]; + + OCTLogInfo(@"set user isTyping to %d for friend number %d, result %d", isTyping, friendNumber, result); + + return (BOOL)result; +} + +- (BOOL)isFriendTypingWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + TOX_ERR_FRIEND_QUERY cError; + + bool isTyping = tox_friend_get_typing(self.tox, friendNumber, &cError); + + [self fillError:error withCErrorFriendQuery:cError]; + + return (BOOL)isTyping; +} + +- (NSUInteger)friendsCount +{ + return tox_self_get_friend_list_size(self.tox); +} + +- (NSArray *)friendsArray +{ + size_t count = tox_self_get_friend_list_size(self.tox); + + if (! count) { + return @[]; + } + + size_t listSize = count * sizeof(uint32_t); + uint32_t *cList = malloc(listSize); + + tox_self_get_friend_list(self.tox, cList); + + NSMutableArray *list = [NSMutableArray new]; + + for (NSUInteger index = 0; index < count; index++) { + int32_t friendId = cList[index]; + [list addObject:@(friendId)]; + } + + free(cList); + + OCTLogVerbose(@"friend array %@", list); + + return [list copy]; +} + +- (NSData *)hashData:(NSData *)data +{ + uint8_t *cHash = malloc(TOX_HASH_LENGTH); + const uint8_t *cData = [data bytes]; + + bool result = tox_hash(cHash, cData, (uint32_t)data.length); + NSData *hash; + + if (result) { + hash = [NSData dataWithBytes:cHash length:TOX_HASH_LENGTH]; + } + + if (cHash) { + free(cHash); + } + + OCTLogInfo(@"hash data result %@", hash); + + return hash; +} + +- (BOOL)fileSendControlForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + control:(OCTToxFileControl)control + error:(NSError **)error +{ + TOX_FILE_CONTROL cControl; + + switch (control) { + case OCTToxFileControlResume: + cControl = TOX_FILE_CONTROL_RESUME; + break; + case OCTToxFileControlPause: + cControl = TOX_FILE_CONTROL_PAUSE; + break; + case OCTToxFileControlCancel: + cControl = TOX_FILE_CONTROL_CANCEL; + break; + } + + TOX_ERR_FILE_CONTROL cError; + + bool result = tox_file_control(self.tox, friendNumber, fileNumber, cControl, &cError); + + [self fillError:error withCErrorFileControl:cError]; + + return (BOOL)result; +} + +- (BOOL)fileSeekForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + position:(OCTToxFileSize)position + error:(NSError **)error +{ + TOX_ERR_FILE_SEEK cError; + + bool result = tox_file_seek(self.tox, friendNumber, fileNumber, position, &cError); + + [self fillError:error withCErrorFileSeek:cError]; + + return (BOOL)result; +} + +- (NSData *)fileGetFileIdForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + error:(NSError **)error +{ + uint8_t *cFileId = malloc(kOCTToxFileIdLength); + TOX_ERR_FILE_GET cError; + + bool result = tox_file_get_file_id(self.tox, friendNumber, fileNumber, cFileId, &cError); + NSData *fileId; + + [self fillError:error withCErrorFileGet:cError]; + + if (result) { + fileId = [NSData dataWithBytes:cFileId length:kOCTToxFileIdLength]; + } + + if (cFileId) { + free(cFileId); + } + + return fileId; +} + +- (OCTToxFileNumber)fileSendWithFriendNumber:(OCTToxFriendNumber)friendNumber + kind:(OCTToxFileKind)kind + fileSize:(OCTToxFileSize)fileSize + fileId:(NSData *)fileId + fileName:(NSString *)fileName + error:(NSError **)error +{ + TOX_ERR_FILE_SEND cError; + TOX_FILE_KIND cKind; + const uint8_t *cFileId = NULL; + const uint8_t *cFileName = NULL; + + switch (kind) { + case OCTToxFileKindData: + cKind = TOX_FILE_KIND_DATA; + break; + case OCTToxFileKindAvatar: + cKind = TOX_FILE_KIND_AVATAR; + break; + } + + if (fileId.length) { + cFileId = [fileId bytes]; + } + + if (fileName.length) { + cFileName = (const uint8_t *)[fileName cStringUsingEncoding:NSUTF8StringEncoding]; + } + + OCTToxFileNumber result = tox_file_send(self.tox, friendNumber, cKind, fileSize, cFileId, cFileName, fileName.length, &cError); + + [self fillError:error withCErrorFileSend:cError]; + + return result; +} + +- (BOOL)fileSendChunkForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + position:(OCTToxFileSize)position + data:(NSData *)data + error:(NSError **)error +{ + TOX_ERR_FILE_SEND_CHUNK cError; + const uint8_t *cData = [data bytes]; + + bool result = tox_file_send_chunk(self.tox, friendNumber, fileNumber, position, cData, (uint32_t)data.length, &cError); + + [self fillError:error withCErrorFileSendChunk:cError]; + + return (BOOL)result; +} + +#pragma mark - Private methods + +- (void)updateTimerIntervalIfNeeded +{ + uint64_t nextIterate = tox_iteration_interval(self.tox) * USEC_PER_SEC; + + if (self.previousIterate == nextIterate) { + return; + } + + self.previousIterate = nextIterate; + dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, nextIterate), nextIterate, nextIterate / 5); +} + +- (void)setupCFunctions +{ + _tox_self_get_public_key = tox_self_get_public_key; +} + +- (void)setupCallbacks +{ + tox_callback_self_connection_status(_tox, connectionStatusCallback); + tox_callback_friend_name(_tox, friendNameCallback); + tox_callback_friend_status_message(_tox, friendStatusMessageCallback); + tox_callback_friend_status(_tox, friendStatusCallback); + tox_callback_friend_connection_status(_tox, friendConnectionStatusCallback); + tox_callback_friend_typing(_tox, friendTypingCallback); + tox_callback_friend_read_receipt(_tox, friendReadReceiptCallback); + tox_callback_friend_request(_tox, friendRequestCallback); + tox_callback_friend_message(_tox, friendMessageCallback); + tox_callback_friend_lossless_packet(_tox, friendLosslessPacketCallback); + tox_callback_file_recv_control(_tox, fileReceiveControlCallback); + tox_callback_file_chunk_request(_tox, fileChunkRequestCallback); + tox_callback_file_recv(_tox, fileReceiveCallback); + tox_callback_file_recv_chunk(_tox, fileReceiveChunkCallback); +} + +- (OCTToxUserStatus)userStatusFromCUserStatus:(TOX_USER_STATUS)cStatus +{ + switch (cStatus) { + case TOX_USER_STATUS_NONE: + return OCTToxUserStatusNone; + case TOX_USER_STATUS_AWAY: + return OCTToxUserStatusAway; + case TOX_USER_STATUS_BUSY: + return OCTToxUserStatusBusy; + } +} + +- (OCTToxConnectionStatus)userConnectionStatusFromCUserStatus:(TOX_CONNECTION)cStatus +{ + switch (cStatus) { + case TOX_CONNECTION_NONE: + return OCTToxConnectionStatusNone; + case TOX_CONNECTION_TCP: + return OCTToxConnectionStatusTCP; + case TOX_CONNECTION_UDP: + return OCTToxConnectionStatusUDP; + } +} + +- (OCTToxMessageType)messageTypeFromCMessageType:(TOX_MESSAGE_TYPE)cType +{ + switch (cType) { + case TOX_MESSAGE_TYPE_NORMAL: + return OCTToxMessageTypeNormal; + case TOX_MESSAGE_TYPE_ACTION: + return OCTToxMessageTypeAction; + } +} + +- (OCTToxFileControl)fileControlFromCFileControl:(TOX_FILE_CONTROL)cControl +{ + switch (cControl) { + case TOX_FILE_CONTROL_RESUME: + return OCTToxFileControlResume; + case TOX_FILE_CONTROL_PAUSE: + return OCTToxFileControlPause; + case TOX_FILE_CONTROL_CANCEL: + return OCTToxFileControlCancel; + } +} + +- (BOOL)fillError:(NSError **)error withCErrorInit:(TOX_ERR_NEW)cError +{ + if (! error || (cError == TOX_ERR_NEW_OK)) { + return NO; + } + + OCTToxErrorInitCode code = OCTToxErrorInitCodeUnknown; + NSString *description = @"Cannot initialize Tox"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_NEW_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_NEW_NULL: + code = OCTToxErrorInitCodeUnknown; + failureReason = @"Unknown error occured"; + break; + case TOX_ERR_NEW_MALLOC: + code = OCTToxErrorInitCodeMemoryError; + failureReason = @"Not enough memory"; + break; + case TOX_ERR_NEW_PORT_ALLOC: + code = OCTToxErrorInitCodePortAlloc; + failureReason = @"Cannot bint to a port"; + break; + case TOX_ERR_NEW_PROXY_BAD_TYPE: + code = OCTToxErrorInitCodeProxyBadType; + failureReason = @"Proxy type is invalid"; + break; + case TOX_ERR_NEW_PROXY_BAD_HOST: + code = OCTToxErrorInitCodeProxyBadHost; + failureReason = @"Proxy host is invalid"; + break; + case TOX_ERR_NEW_PROXY_BAD_PORT: + code = OCTToxErrorInitCodeProxyBadPort; + failureReason = @"Proxy port is invalid"; + break; + case TOX_ERR_NEW_PROXY_NOT_FOUND: + code = OCTToxErrorInitCodeProxyNotFound; + failureReason = @"Proxy host could not be resolved"; + break; + case TOX_ERR_NEW_LOAD_ENCRYPTED: + code = OCTToxErrorInitCodeEncrypted; + failureReason = @"Tox save is encrypted"; + break; + case TOX_ERR_NEW_LOAD_BAD_FORMAT: + code = OCTToxErrorInitCodeLoadBadFormat; + failureReason = @"Tox save is corrupted"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorBootstrap:(TOX_ERR_BOOTSTRAP)cError +{ + if (! error || (cError == TOX_ERR_BOOTSTRAP_OK)) { + return NO; + } + + OCTToxErrorBootstrapCode code = OCTToxErrorBootstrapCodeUnknown; + NSString *description = @"Cannot bootstrap with specified node"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_BOOTSTRAP_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_BOOTSTRAP_NULL: + code = OCTToxErrorBootstrapCodeUnknown; + failureReason = @"Unknown error occured"; + break; + case TOX_ERR_BOOTSTRAP_BAD_HOST: + code = OCTToxErrorBootstrapCodeBadHost; + failureReason = @"The host could not be resolved to an IP address, or the IP address passed was invalid"; + break; + case TOX_ERR_BOOTSTRAP_BAD_PORT: + code = OCTToxErrorBootstrapCodeBadPort; + failureReason = @"The port passed was invalid"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorFriendAdd:(TOX_ERR_FRIEND_ADD)cError +{ + if (! error || (cError == TOX_ERR_FRIEND_ADD_OK)) { + return NO; + } + + OCTToxErrorFriendAdd code = OCTToxErrorFriendAddUnknown; + NSString *description = @"Cannot add friend"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_FRIEND_ADD_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_FRIEND_ADD_NULL: + code = OCTToxErrorFriendAddUnknown; + failureReason = @"Unknown error occured"; + break; + case TOX_ERR_FRIEND_ADD_TOO_LONG: + code = OCTToxErrorFriendAddTooLong; + failureReason = @"The message is too long"; + break; + case TOX_ERR_FRIEND_ADD_NO_MESSAGE: + code = OCTToxErrorFriendAddNoMessage; + failureReason = @"No message specified"; + break; + case TOX_ERR_FRIEND_ADD_OWN_KEY: + code = OCTToxErrorFriendAddOwnKey; + failureReason = @"Cannot add own address"; + break; + case TOX_ERR_FRIEND_ADD_ALREADY_SENT: + code = OCTToxErrorFriendAddAlreadySent; + failureReason = @"The request was already sent"; + break; + case TOX_ERR_FRIEND_ADD_BAD_CHECKSUM: + code = OCTToxErrorFriendAddBadChecksum; + failureReason = @"Bad checksum"; + break; + case TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM: + code = OCTToxErrorFriendAddSetNewNospam; + failureReason = @"The no spam value is outdated"; + break; + case TOX_ERR_FRIEND_ADD_MALLOC: + code = OCTToxErrorFriendAddMalloc; + failureReason = nil; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorFriendDelete:(TOX_ERR_FRIEND_DELETE)cError +{ + if (! error || (cError == TOX_ERR_FRIEND_DELETE_OK)) { + return NO; + } + + OCTToxErrorFriendDelete code = OCTToxErrorFriendDeleteNotFound; + NSString *description = @"Cannot delete friend"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_FRIEND_DELETE_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND: + code = OCTToxErrorFriendDeleteNotFound; + failureReason = @"Friend not found"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorFriendByPublicKey:(TOX_ERR_FRIEND_BY_PUBLIC_KEY)cError +{ + if (! error || (cError == TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK)) { + return NO; + } + + OCTToxErrorFriendByPublicKey code = OCTToxErrorFriendByPublicKeyUnknown; + NSString *description = @"Cannot get friend by public key"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL: + code = OCTToxErrorFriendByPublicKeyUnknown; + failureReason = @"Unknown error occured"; + break; + case TOX_ERR_FRIEND_BY_PUBLIC_KEY_NOT_FOUND: + code = OCTToxErrorFriendByPublicKeyNotFound; + failureReason = @"No friend with the given Public Key exists on the friend list"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorFriendGetPublicKey:(TOX_ERR_FRIEND_GET_PUBLIC_KEY)cError +{ + if (! error || (cError == TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK)) { + return NO; + } + + OCTToxErrorFriendGetPublicKey code = OCTToxErrorFriendGetPublicKeyFriendNotFound; + NSString *description = @"Cannot get public key of a friend"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND: + code = OCTToxErrorFriendGetPublicKeyFriendNotFound; + failureReason = @"Friend not found"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorSetInfo:(TOX_ERR_SET_INFO)cError +{ + if (! error || (cError == TOX_ERR_SET_INFO_OK)) { + return NO; + } + + OCTToxErrorSetInfoCode code = OCTToxErrorSetInfoCodeUnknow; + NSString *description = @"Cannot set user info"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_SET_INFO_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_SET_INFO_NULL: + code = OCTToxErrorSetInfoCodeUnknow; + failureReason = @"Unknown error occured"; + break; + case TOX_ERR_SET_INFO_TOO_LONG: + code = OCTToxErrorSetInfoCodeTooLong; + failureReason = @"Specified string is too long"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorFriendGetLastOnline:(TOX_ERR_FRIEND_GET_LAST_ONLINE)cError +{ + if (! error || (cError == TOX_ERR_FRIEND_GET_LAST_ONLINE_OK)) { + return NO; + } + + OCTToxErrorFriendGetLastOnline code; + NSString *description = @"Cannot get last online of a friend"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_FRIEND_GET_LAST_ONLINE_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND: + code = OCTToxErrorFriendGetLastOnlineFriendNotFound; + failureReason = @"Friend not found"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorFriendQuery:(TOX_ERR_FRIEND_QUERY)cError +{ + if (! error || (cError == TOX_ERR_FRIEND_QUERY_OK)) { + return NO; + } + + OCTToxErrorFriendQuery code = OCTToxErrorFriendQueryUnknown; + NSString *description = @"Cannot perform friend query"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_FRIEND_QUERY_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_FRIEND_QUERY_NULL: + code = OCTToxErrorFriendQueryUnknown; + failureReason = @"Unknown error occured"; + break; + case TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND: + code = OCTToxErrorFriendQueryFriendNotFound; + failureReason = @"Friend not found"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorSetTyping:(TOX_ERR_SET_TYPING)cError +{ + if (! error || (cError == TOX_ERR_SET_TYPING_OK)) { + return NO; + } + + OCTToxErrorSetTyping code = OCTToxErrorSetTypingFriendNotFound; + NSString *description = @"Cannot set typing status for a friend"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_SET_TYPING_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND: + code = OCTToxErrorSetTypingFriendNotFound; + failureReason = @"Friend not found"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorFriendSendMessage:(TOX_ERR_FRIEND_SEND_MESSAGE)cError +{ + if (! error || (cError == TOX_ERR_FRIEND_SEND_MESSAGE_OK)) { + return NO; + } + + OCTToxErrorFriendSendMessage code = OCTToxErrorFriendSendMessageUnknown; + NSString *description = @"Cannot send message to a friend"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_FRIEND_SEND_MESSAGE_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_FRIEND_SEND_MESSAGE_NULL: + code = OCTToxErrorFriendSendMessageUnknown; + failureReason = @"Unknown error occured"; + break; + case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND: + code = OCTToxErrorFriendSendMessageFriendNotFound; + failureReason = @"Friend not found"; + break; + case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED: + code = OCTToxErrorFriendSendMessageFriendNotConnected; + failureReason = @"Friend not connected"; + break; + case TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ: + code = OCTToxErrorFriendSendMessageAlloc; + failureReason = @"Allocation error"; + break; + case TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG: + code = OCTToxErrorFriendSendMessageTooLong; + failureReason = @"Message is too long"; + break; + case TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY: + code = OCTToxErrorFriendSendMessageEmpty; + failureReason = @"Message is empty"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorFileControl:(TOX_ERR_FILE_CONTROL)cError +{ + if (! error || (cError == TOX_ERR_FILE_CONTROL_OK)) { + return NO; + } + + OCTToxErrorFileControl code; + NSString *description = @"Cannot send file control to a friend"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_FILE_CONTROL_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND: + code = OCTToxErrorFileControlFriendNotFound; + failureReason = @"Friend not found"; + break; + case TOX_ERR_FILE_CONTROL_FRIEND_NOT_CONNECTED: + code = OCTToxErrorFileControlFriendNotConnected; + failureReason = @"Friend is not connected"; + break; + case TOX_ERR_FILE_CONTROL_NOT_FOUND: + code = OCTToxErrorFileControlNotFound; + failureReason = @"No file transfer with given file number found"; + break; + case TOX_ERR_FILE_CONTROL_NOT_PAUSED: + code = OCTToxErrorFileControlNotPaused; + failureReason = @"Resume was send, but file transfer if running normally"; + break; + case TOX_ERR_FILE_CONTROL_DENIED: + code = OCTToxErrorFileControlDenied; + failureReason = @"Cannot resume, file transfer was paused by the other party."; + break; + case TOX_ERR_FILE_CONTROL_ALREADY_PAUSED: + code = OCTToxErrorFileControlAlreadyPaused; + failureReason = @"File is already paused"; + break; + case TOX_ERR_FILE_CONTROL_SENDQ: + code = OCTToxErrorFileControlSendq; + failureReason = @"Packet queue is full"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorFileSeek:(TOX_ERR_FILE_SEEK)cError +{ + if (! error || (cError == TOX_ERR_FILE_SEEK_OK)) { + return NO; + } + + OCTToxErrorFileSeek code; + NSString *description = @"Cannot perform file seek"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_FILE_SEEK_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_FILE_SEEK_FRIEND_NOT_FOUND: + code = OCTToxErrorFileSeekFriendNotFound; + failureReason = @"Friend not found"; + break; + case TOX_ERR_FILE_SEEK_FRIEND_NOT_CONNECTED: + code = OCTToxErrorFileSeekFriendNotConnected; + failureReason = @"Friend is not connected"; + break; + case TOX_ERR_FILE_SEEK_NOT_FOUND: + code = OCTToxErrorFileSeekNotFound; + failureReason = @"No file transfer with given file number found"; + break; + case TOX_ERR_FILE_SEEK_DENIED: + code = OCTToxErrorFileSeekDenied; + failureReason = @"File was not in a state where it could be seeked"; + break; + case TOX_ERR_FILE_SEEK_INVALID_POSITION: + code = OCTToxErrorFileSeekInvalidPosition; + failureReason = @"Seek position was invalid"; + break; + case TOX_ERR_FILE_SEEK_SENDQ: + code = OCTToxErrorFileSeekSendq; + failureReason = @"Packet queue is full"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorFileGet:(TOX_ERR_FILE_GET)cError +{ + if (! error || (cError == TOX_ERR_FILE_GET_OK)) { + return NO; + } + + OCTToxErrorFileGet code; + NSString *description = @"Cannot get file id"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_FILE_GET_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_FILE_GET_NULL: + code = OCTToxErrorFileGetInternal; + failureReason = @"Interval error"; + break; + case TOX_ERR_FILE_GET_FRIEND_NOT_FOUND: + code = OCTToxErrorFileGetFriendNotFound; + failureReason = @"Friend not found"; + break; + case TOX_ERR_FILE_GET_NOT_FOUND: + code = OCTToxErrorFileGetNotFound; + failureReason = @"No file transfer with given file number found"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorFileSend:(TOX_ERR_FILE_SEND)cError +{ + if (! error || (cError == TOX_ERR_FILE_SEND_OK)) { + return NO; + } + + OCTToxErrorFileSend code; + NSString *description = @"Cannot send file"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_FILE_SEND_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_FILE_SEND_NULL: + code = OCTToxErrorFileSendUnknown; + failureReason = @"Unknown error occured"; + break; + case TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND: + code = OCTToxErrorFileSendFriendNotFound; + failureReason = @"Friend not found"; + break; + case TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED: + code = OCTToxErrorFileSendFriendNotConnected; + failureReason = @"Friend not connected"; + break; + case TOX_ERR_FILE_SEND_NAME_TOO_LONG: + code = OCTToxErrorFileSendNameTooLong; + failureReason = @"File name is too long"; + break; + case TOX_ERR_FILE_SEND_TOO_MANY: + code = OCTToxErrorFileSendTooMany; + failureReason = @"Too many ongoing transfers with friend"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorFileSendChunk:(TOX_ERR_FILE_SEND_CHUNK)cError +{ + if (! error || (cError == TOX_ERR_FILE_SEND_CHUNK_OK)) { + return NO; + } + + OCTToxErrorFileSendChunk code; + NSString *description = @"Cannot send chunk of file"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_FILE_SEND_CHUNK_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_FILE_SEND_CHUNK_NULL: + code = OCTToxErrorFileSendChunkUnknown; + failureReason = @"Unknown error occured"; + break; + case TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_FOUND: + code = OCTToxErrorFileSendChunkFriendNotFound; + failureReason = @"Friend not found"; + break; + case TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_CONNECTED: + code = OCTToxErrorFileSendChunkFriendNotConnected; + failureReason = @"Friend not connected"; + break; + case TOX_ERR_FILE_SEND_CHUNK_NOT_FOUND: + code = OCTToxErrorFileSendChunkNotFound; + failureReason = @"No file transfer with given file number found"; + break; + case TOX_ERR_FILE_SEND_CHUNK_NOT_TRANSFERRING: + code = OCTToxErrorFileSendChunkNotTransferring; + failureReason = @"Wrong file transferring state"; + break; + case TOX_ERR_FILE_SEND_CHUNK_INVALID_LENGTH: + code = OCTToxErrorFileSendChunkInvalidLength; + failureReason = @"Invalid chunk length"; + break; + case TOX_ERR_FILE_SEND_CHUNK_SENDQ: + code = OCTToxErrorFileSendChunkSendq; + failureReason = @"Packet queue is full"; + break; + case TOX_ERR_FILE_SEND_CHUNK_WRONG_POSITION: + code = OCTToxErrorFileSendChunkWrongPosition; + failureReason = @"Wrong position in file"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + ++ (NSError *)createErrorWithCode:(NSUInteger)code + description:(NSString *)description + failureReason:(NSString *)failureReason +{ + NSMutableDictionary *userInfo = [NSMutableDictionary new]; + + if (description) { + userInfo[NSLocalizedDescriptionKey] = description; + } + + if (failureReason) { + userInfo[NSLocalizedFailureReasonErrorKey] = failureReason; + } + + return [NSError errorWithDomain:kOCTToxErrorDomain code:code userInfo:userInfo]; +} + ++ (NSString *)binToHexString:(uint8_t *)bin length:(NSUInteger)length +{ + NSMutableString *string = [NSMutableString stringWithCapacity:length]; + + for (NSUInteger idx = 0; idx < length; ++idx) { + [string appendFormat:@"%02X", bin[idx]]; + } + + return [string copy]; +} + +// You are responsible for freeing the return value! ++ (uint8_t *)hexStringToBin:(NSString *)string +{ + // byte is represented by exactly 2 hex digits, so lenth of binary string + // is half of that of the hex one. only hex string with even length + // valid. the more proper implementation would be to check if strlen(hex_string) + // is odd and return error code if it is. we assume strlen is even. if it's not + // then the last byte just won't be written in 'ret'. + + char *hex_string = (char *)string.UTF8String; + size_t i, len = strlen(hex_string) / 2; + uint8_t *ret = malloc(len); + char *pos = hex_string; + + for (i = 0; i < len; ++i, pos += 2) { + sscanf(pos, "%2hhx", &ret[i]); + } + + return ret; +} + +@end + +#pragma mark - Callbacks + +void logCallback(Tox *tox, + TOX_LOG_LEVEL level, + const char *file, + uint32_t line, + const char *func, + const char *message, + void *user_data) +{ + switch (level) { + case TOX_LOG_LEVEL_TRACE: + OCTLogCCVerbose(@"TRACE: %s", file, line, func, message); + break; + case TOX_LOG_LEVEL_DEBUG: + OCTLogCCDebug(@"DEBUG: %s", file, line, func, message); + break; + case TOX_LOG_LEVEL_INFO: + OCTLogCCInfo(@"INFO: %s", file, line, func, message); + break; + case TOX_LOG_LEVEL_WARNING: + OCTLogCCWarn(@"WARNING: %s", file, line, func, message); + break; + case TOX_LOG_LEVEL_ERROR: + OCTLogCCError(@"ERROR: %s", file, line, func, message); + break; + } +} + +void connectionStatusCallback(Tox *cTox, TOX_CONNECTION cStatus, void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + OCTToxConnectionStatus status = [tox userConnectionStatusFromCUserStatus:cStatus]; + + dispatch_async(dispatch_get_main_queue(), ^{ + OCTLogCInfo(@"connectionStatusCallback with status %lu", tox, (unsigned long)status); + + if ([tox.delegate respondsToSelector:@selector(tox:connectionStatus:)]) { + [tox.delegate tox:tox connectionStatus:status]; + } + }); +} + +void friendNameCallback(Tox *cTox, uint32_t friendNumber, const uint8_t *cName, size_t length, void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + NSString *name = [NSString stringWithCString:(const char *)cName encoding:NSUTF8StringEncoding]; + + dispatch_async(dispatch_get_main_queue(), ^{ + OCTLogCInfo(@"nameChangeCallback with name %@, friend number %d", tox, name, friendNumber); + + if ([tox.delegate respondsToSelector:@selector(tox:friendNameUpdate:friendNumber:)]) { + [tox.delegate tox:tox friendNameUpdate:name friendNumber:friendNumber]; + } + }); +} + +void friendStatusMessageCallback(Tox *cTox, uint32_t friendNumber, const uint8_t *cMessage, size_t length, void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + NSString *message = [NSString stringWithCString:(const char *)cMessage encoding:NSUTF8StringEncoding]; + + dispatch_async(dispatch_get_main_queue(), ^{ + OCTLogCInfo(@"statusMessageCallback with status message %@, friend number %d", tox, message, friendNumber); + + if ([tox.delegate respondsToSelector:@selector(tox:friendStatusMessageUpdate:friendNumber:)]) { + [tox.delegate tox:tox friendStatusMessageUpdate:message friendNumber:friendNumber]; + } + }); +} + +void friendStatusCallback(Tox *cTox, uint32_t friendNumber, TOX_USER_STATUS cStatus, void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + OCTToxUserStatus status = [tox userStatusFromCUserStatus:cStatus]; + + dispatch_async(dispatch_get_main_queue(), ^{ + OCTLogCInfo(@"userStatusCallback with status %lu, friend number %d", tox, (unsigned long)status, friendNumber); + + if ([tox.delegate respondsToSelector:@selector(tox:friendStatusUpdate:friendNumber:)]) { + [tox.delegate tox:tox friendStatusUpdate:status friendNumber:friendNumber]; + } + }); +} + +void friendConnectionStatusCallback(Tox *cTox, uint32_t friendNumber, TOX_CONNECTION cStatus, void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + OCTToxConnectionStatus status = [tox userConnectionStatusFromCUserStatus:cStatus]; + + OCTLogCInfo(@"connectionStatusCallback with status %lu, friendNumber %d", tox, (unsigned long)status, friendNumber); + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([tox.delegate respondsToSelector:@selector(tox:friendConnectionStatusChanged:friendNumber:)]) { + [tox.delegate tox:tox friendConnectionStatusChanged:status friendNumber:friendNumber]; + } + }); +} + +void friendTypingCallback(Tox *cTox, uint32_t friendNumber, bool isTyping, void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + OCTLogCInfo(@"typingChangeCallback with isTyping %d, friend number %d", tox, isTyping, friendNumber); + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([tox.delegate respondsToSelector:@selector(tox:friendIsTypingUpdate:friendNumber:)]) { + [tox.delegate tox:tox friendIsTypingUpdate:(BOOL)isTyping friendNumber:friendNumber]; + } + }); +} + +void friendReadReceiptCallback(Tox *cTox, uint32_t friendNumber, uint32_t messageId, void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + OCTLogCInfo(@"readReceiptCallback with message id %d, friendNumber %d", tox, messageId, friendNumber); + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([tox.delegate respondsToSelector:@selector(tox:messageDelivered:friendNumber:)]) { + [tox.delegate tox:tox messageDelivered:messageId friendNumber:friendNumber]; + } + }); +} + +void friendRequestCallback(Tox *cTox, const uint8_t *cPublicKey, const uint8_t *cMessage, size_t length, void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + NSString *publicKey = [OCTTox binToHexString:(uint8_t *)cPublicKey length:TOX_PUBLIC_KEY_SIZE]; + NSString *message = [[NSString alloc] initWithBytes:cMessage length:length encoding:NSUTF8StringEncoding]; + + dispatch_async(dispatch_get_main_queue(), ^{ + OCTLogCInfo(@"friendRequestCallback with publicKey %@, message %@", tox, publicKey, message); + + if ([tox.delegate respondsToSelector:@selector(tox:friendRequestWithMessage:publicKey:)]) { + [tox.delegate tox:tox friendRequestWithMessage:message publicKey:publicKey]; + } + }); +} + +void friendMessageCallback( + Tox *cTox, + uint32_t friendNumber, + TOX_MESSAGE_TYPE cType, + const uint8_t *cMessage, + size_t length, + void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + // HINT: invalid UTF-8 will make realm manager crash later, or if length is shorter than bytes in cMessage + // so check at least for NULL bytes and shorten length accordingly + + uint8_t *newcMessage = calloc(1, length + 1); + if (!newcMessage) + { + // HINT: we cant allocate the new buffer, so we must ignore this incoming message + return; + } + + size_t newLength = 0; + for (int i=0; i < length; i++) + { + if (*(cMessage + i) != 0) + { + newLength++; + } + else + { + break; + } + } + + memcpy(newcMessage, cMessage, (size_t)newLength); + + // add 1 for the NULL byte at the end + newLength++; + + if (newLength < 2) + { + // HINT: message seems to contain nothing before the first NULL byte, so discard it + free(newcMessage); + return; + } + + NSString *message = [[NSString alloc] initWithBytes:newcMessage length:newLength encoding:NSUTF8StringEncoding]; + free(newcMessage); + + if (!message) + { + // HINT: message seems to contain invalid UTF-8 + // instead use a dummy message "__" + message = @"__"; + newLength = 2; + } + + + // HINT: msgV3 ------------------------------------------------ + // HINT: msgV3 ------------------------------------------------ + // HINT: msgV3 ------------------------------------------------ + int need_free = 0; + uint32_t msgv3_timstamp_int = 0; + char *message_v3_hash_hexstr = NULL; + + if ((cMessage) && (length > (TOX_MSGV3_MSGID_LENGTH + TOX_MSGV3_TIMESTAMP_LENGTH + TOX_MSGV3_GUARD))) + { + int pos = length - (TOX_MSGV3_MSGID_LENGTH + TOX_MSGV3_TIMESTAMP_LENGTH + TOX_MSGV3_GUARD); + + // bytes at guard position + uint8_t g1 = *(cMessage + pos); + uint8_t g2 = *(cMessage + pos + 1); + + // check for the msgv3 guard + if ((g1 == 0) && (g2 == 0)) + { + uint8_t *message_v3_hash_bin = calloc(1, TOX_MSGV3_MSGID_LENGTH); + if (!message_v3_hash_bin) + { + OCTLogCInfo(@"friendMessageCallback:friend_message_cb:could not allocate buffer for hash: incoming message discarded", tox); + return; + } + + uint8_t *message_v3_timestamp_bin = calloc(1, TOX_MSGV3_TIMESTAMP_LENGTH); + if (!message_v3_timestamp_bin) + { + OCTLogCInfo(@"friendMessageCallback:friend_message_cb:could not allocate buffer for timestamp: incoming message discarded", tox); + free(message_v3_hash_bin); + return; + } + + need_free = 1; + memcpy(message_v3_hash_bin, (cMessage + pos + TOX_MSGV3_GUARD), TOX_MSGV3_MSGID_LENGTH); + memcpy(message_v3_timestamp_bin, (cMessage + pos + TOX_MSGV3_GUARD + TOX_MSGV3_MSGID_LENGTH), TOX_MSGV3_TIMESTAMP_LENGTH); + + message_v3_hash_hexstr = calloc(1, (TOX_MSGV3_MSGID_LENGTH * 2) + 1); + if (message_v3_hash_hexstr) + { + bin_to_hex((const char *)message_v3_hash_bin, (size_t)TOX_MSGV3_MSGID_LENGTH, message_v3_hash_hexstr); + const uint8_t *p = (uint8_t *)(message_v3_timestamp_bin); + p += xnet_unpack_u32(p, &msgv3_timstamp_int); + // OCTLogCInfo(@"mmm:friendMessageCallback:friend_message_cb:hash=%s ts=%d", tox, message_v3_hash_hexstr, msgv3_timstamp_int); + } + + if (need_free == 1) + { + free(message_v3_hash_bin); + free(message_v3_timestamp_bin); + } + } + } + // HINT: msgV3 ------------------------------------------------ + // HINT: msgV3 ------------------------------------------------ + // HINT: msgV3 ------------------------------------------------ + + NSString *msgv3HashHexStr = nil; + if (message_v3_hash_hexstr) + { + msgv3HashHexStr = [[NSString alloc] initWithBytes:message_v3_hash_hexstr length:(TOX_MSGV3_MSGID_LENGTH * 2) encoding:NSUTF8StringEncoding]; + free(message_v3_hash_hexstr); + OCTLogCInfo(@"friendMessageCallback with friend message %@", tox, msgv3HashHexStr); + } + + if (cType == TOX_MESSAGE_TYPE_HIGH_LEVEL_ACK) + { + // HINT: this message is not a normal message, but a msgV3 high level ACK. + // we do not save it in the database, nor show it in the chat window. + if (msgv3HashHexStr != nil) + { + // HINT: set isDelivered status to true for the message with this hex hash. + + dispatch_async(dispatch_get_main_queue(), ^{ + OCTLogCInfo(@"friendMessageCallback received level ACK %@", tox, msgv3HashHexStr); + + if ([tox.delegate respondsToSelector:@selector(tox:friendHighLevelACK:friendNumber:msgv3HashHex:sendTimestamp:)]) { + [tox.delegate tox:tox friendHighLevelACK:message friendNumber:friendNumber + msgv3HashHex:msgv3HashHexStr sendTimestamp:msgv3_timstamp_int]; + } + }); + + } + + return; + } + else + { + if (msgv3HashHexStr != nil) + { + // HINT: msgV3 message reveived + // friend must have msgV3 capability, set it in the database + dispatch_async(dispatch_get_main_queue(), ^{ + if ([tox.delegate respondsToSelector:@selector(tox:friendSetMsgv3Capability:friendNumber:)]) { + [tox.delegate tox:tox friendSetMsgv3Capability:YES friendNumber:friendNumber]; + } + OCTLogCInfo(@"friendMessageCallback msgV3 YES", tox); + }); + } + else + { + // HINT: old msg version recevied + // friend does not msgV3 capability, clear it in the database + dispatch_async(dispatch_get_main_queue(), ^{ + if ([tox.delegate respondsToSelector:@selector(tox:friendSetMsgv3Capability:friendNumber:)]) { + [tox.delegate tox:tox friendSetMsgv3Capability:NO friendNumber:friendNumber]; + } + OCTLogCInfo(@"friendMessageCallback msgV3 --NO--", tox); + }); + } + + OCTToxMessageType type = [tox messageTypeFromCMessageType:cType]; + + dispatch_async(dispatch_get_main_queue(), ^{ + // OCTLogCInfo(@"friendMessageCallback with message %@, friend number %d", tox, message, friendNumber); + // OCTLogCInfo(@"friendMessageCallback with friend number %d len %d newlen %d", tox, friendNumber, length, newLength); + // OCTLogCInfo(@"friendMessageCallback with friend message %@", tox, msgv3HashHexStr); + + // HINT: save message to database + if ([tox.delegate respondsToSelector:@selector(tox:friendMessage:type:friendNumber:msgv3HashHex:sendTimestamp:)]) { + [tox.delegate tox:tox friendMessage:message type:type friendNumber:friendNumber + msgv3HashHex:msgv3HashHexStr sendTimestamp:msgv3_timstamp_int]; + } + + // HINT: now send msgV3 high level ACK + if ([tox.delegate respondsToSelector:@selector(tox:sendFriendHighlevelACK:friendNumber:msgv3HashHex:sendTimestamp:)]) { + NSString *message = @"_"; + [tox.delegate tox:tox sendFriendHighlevelACK:message friendNumber:friendNumber + msgv3HashHex:msgv3HashHexStr sendTimestamp:msgv3_timstamp_int]; + } + + }); + } +} + +void friendLosslessPacketCallback( + Tox *cTox, + uint32_t friendNumber, + const uint8_t *data, + size_t length, + void *userData) +{ + if ((length <= 5) || (length >= 300)) { + return; + } + + OCTTox *tox = (__bridge OCTTox *)(userData); + + // TODO: catch errors and bad utf-8 here! + NSData *lossless_bytes = [NSData dataWithBytes:data length:length]; + + if (lossless_bytes == nil) { + return; + } + + NSString *pushTokenString = nil; + + if ((length > 5) && (length < 300)) { + if (data[0] == 181) { + pushTokenString = [[NSString alloc] initWithUTF8String:(data + 1)]; + } else { + return; + } + } else { + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + // OCTLogCInfo(@"friendLosslessPacketCallback with lossless data %@, friend number %d", + // tox, [lossless_bytes description], friendNumber); + // OCTLogCInfo(@"friendLosslessPacketCallback with pushTokenString %@, friend number %d", + // tox, [pushTokenString description], friendNumber); + + if ([tox.delegate respondsToSelector:@selector(tox:friendPushTokenUpdate:friendNumber:)]) { + [tox.delegate tox:tox friendPushTokenUpdate:pushTokenString friendNumber:friendNumber]; + } + }); +} + +void fileReceiveControlCallback(Tox *cTox, uint32_t friendNumber, OCTToxFileNumber fileNumber, TOX_FILE_CONTROL cControl, void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + OCTToxFileControl control = [tox fileControlFromCFileControl:cControl]; + + dispatch_async(dispatch_get_main_queue(), ^{ + OCTLogCInfo(@"fileReceiveControlCallback with friendNumber %d fileNumber %d controlType %lu", + tox, friendNumber, fileNumber, (unsigned long)control); + + if ([tox.delegate respondsToSelector:@selector(tox:fileReceiveControl:friendNumber:fileNumber:)]) { + [tox.delegate tox:tox fileReceiveControl:control friendNumber:friendNumber fileNumber:fileNumber]; + } + }); +} + +void fileChunkRequestCallback(Tox *cTox, uint32_t friendNumber, OCTToxFileNumber fileNumber, uint64_t position, size_t length, void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([tox.delegate respondsToSelector:@selector(tox:fileChunkRequestForFileNumber:friendNumber:position:length:)]) { + [tox.delegate tox:tox fileChunkRequestForFileNumber:fileNumber + friendNumber:friendNumber + position:position + length:length]; + } + }); +} + +void fileReceiveCallback( + Tox *cTox, + uint32_t friendNumber, + OCTToxFileNumber fileNumber, + TOX_FILE_KIND cKind, + uint64_t fileSize, + const uint8_t *cFileName, + size_t fileNameLength, + void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + OCTToxFileKind kind; + + switch (cKind) { + case TOX_FILE_KIND_DATA: + kind = OCTToxFileKindData; + break; + case TOX_FILE_KIND_AVATAR: + kind = OCTToxFileKindAvatar; + break; + } + + NSString *fileName = [[NSString alloc] initWithBytes:cFileName length:fileNameLength encoding:NSUTF8StringEncoding]; + + dispatch_async(dispatch_get_main_queue(), ^{ + OCTLogCInfo(@"fileReceiveCallback with friendNumber %d fileNumber %d kind %ld fileSize %llu fileName %@", + tox, friendNumber, fileNumber, (long)kind, fileSize, fileName); + + if ([tox.delegate respondsToSelector:@selector(tox:fileReceiveForFileNumber:friendNumber:kind:fileSize:fileName:)]) { + [tox.delegate tox:tox fileReceiveForFileNumber:fileNumber + friendNumber:friendNumber + kind:kind + fileSize:fileSize + fileName:fileName]; + } + }); +} + +void fileReceiveChunkCallback( + Tox *cTox, + uint32_t friendNumber, + OCTToxFileNumber fileNumber, + uint64_t position, + const uint8_t *cData, + size_t length, + void *userData) +{ + OCTTox *tox = (__bridge OCTTox *)(userData); + + NSData *chunk = nil; + + if (length) { + chunk = [NSData dataWithBytes:cData length:length]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([tox.delegate respondsToSelector:@selector(tox:fileReceiveChunk:fileNumber:friendNumber:position:)]) { + [tox.delegate tox:tox fileReceiveChunk:chunk fileNumber:fileNumber friendNumber:friendNumber position:position]; + } + }); +} diff --git a/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxAV+Private.h b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxAV+Private.h new file mode 100644 index 0000000..bebaaac --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxAV+Private.h @@ -0,0 +1,51 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTToxAV.h" +#import + +/** + * ToxAV functions + */ + +extern ToxAV *(*_toxav_new)(Tox *tox, TOXAV_ERR_NEW *error); +extern uint32_t (*_toxav_iteration_interval)(const ToxAV *toxAV); +extern void (*_toxav_iterate)(ToxAV *toxAV); +extern void (*_toxav_kill)(ToxAV *toxAV); + +extern bool (*_toxav_call)(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_CALL *error); +extern bool (*_toxav_answer)(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER *error); +extern bool (*_toxav_call_control)(ToxAV *toxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error); + +extern bool (*_toxav_audio_set_bit_rate)(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error); +extern bool (*_toxav_video_set_bit_rate)(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error); + +extern bool (*_toxav_audio_send_frame)(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error); +extern bool (*_toxav_video_send_frame)(ToxAV *toxAV, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error); + +/** + * Callbacks + */ +toxav_call_cb callIncomingCallback; +toxav_call_state_cb callStateCallback; +toxav_audio_bit_rate_cb audioBitRateStatusCallback; +toxav_video_bit_rate_cb videoBitRateStatusCallback; +toxav_audio_receive_frame_cb receiveAudioFrameCallback; +toxav_video_receive_frame_cb receiveVideoFrameCallback; + +@interface OCTToxAV (Private) + +@property (assign, nonatomic) ToxAV *toxAV; + +- (BOOL)fillError:(NSError **)error withCErrorInit:(TOXAV_ERR_NEW)cError; +- (BOOL)fillError:(NSError **)error withCErrorCall:(TOXAV_ERR_CALL)cError; +- (BOOL)fillError:(NSError **)error withCErrorAnswer:(TOXAV_ERR_ANSWER)cError; +- (BOOL)fillError:(NSError **)error withCErrorControl:(TOXAV_ERR_CALL_CONTROL)cError; +- (BOOL)fillError:(NSError **)error withCErrorSetBitRate:(TOXAV_ERR_BIT_RATE_SET)cError; +- (BOOL)fillError:(NSError **)error withCErrorSendFrame:(TOXAV_ERR_SEND_FRAME)cError; +- (NSError *)createErrorWithCode:(NSUInteger)code + description:(NSString *)description + failureReason:(NSString *)failureReason; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxAV.m b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxAV.m new file mode 100644 index 0000000..78fb6fb --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxAV.m @@ -0,0 +1,648 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTTox+Private.h" +#import "OCTToxAV+Private.h" +#import "OCTLogging.h" + +ToxAV *(*_toxav_new)(Tox *tox, TOXAV_ERR_NEW *error); +uint32_t (*_toxav_iteration_interval)(const ToxAV *toxAV); +void (*_toxav_iterate)(ToxAV *toxAV); +void (*_toxav_kill)(ToxAV *toxAV); + +bool (*_toxav_call)(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_CALL *error); +bool (*_toxav_answer)(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER *error); +bool (*_toxav_call_control)(ToxAV *toxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error); + +bool (*_toxav_audio_set_bit_rate)(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error); +bool (*_toxav_video_set_bit_rate)(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error); + +bool (*_toxav_audio_send_frame)(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error); +bool (*_toxav_video_send_frame)(ToxAV *toxAV, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error); + + +@interface OCTToxAV () + +@property (assign, nonatomic) ToxAV *toxAV; + +@property (strong, nonatomic) dispatch_source_t timer; + +@property (assign, nonatomic) uint64_t previousIterate; + +@end + +@implementation OCTToxAV + +#pragma mark - Lifecycle +- (instancetype)initWithTox:(OCTTox *)tox error:(NSError **)error +{ + self = [super init]; + + if (! self) { + return nil; + } + + OCTLogVerbose(@"init called"); + + [self setupCFunctions]; + + TOXAV_ERR_NEW cError; + _toxAV = _toxav_new(tox.tox, &cError); + + [self fillError:error withCErrorInit:cError]; + + [self setupCallbacks]; + + return self; +} + +- (void)start +{ + OCTLogVerbose(@"start method called"); + + @synchronized(self) { + if (self.timer) { + OCTLogWarn(@"already started"); + return; + } + + dispatch_queue_t queue = dispatch_queue_create("me.dvor.objcTox.OCTToxAVQueue", NULL); + self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + + [self updateTimerIntervalIfNeeded]; + + __weak OCTToxAV *weakSelf = self; + dispatch_source_set_event_handler(self.timer, ^{ + OCTToxAV *strongSelf = weakSelf; + if (! strongSelf) { + return; + } + + _toxav_iterate(strongSelf.toxAV); + + [strongSelf updateTimerIntervalIfNeeded]; + }); + + dispatch_resume(self.timer); + } + OCTLogInfo(@"started"); +} + +- (void)stop +{ + OCTLogVerbose(@"stop method called"); + + @synchronized(self) { + if (! self.timer) { + OCTLogWarn(@"toxav isn't running, nothing to stop"); + return; + } + + dispatch_source_cancel(self.timer); + self.timer = nil; + } + + OCTLogInfo(@"stopped"); +} + +- (void)dealloc +{ + [self stop]; + _toxav_kill(self.toxAV); + OCTLogVerbose(@"dealloc called, toxav killed"); +} + +#pragma mark - Call Methods + +- (BOOL)callFriendNumber:(OCTToxFriendNumber)friendNumber audioBitRate:(OCTToxAVAudioBitRate)audioBitRate videoBitRate:(OCTToxAVVideoBitRate)videoBitRate error:(NSError **)error +{ + TOXAV_ERR_CALL cError; + BOOL status = _toxav_call(self.toxAV, friendNumber, audioBitRate, videoBitRate, &cError); + + [self fillError:error withCErrorCall:cError]; + + return status; +} + +- (BOOL)answerIncomingCallFromFriend:(OCTToxFriendNumber)friendNumber audioBitRate:(OCTToxAVAudioBitRate)audioBitRate videoBitRate:(OCTToxAVVideoBitRate)videoBitrate error:(NSError **)error +{ + TOXAV_ERR_ANSWER cError; + BOOL status = _toxav_answer(self.toxAV, friendNumber, audioBitRate, videoBitrate, &cError); + + [self fillError:error withCErrorAnswer:cError]; + + return status; +} + +- (BOOL)sendCallControl:(OCTToxAVCallControl)control toFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + TOXAV_CALL_CONTROL cControl; + + switch (control) { + case OCTToxAVCallControlResume: + cControl = TOXAV_CALL_CONTROL_RESUME; + break; + case OCTToxAVCallControlPause: + cControl = TOXAV_CALL_CONTROL_PAUSE; + break; + case OCTToxAVCallControlCancel: + cControl = TOXAV_CALL_CONTROL_CANCEL; + break; + case OCTToxAVCallControlMuteAudio: + cControl = TOXAV_CALL_CONTROL_MUTE_AUDIO; + break; + case OCTToxAVCallControlUnmuteAudio: + cControl = TOXAV_CALL_CONTROL_UNMUTE_AUDIO; + break; + case OCTToxAVCallControlHideVideo: + cControl = TOXAV_CALL_CONTROL_HIDE_VIDEO; + break; + case OCTToxAVCallControlShowVideo: + cControl = TOXAV_CALL_CONTROL_SHOW_VIDEO; + break; + } + + TOXAV_ERR_CALL_CONTROL cError; + + BOOL status = _toxav_call_control(self.toxAV, friendNumber, cControl, &cError); + + [self fillError:error withCErrorControl:cError]; + + return status; +} + +#pragma mark - Controlling bit rates + +- (BOOL)setAudioBitRate:(OCTToxAVAudioBitRate)bitRate force:(BOOL)force forFriend:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + TOXAV_ERR_BIT_RATE_SET cError; + + BOOL status = _toxav_audio_set_bit_rate(self.toxAV, friendNumber, bitRate, &cError); + + [self fillError:error withCErrorSetBitRate:cError]; + + OCTLogVerbose(@"setAudioBitRate:%lu, force:%d, friend:%d", (long)bitRate, force, friendNumber); + + return status; +} + +- (BOOL)setVideoBitRate:(OCTToxAVVideoBitRate)bitRate force:(BOOL)force forFriend:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + TOXAV_ERR_BIT_RATE_SET cError; + + BOOL status = _toxav_video_set_bit_rate(self.toxAV, friendNumber, bitRate, &cError); + + [self fillError:error withCErrorSetBitRate:cError]; + + return status; +} + +#pragma mark - Sending frames +- (BOOL)sendAudioFrame:(OCTToxAVPCMData *)pcm sampleCount:(OCTToxAVSampleCount)sampleCount + channels:(OCTToxAVChannels)channels sampleRate:(OCTToxAVSampleRate)sampleRate + toFriend:(OCTToxFriendNumber)friendNumber error:(NSError **)error +{ + // TOXAUDIO: -outgoing-audio- + TOXAV_ERR_SEND_FRAME cError; + + BOOL status = _toxav_audio_send_frame(self.toxAV, friendNumber, + pcm, sampleCount, + channels, sampleRate, &cError); + + [self fillError:error withCErrorSendFrame:cError]; + + return status; +} + +- (BOOL)sendVideoFrametoFriend:(OCTToxFriendNumber)friendNumber + width:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height + yPlane:(OCTToxAVPlaneData *)yPlane uPlane:(OCTToxAVPlaneData *)uPlane + vPlane:(OCTToxAVPlaneData *)vPlane + error:(NSError **)error +{ + TOXAV_ERR_SEND_FRAME cError; + BOOL status = _toxav_video_send_frame(self.toxAV, friendNumber, width, height, yPlane, uPlane, vPlane, &cError); + + [self fillError:error withCErrorSendFrame:cError]; + + return status; +} + +#pragma mark - Private + +- (void)setupCFunctions +{ + _toxav_new = toxav_new; + _toxav_iteration_interval = toxav_iteration_interval; + _toxav_iterate = toxav_iterate; + _toxav_kill = toxav_kill; + + _toxav_call = toxav_call; + _toxav_answer = toxav_answer; + _toxav_call_control = toxav_call_control; + + _toxav_audio_set_bit_rate = toxav_audio_set_bit_rate; + _toxav_video_set_bit_rate = toxav_video_set_bit_rate; + + _toxav_audio_send_frame = toxav_audio_send_frame; + _toxav_video_send_frame = toxav_video_send_frame; +} + +- (void)setupCallbacks +{ + toxav_callback_call(_toxAV, callIncomingCallback, (__bridge void *)(self)); + toxav_callback_call_state(_toxAV, callStateCallback, (__bridge void *)(self)); + toxav_callback_audio_bit_rate(_toxAV, audioBitRateStatusCallback, (__bridge void *)(self)); + toxav_callback_video_bit_rate(_toxAV, videoBitRateStatusCallback, (__bridge void *)(self)); + toxav_callback_audio_receive_frame(_toxAV, receiveAudioFrameCallback, (__bridge void *)(self)); + toxav_callback_video_receive_frame(_toxAV, receiveVideoFrameCallback, (__bridge void *)(self)); +} + +- (BOOL)fillError:(NSError **)error withCErrorInit:(TOXAV_ERR_NEW)cError +{ + if (! error || (cError == TOXAV_ERR_NEW_OK)) { + return NO; + } + + OCTToxAVErrorInitCode code = OCTToxAVErrorInitCodeUnknown; + NSString *description = @"Cannot initialize ToxAV"; + NSString *failureReason = nil; + + switch (cError) { + case TOXAV_ERR_NEW_OK: + NSAssert(NO, @"We shouldn't be here!"); + break; + case TOXAV_ERR_NEW_NULL: + code = OCTToxAVErrorInitNULL; + failureReason = @"One of the arguments to the function was NULL when it was not expected."; + break; + case TOXAV_ERR_NEW_MALLOC: + code = OCTToxAVErrorInitCodeMemoryError; + failureReason = @"Memory allocation failure while trying to allocate structures required for the A/V session."; + break; + case TOXAV_ERR_NEW_MULTIPLE: + code = OCTToxAVErrorInitMultiple; + failureReason = @"Attempted to create a second session for the same Tox instance."; + break; + } + *error = [self createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorCall:(TOXAV_ERR_CALL)cError +{ + if (! error || (cError == TOXAV_ERR_CALL_OK)) { + return NO; + } + + OCTToxAVErrorCall code = OCTToxAVErrorCallUnknown; + NSString *description = @"Could not make call"; + NSString *failureReason = nil; + + switch (cError) { + case TOXAV_ERR_CALL_OK: + NSAssert(NO, @"We shouldn't be here!"); + break; + case TOXAV_ERR_CALL_MALLOC: + code = OCTToxAVErrorCallMalloc; + failureReason = @"A resource allocation error occured while trying to create the structures required for the call."; + break; + case TOXAV_ERR_CALL_SYNC: + code = OCTToxAVErrorCallSync; + failureReason = @"Synchronization error occurred."; + break; + case TOXAV_ERR_CALL_FRIEND_NOT_FOUND: + code = OCTToxAVErrorCallFriendNotFound; + failureReason = @"The friend number did not designate a valid friend."; + break; + case TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED: + code = OCTToxAVErrorCallFriendNotConnected; + failureReason = @"The friend was valid, but not currently connected"; + break; + case TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL: + code = OCTToxAVErrorCallAlreadyInCall; + failureReason = @"Attempted to call a friend while already in an audio or video call with them."; + break; + case TOXAV_ERR_CALL_INVALID_BIT_RATE: + code = OCTToxAVErrorCallInvalidBitRate; + failureReason = @"Audio or video bit rate is invalid"; + break; + } + + *error = [self createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorAnswer:(TOXAV_ERR_ANSWER)cError +{ + if (! error || (cError == TOXAV_ERR_ANSWER_OK)) { + return NO; + } + + OCTToxAVErrorAnswer code = OCTToxAVErrorAnswerUnknown; + NSString *description = @"Could not answer call"; + NSString *failureReason = nil; + + switch (cError) { + case TOXAV_ERR_ANSWER_OK: + NSAssert(NO, @"We shouldn't be here!"); + break; + case TOXAV_ERR_ANSWER_SYNC: + code = OCTToxAVErrorAnswerSync; + break; + case TOXAV_ERR_ANSWER_CODEC_INITIALIZATION: + code = OCTToxAVErrorAnswerCodecInitialization; + break; + case TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING: + code = OCTToxAVErrorAnswerFriendNotCalling; + break; + case TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND: + code = OCTToxAVErrorAnswerFriendNotFound; + break; + case TOXAV_ERR_ANSWER_INVALID_BIT_RATE: + code = OCTToxAVErrorAnswerInvalidBitRate; + break; + } + + *error = [self createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorControl:(TOXAV_ERR_CALL_CONTROL)cError +{ + if (! error || (cError == TOXAV_ERR_CALL_CONTROL_OK)) { + return NO; + } + + OCTToxErrorCallControl code = OCTToxAVErrorControlUnknown; + NSString *description = @"Unable set control"; + NSString *failureReason = nil; + + switch (cError) { + case TOXAV_ERR_CALL_CONTROL_OK: + NSAssert(NO, @"We shouldn't be here!"); + break; + case TOXAV_ERR_CALL_CONTROL_SYNC: + code = OCTToxAVErrorControlSync; + failureReason = @"Synchronization error occurred."; + break; + case TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND: + code = OCTToxAVErrorControlFriendNotFound; + failureReason = @"The friend number passed did not designate a valid friend."; + break; + case TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL: + code = OCTToxAVErrorControlFriendNotInCall; + failureReason = @"This client is currently not in a call with the friend. Before the call is answered, only CANCEL is a valid control."; + break; + case TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION: + code = OCTToxAVErrorControlInvaldTransition; + failureReason = @"Happens if user tried to pause an already paused call or if trying to resume a call that is not paused."; + break; + } + + *error = [self createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorSetBitRate:(TOXAV_ERR_BIT_RATE_SET)cError +{ + if (! error || (cError == TOXAV_ERR_BIT_RATE_SET_OK)) { + return NO; + } + + OCTToxAVErrorSetBitRate code = OCTToxAVErrorSetBitRateUnknown; + NSString *description = @"Unable to set audio/video bitrate"; + NSString *failureReason = nil; + + switch (cError) { + case TOXAV_ERR_BIT_RATE_SET_OK: + NSAssert(NO, @"We shouldn't be here!"); + break; + case TOXAV_ERR_BIT_RATE_SET_SYNC: + code = OCTToxAVErrorSetBitRateSync; + failureReason = @"Synchronization error occurred."; + break; + case TOXAV_ERR_BIT_RATE_SET_INVALID_BIT_RATE: + code = OCTToxAVErrorSetBitRateInvalidBitRate; + failureReason = @"The bit rate passed was not one of the supported values."; + break; + case TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND: + code = OCTToxAVErrorSetBitRateFriendNotFound; + failureReason = @"The friend number passed did not designate a valid friend"; + break; + case TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL: + code = OCTToxAVErrorSetBitRateFriendNotInCall; + failureReason = @"This client is currently not in a call with the friend"; + break; + } + + *error = [self createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (BOOL)fillError:(NSError **)error withCErrorSendFrame:(TOXAV_ERR_SEND_FRAME)cError +{ + if (! error || (cError == TOXAV_ERR_SEND_FRAME_OK)) { + return NO; + } + + OCTToxAVErrorSendFrame code = OCTToxAVErrorSendFrameUnknown; + NSString *description = @"Failed to send audio/video frame"; + NSString *failureReason = @"Unable to sending audio/video frame"; + switch (cError) { + case TOXAV_ERR_SEND_FRAME_OK: + NSAssert(NO, @"We shouldn't be here!"); + break; + case TOXAV_ERR_SEND_FRAME_NULL: + code = OCTToxAVErrorSendFrameNull; + failureReason = @"In case of video, one of Y, U, or V was NULL. In case of audio, the samples data pointer was NULL."; + break; + case TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND: + code = OCTToxAVErrorSendFrameFriendNotFound; + failureReason = @"The friend number passed did not designate a valid friend."; + break; + case TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL: + code = OCTToxAVErrorSendFrameFriendNotInCall; + failureReason = @"This client is currently not in a call with the friend"; + break; + case TOXAV_ERR_SEND_FRAME_SYNC: + code = OCTToxAVErrorSendFrameSync; + failureReason = @"Synchronization error occurred"; + break; + case TOXAV_ERR_SEND_FRAME_INVALID: + code = OCTToxAVErrorSendFrameInvalid; + failureReason = @"One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling rate may be unsupported"; + break; + case TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED: + code = OCTToxAVErrorSendFramePayloadTypeDisabled; + failureReason = @"Either friend turned off audio/video receiving or we turned off sending for the said payload."; + break; + case TOXAV_ERR_SEND_FRAME_RTP_FAILED: + code = OCTToxAVErrorSendFrameRTPFailed; + failureReason = @"Failed to push frame through rtp interface"; + break; + } + + *error = [self createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +- (NSError *)createErrorWithCode:(NSUInteger)code + description:(NSString *)description + failureReason:(NSString *)failureReason +{ + NSMutableDictionary *userInfo = [NSMutableDictionary new]; + + if (description) { + userInfo[NSLocalizedDescriptionKey] = description; + } + + if (failureReason) { + userInfo[NSLocalizedFailureReasonErrorKey] = failureReason; + } + + return [NSError errorWithDomain:kOCTToxAVErrorDomain code:code userInfo:userInfo]; +} + +- (void)updateTimerIntervalIfNeeded +{ + uint64_t nextIterate = _toxav_iteration_interval(self.toxAV) * (NSEC_PER_SEC / 1000); + + if (self.previousIterate == nextIterate) { + return; + } + + self.previousIterate = nextIterate; + dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, nextIterate), nextIterate, nextIterate / 5); +} + +@end + +#pragma mark - Callbacks + +void callIncomingCallback(ToxAV *cToxAV, + uint32_t friendNumber, + bool audioEnabled, + bool videoEnabled, + void *userData) +{ + OCTToxAV *toxAV = (__bridge OCTToxAV *)userData; + + dispatch_async(dispatch_get_main_queue(), ^{ + OCTLogCInfo(@"callIncomingCallback from friend %lu with audio:%d with video:%d", toxAV, (unsigned long)friendNumber, audioEnabled, videoEnabled); + if ([toxAV.delegate respondsToSelector:@selector(toxAV:receiveCallAudioEnabled:videoEnabled:friendNumber:)]) { + [toxAV.delegate toxAV:toxAV receiveCallAudioEnabled:audioEnabled videoEnabled:videoEnabled friendNumber:friendNumber]; + } + }); +} + +void callStateCallback(ToxAV *cToxAV, + uint32_t friendNumber, + TOXAV_FRIEND_CALL_STATE cState, + void *userData) +{ + OCTToxAV *toxAV = (__bridge OCTToxAV *)userData; + + dispatch_async(dispatch_get_main_queue(), ^{ + + OCTLogCInfo(@"callStateCallback from friend %d with state: %d", toxAV, friendNumber, cState); + + OCTToxAVCallState state = 0; + + if (cState & TOXAV_FRIEND_CALL_STATE_ERROR) { + state |= OCTToxAVFriendCallStateError; + } + if (cState & TOXAV_FRIEND_CALL_STATE_FINISHED) { + state |= OCTToxAVFriendCallStateFinished; + } + if (cState & TOXAV_FRIEND_CALL_STATE_SENDING_A) { + state |= OCTToxAVFriendCallStateSendingAudio; + } + if (cState & TOXAV_FRIEND_CALL_STATE_SENDING_V) { + state |= OCTToxAVFriendCallStateSendingVideo; + } + if (cState & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A) { + state |= OCTToxAVFriendCallStateAcceptingAudio; + } + if (cState & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V) { + state |= OCTToxAVFriendCallStateAcceptingVideo; + } + + if ([toxAV.delegate respondsToSelector:@selector(toxAV:callStateChanged:friendNumber:)]) { + [toxAV.delegate toxAV:toxAV callStateChanged:state friendNumber:friendNumber]; + } + }); +} + +void audioBitRateStatusCallback(ToxAV *cToxAV, + uint32_t friendNumber, + uint32_t bit_rate, + void *userData) +{ + OCTToxAV *toxAV = (__bridge OCTToxAV *)userData; + + dispatch_async(dispatch_get_main_queue(), ^{ + OCTLogCInfo(@"audioBitRateStatusCallback from friend %d bitRate: %d", toxAV, friendNumber, bit_rate); + if ([toxAV.delegate respondsToSelector:@selector(toxAV:audioBitRateStatus:forFriendNumber:)]) { + [toxAV.delegate toxAV:toxAV audioBitRateStatus:bit_rate forFriendNumber:friendNumber]; + } + }); +} + +void videoBitRateStatusCallback(ToxAV *cToxAV, + uint32_t friendNumber, + uint32_t bit_rate, + void *userData) +{ + OCTToxAV *toxAV = (__bridge OCTToxAV *)userData; + + dispatch_async(dispatch_get_main_queue(), ^{ + OCTLogCInfo(@"videoBitRateStatusCallback from friend %d bitRate: %d", toxAV, friendNumber, bit_rate); + if ([toxAV.delegate respondsToSelector:@selector(toxAV:videoBitRateStatus:forFriendNumber:)]) { + [toxAV.delegate toxAV:toxAV videoBitRateStatus:bit_rate forFriendNumber:friendNumber]; + } + }); +} + +void receiveAudioFrameCallback(ToxAV *cToxAV, + uint32_t friendNumber, + OCTToxAVPCMData *pcm, + OCTToxAVSampleCount sampleCount, + OCTToxAVChannels channels, + OCTToxAVSampleRate sampleRate, + void *userData) +{ + OCTToxAV *toxAV = (__bridge OCTToxAV *)userData; + + // TOXAUDIO: -incoming-audio- + if ([toxAV.delegate respondsToSelector:@selector(toxAV:receiveAudio:sampleCount:channels:sampleRate:friendNumber:)]) { + [toxAV.delegate toxAV:toxAV receiveAudio:pcm sampleCount:sampleCount channels:channels sampleRate:sampleRate friendNumber:friendNumber]; + } +} + +void receiveVideoFrameCallback(ToxAV *cToxAV, + uint32_t friendNumber, + OCTToxAVVideoWidth width, + OCTToxAVVideoHeight height, + OCTToxAVPlaneData *yPlane, OCTToxAVPlaneData *uPlane, OCTToxAVPlaneData *vPlane, + OCTToxAVStrideData yStride, OCTToxAVStrideData uStride, OCTToxAVStrideData vStride, + void *userData) +{ + OCTToxAV *toxAV = (__bridge OCTToxAV *)userData; + + if ([toxAV.delegate respondsToSelector:@selector(toxAV:receiveVideoFrameWithWidth:height:yPlane:uPlane:vPlane:yStride:uStride:vStride:friendNumber:)]) { + [toxAV.delegate toxAV:toxAV + receiveVideoFrameWithWidth:width height:height + yPlane:yPlane uPlane:uPlane vPlane:vPlane + yStride:yStride uStride:uStride vStride:vStride + friendNumber:friendNumber]; + } + +} diff --git a/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxAVConstants.m b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxAVConstants.m new file mode 100644 index 0000000..8dfe835 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxAVConstants.m @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTToxAVConstants.h" + +const OCTToxAVVideoBitRate kOCTToxAVVideoBitRateDisable = 0; + +NSString *const kOCTToxAVErrorDomain = @"me.dvor.objcTox.ErrorDomain"; diff --git a/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxConstants.m b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxConstants.m new file mode 100644 index 0000000..c114d06 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxConstants.m @@ -0,0 +1,25 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTToxConstants.h" +#import + +const OCTToxFriendNumber kOCTToxFriendNumberFailure = UINT32_MAX; +const OCTToxFileNumber kOCTToxFileNumberFailure = UINT32_MAX; +const OCTToxFileSize kOCTToxFileSizeUnknown = UINT64_MAX; + +NSString *const kOCTToxErrorDomain = @"me.dvor.objcTox.OCTToxErrorDomain"; + +const NSUInteger kOCTToxAddressLength = 2 * TOX_ADDRESS_SIZE; +const NSUInteger kOCTToxPublicKeyLength = 2 * TOX_PUBLIC_KEY_SIZE; +const NSUInteger kOCTToxSecretKeyLength = 2 * TOX_SECRET_KEY_SIZE; +const NSUInteger kOCTToxMaxNameLength = TOX_MAX_NAME_LENGTH; +const NSUInteger kOCTToxMaxStatusMessageLength = TOX_MAX_STATUS_MESSAGE_LENGTH; +const NSUInteger kOCTToxMaxFriendRequestLength = TOX_MAX_FRIEND_REQUEST_LENGTH; +const NSUInteger kOCTToxMaxMessageLength = TOX_MAX_MESSAGE_LENGTH; +const NSUInteger kOCTToxMaxCustomPacketSize = TOX_MAX_CUSTOM_PACKET_SIZE; +const NSUInteger kOCTToxMaxFileNameLength = TOX_MAX_FILENAME_LENGTH; + +const NSUInteger kOCTToxHashLength = TOX_HASH_LENGTH; +const NSUInteger kOCTToxFileIdLength = TOX_FILE_ID_LENGTH; diff --git a/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxEncryptSave.m b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxEncryptSave.m new file mode 100644 index 0000000..c1b7398 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxEncryptSave.m @@ -0,0 +1,272 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTToxEncryptSave.h" +#import +#import "OCTToxEncryptSaveConstants.h" +#import "OCTTox+Private.h" + +@interface OCTToxEncryptSave () + +@property (assign, nonatomic) Tox_Pass_Key *passKey; + +@end + +@implementation OCTToxEncryptSave + +#pragma mark - Lifecycle + +- (nullable instancetype)initWithPassphrase:(nonnull NSString *)passphrase + toxData:(nullable NSData *)toxData + error:(NSError *__nullable *__nullable)error +{ + self = [super init]; + + if (! self) { + return nil; + } + + TOX_ERR_KEY_DERIVATION cError; + + uint8_t salt[TOX_PASS_SALT_LENGTH]; + bool hasSalt = false; + + if (toxData) { + hasSalt = tox_get_salt(toxData.bytes, salt, NULL); + } + + if (hasSalt) { + _passKey = tox_pass_key_derive_with_salt( + (const uint8_t *)[passphrase cStringUsingEncoding:NSUTF8StringEncoding], + [passphrase lengthOfBytesUsingEncoding:NSUTF8StringEncoding], + salt, + &cError); + } + else { + _passKey = tox_pass_key_derive( + (const uint8_t *)[passphrase cStringUsingEncoding:NSUTF8StringEncoding], + [passphrase lengthOfBytesUsingEncoding:NSUTF8StringEncoding], + &cError); + } + + [OCTToxEncryptSave fillError:error withCErrorKeyDerivation:cError]; + + return (_passKey != NULL) ? self : nil; +} + +- (void)dealloc +{ + if (_passKey) { + tox_pass_key_free(_passKey); + } +} + +#pragma mark - Public class methods + ++ (BOOL)isDataEncrypted:(nonnull NSData *)data +{ + return tox_is_data_encrypted(data.bytes); +} + ++ (nullable NSData *)encryptData:(nonnull NSData *)data + withPassphrase:(nonnull NSString *)passphrase + error:(NSError *__nullable *__nullable)error +{ + NSParameterAssert(data); + NSParameterAssert(passphrase); + + return [OCTToxEncryptSave convertDataOfLength:data.length encrypt:YES withConvertBlock:^bool (uint8_t *out) { + TOX_ERR_ENCRYPTION cError; + + bool result = tox_pass_encrypt( + data.bytes, + data.length, + (const uint8_t *)[passphrase cStringUsingEncoding:NSUTF8StringEncoding], + [passphrase lengthOfBytesUsingEncoding:NSUTF8StringEncoding], + out, + &cError); + + [OCTToxEncryptSave fillError:error withCErrorEncryption:cError]; + + return result; + }]; +} + ++ (nullable NSData *)decryptData:(nonnull NSData *)data + withPassphrase:(nonnull NSString *)passphrase + error:(NSError *__nullable *__nullable)error +{ + NSParameterAssert(data); + NSParameterAssert(passphrase); + + return [OCTToxEncryptSave convertDataOfLength:data.length encrypt:NO withConvertBlock:^bool (uint8_t *out) { + TOX_ERR_DECRYPTION cError; + + bool result = tox_pass_decrypt( + data.bytes, + data.length, + (const uint8_t *)[passphrase cStringUsingEncoding:NSUTF8StringEncoding], + [passphrase lengthOfBytesUsingEncoding:NSUTF8StringEncoding], + out, + &cError); + + [OCTToxEncryptSave fillError:error withCErrorDecryption:cError]; + + return result; + }]; +} + +#pragma mark - Public instance method + +- (nullable NSData *)encryptData:(nonnull NSData *)data error:(NSError *__nullable *__nullable)error +{ + NSParameterAssert(data); + + return [OCTToxEncryptSave convertDataOfLength:data.length encrypt:YES withConvertBlock:^bool (uint8_t *out) { + TOX_ERR_ENCRYPTION cError; + + bool result = tox_pass_key_encrypt( + self.passKey, + data.bytes, + data.length, + out, + &cError); + + [OCTToxEncryptSave fillError:error withCErrorEncryption:cError]; + + return result; + }]; +} + +- (nullable NSData *)decryptData:(nonnull NSData *)data error:(NSError *__nullable *__nullable)error +{ + NSParameterAssert(data); + + return [OCTToxEncryptSave convertDataOfLength:data.length encrypt:NO withConvertBlock:^bool (uint8_t *out) { + TOX_ERR_DECRYPTION cError; + + bool result = tox_pass_key_decrypt( + self.passKey, + data.bytes, + data.length, + out, + &cError); + + [OCTToxEncryptSave fillError:error withCErrorDecryption:cError]; + + return result; + }]; +} + +#pragma mark - Private + ++ (NSData *)convertDataOfLength:(NSUInteger)dataLength + encrypt:(BOOL)encrypt + withConvertBlock:(bool (^)(uint8_t *out))convertBlock +{ + NSUInteger outLength = dataLength + (encrypt ? TOX_PASS_ENCRYPTION_EXTRA_LENGTH : -TOX_PASS_ENCRYPTION_EXTRA_LENGTH); + uint8_t *out = malloc(outLength); + + bool result = convertBlock(out); + NSData *resultData = nil; + + if (result) { + resultData = [NSData dataWithBytes:out length:outLength]; + } + + if (out) { + free(out); + } + + return resultData; +} + ++ (BOOL)fillError:(NSError **)error withCErrorKeyDerivation:(TOX_ERR_KEY_DERIVATION)cError +{ + if (! error || (cError == TOX_ERR_KEY_DERIVATION_OK)) { + return NO; + } + + switch (cError) { + case TOX_ERR_KEY_DERIVATION_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_KEY_DERIVATION_NULL: + case TOX_ERR_KEY_DERIVATION_FAILED: + *error = [OCTTox createErrorWithCode:OCTToxEncryptSaveKeyDerivationErrorFailed + description:@"Cannot create key from given passphrase" + failureReason:nil]; + break; + } + + + return YES; +} + ++ (BOOL)fillError:(NSError **)error withCErrorEncryption:(TOX_ERR_ENCRYPTION)cError +{ + if (! error || (cError == TOX_ERR_ENCRYPTION_OK)) { + return NO; + } + + OCTToxEncryptSaveEncryptionError code; + NSString *description = @"Encryption failed"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_ENCRYPTION_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_ENCRYPTION_NULL: + code = OCTToxEncryptSaveEncryptionErrorNull; + failureReason = @"Some input data was empty."; + break; + case TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED: + case TOX_ERR_ENCRYPTION_FAILED: + code = OCTToxEncryptSaveEncryptionErrorFailed; + failureReason = @"Encryption failed, please report"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + ++ (BOOL)fillError:(NSError **)error withCErrorDecryption:(TOX_ERR_DECRYPTION)cError +{ + if (! error || (cError == TOX_ERR_DECRYPTION_OK)) { + return NO; + } + + OCTToxEncryptSaveDecryptionError code; + NSString *description = @"Decryption failed"; + NSString *failureReason = nil; + + switch (cError) { + case TOX_ERR_DECRYPTION_OK: + NSAssert(NO, @"We shouldn't be here"); + return NO; + case TOX_ERR_DECRYPTION_NULL: + code = OCTToxEncryptSaveDecryptionErrorNull; + failureReason = @"Some input data was empty."; + break; + case TOX_ERR_DECRYPTION_BAD_FORMAT: + code = OCTToxEncryptSaveDecryptionErrorBadFormat; + failureReason = @"The input data has bad format"; + break; + case TOX_ERR_DECRYPTION_INVALID_LENGTH: + case TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED: + case TOX_ERR_DECRYPTION_FAILED: + code = OCTToxEncryptSaveDecryptionErrorFailed; + failureReason = @"Decryption failed, passphrase is incorrect or data is corrupt"; + break; + } + + *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; + + return YES; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxOptions+Private.h b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxOptions+Private.h new file mode 100644 index 0000000..c0d01cc --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxOptions+Private.h @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTToxOptions.h" + +@interface OCTToxOptions (Private) + +@property (nonatomic, assign, readonly) struct Tox_Options *options; + +@end diff --git a/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxOptions.m b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxOptions.m new file mode 100644 index 0000000..ad8fa87 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Private/Wrapper/OCTToxOptions.m @@ -0,0 +1,212 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTToxOptions+Private.h" +#import + +@interface OCTToxOptions () + +@property (nonatomic, assign, readonly) struct Tox_Options *options; + +// Used to retain proxy_host option. +@property (nonatomic, copy) NSString *proxyHostStorage; + +@end + +@implementation OCTToxOptions + +#pragma mark - Lifecycle + +- (instancetype)init +{ + self = [super init]; + + if (! self) { + return nil; + } + + _options = tox_options_new(NULL); + + return self; +} + +- (void)dealloc +{ + tox_options_free(_options); +} + +#pragma mark - Description + +- (NSString *)description +{ + return [NSString stringWithFormat:@"OCTToxOptions:\n" + @"ipv6Enabled %d\n" + @"udpEnabled %d\n" + @"localDiscoveryEnabled %d\n" + @"proxyType %lu\n" + @"proxyHost %@\n" + @"proxyPort %d\n" + @"startPort %d\n" + @"endPort %d\n" + @"tcpPort %d\n" + @"holePunchingEnabled %d\n", + self.ipv6Enabled, + self.udpEnabled, + self.localDiscoveryEnabled, + self.proxyType, + self.proxyHost, + self.proxyPort, + self.startPort, + self.endPort, + self.tcpPort, + self.holePunchingEnabled]; +} + +#pragma mark - Properties + +- (BOOL)ipv6Enabled +{ + return tox_options_get_ipv6_enabled(self.options); +} + +- (void)setIpv6Enabled:(BOOL)enabled +{ + tox_options_set_ipv6_enabled(self.options, enabled); +} + +- (BOOL)udpEnabled +{ + return tox_options_get_udp_enabled(self.options); +} + +- (void)setUdpEnabled:(BOOL)enabled +{ + tox_options_set_udp_enabled(self.options, enabled); +} + +- (BOOL)localDiscoveryEnabled +{ + return tox_options_get_local_discovery_enabled(self.options); +} + +- (void)setLocalDiscoveryEnabled:(BOOL)enabled +{ + tox_options_set_local_discovery_enabled(self.options, enabled); +} + +- (OCTToxProxyType)proxyType +{ + switch (tox_options_get_proxy_type(self.options)) { + case TOX_PROXY_TYPE_NONE: + return OCTToxProxyTypeNone; + case TOX_PROXY_TYPE_HTTP: + return OCTToxProxyTypeHTTP; + case TOX_PROXY_TYPE_SOCKS5: + return OCTToxProxyTypeSocks5; + } +} + +- (void)setProxyType:(OCTToxProxyType)type +{ + switch (type) { + case OCTToxProxyTypeNone: + tox_options_set_proxy_type(self.options, TOX_PROXY_TYPE_NONE); + break; + case OCTToxProxyTypeHTTP: + tox_options_set_proxy_type(self.options, TOX_PROXY_TYPE_HTTP); + break; + case OCTToxProxyTypeSocks5: + tox_options_set_proxy_type(self.options, TOX_PROXY_TYPE_SOCKS5); + break; + } +} + +- (NSString *)proxyHost +{ + const char *cHost = tox_options_get_proxy_host(self.options); + + if (cHost) { + return [NSString stringWithCString:cHost encoding:NSUTF8StringEncoding]; + } + + return nil; +} + +- (void)setProxyHost:(NSString *)host +{ + self.proxyHostStorage = host; + tox_options_set_proxy_host(self.options, self.proxyHostStorage.UTF8String); +} + +- (uint16_t)proxyPort +{ + return tox_options_get_proxy_port(self.options); +} + +- (void)setProxyPort:(uint16_t)port +{ + tox_options_set_proxy_port(self.options, port); +} + +- (uint16_t)startPort +{ + return tox_options_get_start_port(self.options); +} + +- (void)setStartPort:(uint16_t)port +{ + tox_options_set_start_port(self.options, port); +} + +- (uint16_t)endPort +{ + return tox_options_get_end_port(self.options); +} + +- (void)setEndPort:(uint16_t)port +{ + tox_options_set_end_port(self.options, port); +} + +- (uint16_t)tcpPort +{ + return tox_options_get_tcp_port(self.options); +} + +- (void)setTcpPort:(uint16_t)port +{ + tox_options_set_tcp_port(self.options, port); +} + +- (BOOL)holePunchingEnabled +{ + return tox_options_get_hole_punching_enabled(self.options); +} + +- (void)setHolePunchingEnabled:(BOOL)enabled +{ + tox_options_set_hole_punching_enabled(self.options, enabled); +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone +{ + OCTToxOptions *options = [[[self class] allocWithZone:zone] init]; + + options.ipv6Enabled = self.ipv6Enabled; + options.udpEnabled = self.udpEnabled; + options.localDiscoveryEnabled = self.localDiscoveryEnabled; + options.proxyType = self.proxyType; + options.proxyHost = self.proxyHost; + options.proxyPort = self.proxyPort; + options.startPort = self.startPort; + options.endPort = self.endPort; + options.tcpPort = self.tcpPort; + options.holePunchingEnabled = self.holePunchingEnabled; + + return options; +} + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Configuration/OCTDefaultFileStorage.h b/local_pod_repo/objcTox/Classes/Public/Manager/Configuration/OCTDefaultFileStorage.h new file mode 100644 index 0000000..8657deb --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Configuration/OCTDefaultFileStorage.h @@ -0,0 +1,39 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTFileStorageProtocol.h" + +/** + * Default storage for files. It has following directory structure: + * /baseDirectory/saveFileName.tox - tox save file name. You can specify it in appropriate method. + * /baseDirectory/database - database with chats, messages and related stuff. + * /baseDirectory/database.encryptionkey - encryption key for database. + * /baseDirectory/files/ - downloaded and uploaded files will be stored here. + * /baseDirectory/avatars/ - avatars will be stored here. + * /temporaryDirectory/ - temporary files will be stored here. + */ +@interface OCTDefaultFileStorage : NSObject + +/** + * Creates default file storage. Will use "save.tox" as default save file name. + * + * @param baseDirectory Base directory to use. It will have "files", "avatars" subdirectories. + * @param temporaryDirectory All temporary files will be stored here. You can pass NSTemporaryDirectory() here. + */ +- (instancetype)initWithBaseDirectory:(NSString *)baseDirectory temporaryDirectory:(NSString *)temporaryDirectory; + +/** + * Creates default file storage. + * + * @param saveFileName Name of file to store tox save data. ".tox" extension will be appended to the name. + * @param baseDirectory Base directory to use. It will have "files", "avatars" subdirectories. + * @param temporaryDirectory All temporary files will be stored here. You can pass NSTemporaryDirectory() here. + */ +- (instancetype)initWithToxSaveFileName:(NSString *)saveFileName + baseDirectory:(NSString *)baseDirectory + temporaryDirectory:(NSString *)temporaryDirectory; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Configuration/OCTFileStorageProtocol.h b/local_pod_repo/objcTox/Classes/Public/Manager/Configuration/OCTFileStorageProtocol.h new file mode 100644 index 0000000..7736a0e --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Configuration/OCTFileStorageProtocol.h @@ -0,0 +1,66 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol OCTFileStorageProtocol + +@required + +/** + * Returns path where tox save data will be stored. Save file should have ".tox" extension. + * See Tox STS for more information: https://github.com/Tox/Tox-STS + * + * @return Full path to the file for loading/saving tox data. + * + * @warning Path should be file path. The file can be rewritten at any time while OCTManager is alive. + */ +@property (readonly) NSString *pathForToxSaveFile; + +/** + * Returns file path for database to be stored in. Must be a file path, not directory. + * In database will be stored chats, messages and related stuff. + * + * @return Full path to the file for the database. + * + * @warning Path should be file path. The file can be rewritten at any time while OCTManager is alive. + */ +@property (readonly) NSString *pathForDatabase; + +/** + * Returns file path for database encryption key to be stored in. Must be a file path, not a directory. + * + * @return Full path to the file to store database encryption key. + * + * @warning Path should be file path. The file can be rewritten at any time while OCTManager is alive. + */ +@property (readonly) NSString *pathForDatabaseEncryptionKey; + +/** + * Returns path where all downloaded files will be stored. + * + * @return Full path to the directory with downloaded files. + */ +@property (readonly) NSString *pathForDownloadedFilesDirectory; + +/** + * Returns path where all uploaded files will be stored. + * + * @return Full path to the directory with uploaded files. + */ +@property (readonly) NSString *pathForUploadedFilesDirectory; + +/** + * Returns path where temporary files will be stored. This directory can be cleaned on relaunch of app. + * You can use NSTemporaryDirectory() here. + * + * @return Full path to the directory with temporary files. + */ +@property (readonly) NSString *pathForTemporaryFilesDirectory; + +@end + +NS_ASSUME_NONNULL_END diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Configuration/OCTManagerConfiguration.h b/local_pod_repo/objcTox/Classes/Public/Manager/Configuration/OCTManagerConfiguration.h new file mode 100644 index 0000000..ffa08bf --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Configuration/OCTManagerConfiguration.h @@ -0,0 +1,60 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTFileStorageProtocol.h" +#import "OCTToxOptions.h" + +/** + * Configuration for OCTManager. + */ +@interface OCTManagerConfiguration : NSObject + +/** + * File storage to use. + * + * Default values: OCTDefaultFileStorage will be used with following parameters: + * - tox save file is stored at "{app document directory}/me.dvor.objcTox/save.tox" + * - database file is stored at "{app document directory}/me.dvor.objcTox/database" + * - database encryption key file is stored at "{app document directory}/me.dvor.objcTox/database.encryptionkey" + * - downloaded files are stored at "{app document directory}/me.dvor.objcTox/downloads" + * - uploaded files are stored at "{app document directory}/me.dvor.objcTox/uploads" + * - avatars are stored at "{app document directory}/me.dvor.objcTox/avatars" + * - temporary files are stored at NSTemporaryDirectory() + */ +@property (strong, nonatomic, nonnull) id fileStorage; + +/** + * Options for tox to use. + */ +@property (strong, nonatomic, nonnull) OCTToxOptions *options; + +/** + * If this parameter is set, tox save file will be copied from given path. + * You can set this property to import tox save from some other location. + * + * Default value: nil. + */ +@property (strong, nonatomic, nullable) NSString *importToxSaveFromPath; + +/** + * When faux offline messaging is enabled, it is allowed to send message to + * offline friends. In that case message would be stored in database and resend + * when friend comes online. + * + * Default value: YES. + */ +@property (assign, nonatomic) BOOL useFauxOfflineMessaging; + +/** + * This is default configuration for manager. + * Each property of OCTManagerConfiguration has "Default value" field. This method returns configuration + * with those default values set. + * + * @return Default configuration for OCTManager. + */ ++ (nonnull instancetype)defaultConfiguration; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/OCTManager.h b/local_pod_repo/objcTox/Classes/Public/Manager/OCTManager.h new file mode 100644 index 0000000..bbaee1d --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/OCTManager.h @@ -0,0 +1,95 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTToxConstants.h" +#import "OCTManagerConstants.h" + +NS_ASSUME_NONNULL_BEGIN +@class OCTManagerConfiguration; + +@protocol OCTSubmanagerBootstrap; +@protocol OCTSubmanagerCalls; +@protocol OCTSubmanagerChats; +@protocol OCTSubmanagerFiles; +@protocol OCTSubmanagerFriends; +@protocol OCTSubmanagerObjects; +@protocol OCTSubmanagerUser; + +@protocol OCTManager + +/** + * Submanager responsible for connecting to other nodes. + */ +@property (strong, nonatomic, readonly) id bootstrap; + +/** + * Submanager with all video/calling methods. + */ +@property (strong, nonatomic, readonly) id calls; + +/** + * Submanager with all chats methods. + */ +@property (strong, nonatomic, readonly) id chats; + +/** + * Submanager with all files methods. + */ +@property (strong, nonatomic, readonly) id files; + +/** + * Submanager with all friends methods. + */ +@property (strong, nonatomic, readonly) id friends; + +/** + * Submanager with all objects methods. + */ +@property (strong, nonatomic, readonly) id objects; + +/** + * Submanager with all user methods. + */ +@property (strong, nonatomic, readonly) id user; + +/** + * Configuration used by OCTManager. + * + * @return Copy of configuration used by manager. + */ +- (OCTManagerConfiguration *)configuration; + +/** + * Copies tox save file to temporary directory and return path to it. + * + * @param error NSFileManager error in case if file cannot be copied. + * + * @return Temporary path of current tox save file. + */ +- (nullable NSString *)exportToxSaveFileAndReturnError:(NSError *__nullable *__nullable)error; + +/** + * Set password to encrypt tox save file and database. + * + * @param newPassword New password used to encrypt tox save file and database. + * @param oldPassword Old password. + * + * @return YES on success, NO on failure (if old password doesn't match). + */ +- (BOOL)changeEncryptPassword:(nonnull NSString *)newPassword oldPassword:(nonnull NSString *)oldPassword; + +/** + * Checks if manager is encrypted with given password. + * + * @param password Password to verify. + * + * @return YES if manager is encrypted with given password, NO otherwise. + */ +- (BOOL)isManagerEncryptedWithPassword:(nonnull NSString *)password; + +@end + +NS_ASSUME_NONNULL_END diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/OCTManagerConstants.h b/local_pod_repo/objcTox/Classes/Public/Manager/OCTManagerConstants.h new file mode 100644 index 0000000..c49076e --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/OCTManagerConstants.h @@ -0,0 +1,326 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTToxConstants.h" + +/** + * Maximum avatar size as defined in + * https://tox.gitbooks.io/tox-client-standard/content/user_identification/avatar.html + */ +static const OCTToxFileSize kOCTManagerMaxAvatarSize = 65536; + +typedef NS_ENUM(NSInteger, OCTFetchRequestType) { + OCTFetchRequestTypeFriend, + OCTFetchRequestTypeFriendRequest, + OCTFetchRequestTypeChat, + OCTFetchRequestTypeCall, + OCTFetchRequestTypeMessageAbstract, +}; + +typedef NS_ENUM(NSInteger, OCTMessageFileType) { + /** + * File is incoming and is waiting confirmation of user to be downloaded. + * Please start loading or cancel it with <> method. + */ + OCTMessageFileTypeWaitingConfirmation, + + /** + * File is downloading or uploading. + */ + OCTMessageFileTypeLoading, + + /** + * Downloading or uploading of file is paused. + */ + OCTMessageFileTypePaused, + + /** + * Downloading or uploading of file was canceled. + */ + OCTMessageFileTypeCanceled, + + /** + * File is fully loaded. + * In case of incoming file now it can be shown to user. + */ + OCTMessageFileTypeReady, +}; + +typedef NS_ENUM(NSInteger, OCTMessageFilePausedBy) { + /** + * File transfer isn't paused. + */ + OCTMessageFilePausedByNone = 0, + + /** + * File transfer is paused by user. + */ + OCTMessageFilePausedByUser = 1 << 0, + + /** + * File transfer is paused by friend. + */ + OCTMessageFilePausedByFriend = 1 << 1, +}; + +typedef NS_ENUM(NSInteger, OCTMessageCallEvent) { + /** + * Call was answered. + */ + OCTMessageCallEventAnswered, + + /** + * Call was unanswered. + */ + OCTMessageCallEventUnanswered, +}; + +typedef NS_ENUM(NSInteger, OCTCallStatus) { + /** + * Call is currently ringing. + */ + OCTCallStatusRinging, + + /** + * Call is currently dialing a chat. + */ + OCTCallStatusDialing, + + /** + * Call is currently active in session. + */ + OCTCallStatusActive, +}; + +typedef NS_OPTIONS(NSInteger, OCTCallPausedStatus) { + /** + * Call is not paused + */ + OCTCallPausedStatusNone = 0, + + /** + * Call is paused by the user + */ + OCTCallPausedStatusByUser = 1 << 0, + + /** + * Call is paused by friend + */ + OCTCallPausedStatusByFriend = 1 << 1, +}; + +extern NSString *const kOCTManagerErrorDomain; + +typedef NS_ENUM(NSInteger, OCTManagerInitError) { + /** + * Cannot create symmetric key from given passphrase. + */ + OCTManagerInitErrorPassphraseFailed, + + /** ---------------------------------------- */ + + /** + * Cannot copy tox save at `importToxSaveFromPath` path. + */ + OCTManagerInitErrorCannotImportToxSave, + + /** ---------------------------------------- */ + + /** + * Cannot create encryption key. + */ + OCTManagerInitErrorDatabaseKeyCannotCreateKey, + + /** + * Cannot read encryption key. + */ + OCTManagerInitErrorDatabaseKeyCannotReadKey, + + /** + * Old unencrypted database was found and migration attempt was made. However migration failed for some reason. + * + * You can check NSLocalizedDescriptionKey and NSLocalizedFailureReasonErrorKey for more info. + */ + OCTManagerInitErrorDatabaseKeyMigrationToEncryptedFailed, + + /** + * Cannot decrypt database key file. + * Some input data was empty. + */ + OCTManagerInitErrorDatabaseKeyDecryptNull, + + /** + * Cannot decrypt database key file. + * The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted). + */ + OCTManagerInitErrorDatabaseKeyDecryptBadFormat, + + /** + * Cannot decrypt database key file. + * The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect. + */ + OCTManagerInitErrorDatabaseKeyDecryptFailed, + + /** ---------------------------------------- */ + + /** + * Cannot decrypt tox save file. + * Some input data was empty. + */ + OCTManagerInitErrorToxFileDecryptNull, + + /** + * Cannot decrypt tox save file. + * The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted). + */ + OCTManagerInitErrorToxFileDecryptBadFormat, + + /** + * Cannot decrypt tox save file. + * The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect. + */ + OCTManagerInitErrorToxFileDecryptFailed, + + /** ---------------------------------------- */ + + /** + * Cannot create tox. + * Unknown error occurred. + */ + OCTManagerInitErrorCreateToxUnknown, + + /** + * Cannot create tox. + * Was unable to allocate enough memory to store the internal structures for the Tox object. + */ + OCTManagerInitErrorCreateToxMemoryError, + + /** + * Cannot create tox. + * Was unable to bind to a port. This may mean that all ports have already been bound, + * e.g. by other Tox instances, or it may mean a permission error. + */ + OCTManagerInitErrorCreateToxPortAlloc, + + /** + * Cannot create tox. + * proxyType was invalid. + */ + OCTManagerInitErrorCreateToxProxyBadType, + + /** + * Cannot create tox. + * proxyAddress had an invalid format or was nil (while proxyType was set). + */ + OCTManagerInitErrorCreateToxProxyBadHost, + + /** + * Cannot create tox. + * proxyPort was invalid. + */ + OCTManagerInitErrorCreateToxProxyBadPort, + + /** + * Cannot create tox. + * The proxy host passed could not be resolved. + */ + OCTManagerInitErrorCreateToxProxyNotFound, + + /** + * Cannot create tox. + * The saved data to be loaded contained an encrypted save. + */ + OCTManagerInitErrorCreateToxEncrypted, + + /** + * Cannot create tox. + * The data format was invalid. This can happen when loading data that was + * saved by an older version of Tox, or when the data has been corrupted. + * When loading from badly formatted data, some data may have been loaded, + * and the rest is discarded. Passing an invalid length parameter also + * causes this error. + */ + OCTManagerInitErrorCreateToxBadFormat, +}; + +typedef NS_ENUM(NSInteger, OCTSetUserAvatarError) { + /** + * User avatar size is too big. It should be <= kOCTManagerMaxAvatarSize. + */ + OCTSetUserAvatarErrorTooBig, +}; + +typedef NS_ENUM(NSInteger, OCTSendFileError) { + /** + * Internal error occured while sending file. + * Check logs for more info. + */ + OCTSendFileErrorInternalError, + + /** + * Cannot read file. + */ + OCTSendFileErrorCannotReadFile, + + /** + * Cannot save send file to uploads folder. + */ + OCTSendFileErrorCannotSaveFileToUploads, + + /** + * Friend to send file to was not found. + */ + OCTSendFileErrorFriendNotFound, + + /** + * Friend is not connected at the moment. + */ + OCTSendFileErrorFriendNotConnected, + + /** + * Filename length exceeded kOCTToxMaxFileNameLength bytes. + */ + OCTSendFileErrorNameTooLong, + + /** + * Too many ongoing transfers. The maximum number of concurrent file transfers + * is 256 per friend per direction (sending and receiving). + */ + OCTSendFileErrorTooMany, +}; + +typedef NS_ENUM(NSInteger, OCTAcceptFileError) { + /** + * Internal error occured while sending file. + * Check logs for more info. + */ + OCTAcceptFileErrorInternalError, + + /** + * File is not available for writing. + */ + OCTAcceptFileErrorCannotWriteToFile, + + /** + * Friend to send file to was not found. + */ + OCTAcceptFileErrorFriendNotFound, + + /** + * Friend is not connected at the moment. + */ + OCTAcceptFileErrorFriendNotConnected, + + /** + * Wrong message specified (with no friend, no file or not waiting for confirmation). + */ + OCTAcceptFileErrorWrongMessage, +}; + +typedef NS_ENUM(NSInteger, OCTFileTransferError) { + /** + * Wrong message specified (with no file). + */ + OCTFileTransferErrorWrongMessage, +}; diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/OCTManagerFactory.h b/local_pod_repo/objcTox/Classes/Public/Manager/OCTManagerFactory.h new file mode 100644 index 0000000..ff059e8 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/OCTManagerFactory.h @@ -0,0 +1,35 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class OCTManagerConfiguration; +@protocol OCTManager; + +@interface OCTManagerFactory : NSObject + +/** + * Create manager with configuration. There is no way to change configuration after init method. If you'd like to + * change it you have to recreate OCTManager. + * + * @param configuration Configuration to be used. + * @param encryptPassword Password used to encrypt/decrypt tox save file and database. + * Tox file will be encrypted automatically if it wasn't encrypted before. + * @param successBlock Block called on success with initialized OCTManager. Will be called on main thread. + * @param failureBlock Block called on failure. Will be called on main thread. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTManagerInitError for all error codes. + * + * @warning This method should be called on main thread. + */ ++ (void)managerWithConfiguration:(OCTManagerConfiguration *)configuration + encryptPassword:(NSString *)encryptPassword + successBlock:(nullable void (^)(id manager))successBlock + failureBlock:(nullable void (^)(NSError *error))failureBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTCall.h b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTCall.h new file mode 100644 index 0000000..498af2a --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTCall.h @@ -0,0 +1,84 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTChat.h" +#import "OCTFriend.h" +#import "OCTManagerConstants.h" + +/** + * Please note that all properties of this object are readonly. + * All management of calls are handeled through OCTCallSubmanagerCalls. + */ + +@interface OCTCall : OCTObject + +/** + * OCTChat related session with the call. + **/ +@property (nonnull) OCTChat *chat; + +/** + * Call status + **/ +@property OCTCallStatus status; + +/** + * This property contains paused status for Active call. + */ +@property OCTCallPausedStatus pausedStatus; + +/** + * The friend who started the call. + * Nil if the you started the call yourself. + **/ +@property (nullable) OCTFriend *caller; + +/** + * Video device is active for this call + */ +@property BOOL videoIsEnabled; + +/** + * Friend is sending audio. + */ +@property BOOL friendSendingAudio; + +/** + * Friend is sending video. + */ +@property BOOL friendSendingVideo; + +/** + * Friend is accepting audio. + */ +@property BOOL friendAcceptingAudio; + +/** + * Friend is accepting video. + */ +@property BOOL friendAcceptingVideo; + +/** + * Call duration + **/ +@property NSTimeInterval callDuration; + +/** + * The on hold start interval when call was put on hold. + */ +@property NSTimeInterval onHoldStartInterval; + +/** + * The date when the call was put on hold. + */ +- (nullable NSDate *)onHoldDate; + +/** + * Indicates if call is outgoing or incoming. + * In case if it is incoming you can check `caller` property for friend. + **/ +- (BOOL)isOutgoing; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTChat.h b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTChat.h new file mode 100644 index 0000000..733ce16 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTChat.h @@ -0,0 +1,74 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTObject.h" +#import "OCTFriend.h" + +@class OCTMessageAbstract; + +/** + * Please note that all properties of this object are readonly. + * You can change some of them only with appropriate method in OCTSubmanagerObjects. + */ +@interface OCTChat : OCTObject + +/** + * Array with OCTFriends that participate in this chat. + */ +@property (nonnull) RLMArray *friends; + +/** + * The latest message that was send or received. + */ +@property (nullable) OCTMessageAbstract *lastMessage; + +/** + * This property can be used for storing entered text that wasn't send yet. + * + * To change please use OCTSubmanagerObjects method. + * + * May be empty. + */ +@property (nullable) NSString *enteredText; + +/** + * This property stores last date interval when chat was read. + * `hasUnreadMessages` method use lastReadDateInterval to determine if there are unread messages. + * + * To change please use OCTSubmanagerObjects method. + */ +@property NSTimeInterval lastReadDateInterval; + +/** + * Date interval of lastMessage or chat creationDate if there is no last message. + * + * This property is workaround to support sorting. Should be replaced with keypath + * lastMessage.dateInterval sorting in future. + * See https://github.com/realm/realm-cocoa/issues/1277 + */ +@property NSTimeInterval lastActivityDateInterval; + +/** + * The date when chat was read last time. + */ +- (nullable NSDate *)lastReadDate; + +/** + * Returns date of lastMessage or chat creationDate if there is no last message. + */ +- (nullable NSDate *)lastActivityDate; + +/** + * If there are unread messages in chat YES is returned. All messages that have date later than lastReadDateInterval + * are considered as unread. + * + * Please note that you have to set lastReadDateInterval to make this method work. + * + * @return YES if there are unread messages, NO otherwise. + */ +- (BOOL)hasUnreadMessages; + +@end + +RLM_ARRAY_TYPE(OCTChat) diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTFriend.h b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTFriend.h new file mode 100644 index 0000000..9c2f62a --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTFriend.h @@ -0,0 +1,110 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTObject.h" +#import "OCTToxConstants.h" + +/** + * Class that represents friend (or just simply contact). + * + * Please note that all properties of this object are readonly. + * You can change some of them only with appropriate method in OCTSubmanagerObjects. + */ +@interface OCTFriend : OCTObject + +/** + * Friend number that is unique for Tox. + * In case if friend will be deleted, old id may be reused on new friend creation. + */ +@property OCTToxFriendNumber friendNumber; + +/** + * Nickname of friend. + * + * When friend is created it is set to the publicKey. + * It is set to name when obtaining name for the first time. + * After that name is unchanged (unless it is changed explicitly). + * + * To change please use OCTSubmanagerObjects method. + */ +@property (nonnull) NSString *nickname; + +/** + * Public key of a friend, is kOCTToxPublicKeyLength length. + * Is constant, cannot be changed. + */ +@property (nonnull) NSString *publicKey; + +/** + * Name of a friend. + * + * May be empty. + */ +@property (nullable) NSString *name; + +/** + * Status message of a friend. + * + * May be empty. + */ +@property (nullable) NSString *statusMessage; + +/** + * Status message of a friend. + */ +@property OCTToxUserStatus status; + +/** + * Property specifies if friend is connected. For type of connection you can check + * connectionStatus property. + */ +@property BOOL isConnected; + +/** + * Connection status message of a friend. + */ +@property OCTToxConnectionStatus connectionStatus; + +/** + * The date interval when friend was last seen online. + * Contains actual information in case if friend has connectionStatus offline. + */ +@property NSTimeInterval lastSeenOnlineInterval; + +/** + * Whether friend is typing now in current chat. + */ +@property BOOL isTyping; + +/** + * Data representation of friend's avatar. + */ +@property (nullable) NSData *avatarData; + +/** + * The date when friend was last seen online. + * Contains actual information in case if friend has connectionStatus offline. + */ +- (nullable NSDate *)lastSeenOnline; + +/** + * Push Token of a friend. + * + * May be empty. + */ +@property (nullable) NSString *pushToken; + +/** + * Indicate if a friend has msgV3 Capability. + */ +@property BOOL msgv3Capability; + +/** + * Friend's capabilities. A 64 bit unsigned integer. + */ +@property (nonnull) NSString *capabilities2; + +@end + +RLM_ARRAY_TYPE(OCTFriend) diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTFriendRequest.h b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTFriendRequest.h new file mode 100644 index 0000000..266c59d --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTFriendRequest.h @@ -0,0 +1,35 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTObject.h" + +/** + * Please note that all properties of this object are readonly. + * You can change some of them only with appropriate method in OCTSubmanagerObjects. + */ +@interface OCTFriendRequest : OCTObject + +/** + * Public key of a friend. + */ +@property (nonnull) NSString *publicKey; + +/** + * Message that friend did send with friend request. + */ +@property (nullable) NSString *message; + +/** + * Date interval when friend request was received (since 1970). + */ +@property NSTimeInterval dateInterval; + +/** + * Date when friend request was received. + */ +- (nonnull NSDate *)date; + +@end + +RLM_ARRAY_TYPE(OCTFriendRequest) diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTMessageAbstract.h b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTMessageAbstract.h new file mode 100644 index 0000000..3efcc73 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTMessageAbstract.h @@ -0,0 +1,67 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTObject.h" + +@class OCTFriend; +@class OCTChat; +@class OCTMessageText; +@class OCTMessageFile; +@class OCTMessageCall; + +/** + * An abstract message that represents one chunk of chat history. + * + * Please note that all properties of this object are readonly. + * You can change some of them only with appropriate method in OCTSubmanagerObjects. + */ +@interface OCTMessageAbstract : OCTObject + +/** + * The date interval when message was send/received. + */ +@property NSTimeInterval dateInterval; + +/** + * Unixtimestamp when messageV3 was sent or 0. + */ +@property NSTimeInterval tssent; + +/** + * Unixtimestamp when messageV3 was received or 0. + */ +@property NSTimeInterval tsrcvd; + +/** + * Unique identifier of friend that have send message. + * If the message if outgoing senderUniqueIdentifier is nil. + */ +@property (nullable) NSString *senderUniqueIdentifier; + +/** + * The chat message message belongs to. + */ +@property (nonnull) NSString *chatUniqueIdentifier; + +/** + * Message has one of the following properties. + */ +@property (nullable) OCTMessageText *messageText; +@property (nullable) OCTMessageFile *messageFile; +@property (nullable) OCTMessageCall *messageCall; + +/** + * The date when message was send/received. + */ +- (nonnull NSDate *)date; + +/** + * Indicates if message is outgoing or incoming. + * In case if it is incoming you can check `sender` property for message sender. + */ +- (BOOL)isOutgoing; + +@end + +RLM_ARRAY_TYPE(OCTMessageAbstract) diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTMessageCall.h b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTMessageCall.h new file mode 100644 index 0000000..ebf6e8b --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTMessageCall.h @@ -0,0 +1,20 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTObject.h" +#import "OCTManagerConstants.h" + +@interface OCTMessageCall : OCTObject + +/** + * The length of the call in seconds. + **/ +@property NSTimeInterval callDuration; + +/** + * The type of message call. + **/ +@property OCTMessageCallEvent callEvent; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTMessageFile.h b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTMessageFile.h new file mode 100644 index 0000000..c85b344 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTMessageFile.h @@ -0,0 +1,61 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTObject.h" +#import "OCTToxConstants.h" +#import "OCTManagerConstants.h" + +/** + * Message that contains file, that has been send/received. Represents pending, canceled and loaded files. + * + * Please note that all properties of this object are readonly. + * You can change some of them only with appropriate method in OCTSubmanagerObjects. + */ +@interface OCTMessageFile : OCTObject + +/** + * The current state of file. + */ +@property OCTMessageFileType fileType; + +/** + * In case if fileType is equal to OCTMessageFileTypePaused this property will contain information + * by whom file transfer was paused. + */ +@property OCTMessageFilePausedBy pausedBy; + +/** + * Size of file in bytes. + */ +@property OCTToxFileSize fileSize; + +/** + * Name of the file as specified by sender. Note that actual fileName in path + * may differ from this fileName. + */ +@property (nullable) NSString *fileName; + +/** + * Uniform Type Identifier of file. + */ +@property (nullable) NSString *fileUTI; + +/** + * Path of file on disk. If you need fileName to show to user please use + * `fileName` property. filePath has it's own random fileName. + * + * In case of incoming file filePath will have value only if fileType is OCTMessageFileTypeReady + */ +- (nullable NSString *)filePath; + +// Properties and methods below are for internal use. +// Do not use them or rely on them. They may change in any moment. + +@property int internalFileNumber; +@property (nullable) NSString *internalFilePath; +- (void)internalSetFilePath:(nullable NSString *)path; + +@end + +RLM_ARRAY_TYPE(OCTMessageFile) diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTMessageText.h b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTMessageText.h new file mode 100644 index 0000000..b2e0206 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTMessageText.h @@ -0,0 +1,49 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTObject.h" +#import "OCTToxConstants.h" + +@class OCTToxConstants; + +/** + * Simple text message. + * + * Please note that all properties of this object are readonly. + * You can change some of them only with appropriate method in OCTSubmanagerObjects. + */ +@interface OCTMessageText : OCTObject + +/** + * The text of the message. + */ +@property (nullable) NSString *text; + +/** + * Indicate if message is delivered. Actual only for outgoing messages. + */ +@property BOOL isDelivered; + +/** + * Type of the message. + */ +@property OCTToxMessageType type; + +@property OCTToxMessageId messageId; + +/** + * msgV3 Hash as uppercase Hexstring. + * + * May be empty if message is not v3. + */ +@property (nullable) NSString *msgv3HashHex; + +/** + * Indicate if message has triggered a push notification. + */ +@property BOOL sentPush; + +@end + +RLM_ARRAY_TYPE(OCTMessageText) diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTObject.h b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTObject.h new file mode 100644 index 0000000..9930b40 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTObject.h @@ -0,0 +1,33 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +/** + * Please note that all properties of this object are readonly. + * You can change some of them only with appropriate method in OCTSubmanagerObjects. + */ +@interface OCTObject : RLMObject + +/** + * The unique identifier of object. + */ +@property NSString *uniqueIdentifier; + +/** + * Returns a string that represents the contents of the receiving class. + */ +- (NSString *)description; + +/** + * Returns a Boolean value that indicates whether the receiver and a given object are equal. + */ +- (BOOL)isEqual:(id)object; + +/** + * Returns an integer that can be used as a table address in a hash table structure. + */ +- (NSUInteger)hash; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTView.h b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTView.h new file mode 100644 index 0000000..6dac02f --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Objects/OCTView.h @@ -0,0 +1,17 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "TargetConditionals.h" + +#if TARGET_OS_IPHONE + +#import +typedef UIView OCTView; + +#else + +#import +typedef NSView OCTView; + +#endif diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerBootstrap.h b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerBootstrap.h new file mode 100644 index 0000000..d7ba9fd --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerBootstrap.h @@ -0,0 +1,57 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTToxConstants.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol OCTSubmanagerBootstrap + +/** + * Add node to bootstrap with. + * + * This will NOT start bootstrapping. To start actual bootstrapping set all desired nodes + * and call `bootstrap` method. + * + * @param ipv4Host IPv4 hostname or an IP address of the node. + * @param ipv6Host IPv4 hostname or an IP address of the node. + * @param udpPort The port on the host on which the bootstrap Tox instance is listening. + * @param tcpPorts NSNumbers with OCTToxPorts on which the TCP relay is listening. + * @param publicKey Public key of the node (of kOCTToxPublicKeyLength length). + */ +- (void)addNodeWithIpv4Host:(nullable NSString *)ipv4Host + ipv6Host:(nullable NSString *)ipv6Host + udpPort:(OCTToxPort)udpPort + tcpPorts:(NSArray *)tcpPorts + publicKey:(NSString *)publicKey; + +/** + * Add nodes from https://nodes.tox.chat/. objcTox is trying to keep this list up to date. + * You can check all nodes and update date in nodes.json file. + * + * This will NOT start bootstrapping. To start actual bootstrapping set all desired nodes + * and call `bootstrap` method. + */ +- (void)addPredefinedNodes; + +/** + * You HAVE TO call this method on startup to connect to Tox network. + * + * Before calling this method add nodes to bootstrap with. + * + * After calling this method + * - if manager wasn't connected before it will start bootstrapping immediately. + * - if it was connected before, it will wait 10 to connect to existing nodes + * before starting actually bootstrapping. + * + * When bootstrapping, submanager will bootstrap 4 random nodes from a list every 5 seconds + * until is will be connected. + */ +- (void)bootstrap; + +@end + +NS_ASSUME_NONNULL_END diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerCalls.h b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerCalls.h new file mode 100644 index 0000000..c36a615 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerCalls.h @@ -0,0 +1,145 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTView.h" +#import "OCTChat.h" +#import "OCTToxAVConstants.h" +#import "OCTSubmanagerCallsDelegate.h" + +@class OCTToxAV; +@class OCTCall; + +@protocol OCTSubmanagerCalls + +@property (nullable, weak, nonatomic) id delegate; + +/** + * Set the property to YES to enable the microphone, otherwise NO. + * Default value is YES at the start of every call; + **/ +@property (nonatomic, assign) BOOL enableMicrophone; + +/** + * This must be called once after initialization. + * @param error Pointer to an error when setting up. + * @return YES on success, otherwise NO. + */ +- (BOOL)setupAndReturnError:(NSError *__nullable *__nullable)error; + +/** + * This class is responsible for telling the end-user what calls we have available. + * We can also initialize a call session from here. + * @param chat The chat for which we would like to initiate a call. + * @param enableAudio YES for Audio, otherwise NO. + * @param enableVideo YES for Video, otherwise NO. + * @param error Pointer to an error when attempting to answer a call + * @return OCTCall session + */ +- (nullable OCTCall *)callToChat:(nonnull OCTChat *)chat + enableAudio:(BOOL)enableAudio + enableVideo:(BOOL)enableVideo + error:(NSError *__nullable *__nullable)error; + +/** + * Enable video calling for an active call. + * Use this when you started a call without video in the first place. + * @param enable YES to enable video, NO to stop video sending. + * @param call Call to enable video for. + * @param error Pointer to an error object. + * @return YES on success, otherwise NO. + */ +- (BOOL)enableVideoSending:(BOOL)enable + forCall:(nonnull OCTCall *)call + error:(NSError *__nullable *__nullable)error; + +/** + * Answer a call + * @param call The call session we would like to answer + * @param enableAudio YES for Audio, otherwise NO. + * @param enableVideo YES for Video, otherwise NO. + * @param error Pointer to an error when attempting to answer a call + * @return YES if we were able to succesfully answer the call, otherwise NO. + */ +- (BOOL)answerCall:(nonnull OCTCall *)call + enableAudio:(BOOL)enableAudio + enableVideo:(BOOL)enableVideo + error:(NSError *__nullable *__nullable)error; + +/** + * Send call control to call. + * @param control The control to send to call. + * @param call The appopriate call to send to. + * @param error Pointer to error object if there's an issue muting the call. + * @return YES if succesful, NO otherwise. + */ +- (BOOL)sendCallControl:(OCTToxAVCallControl)control + toCall:(nonnull OCTCall *)call + error:(NSError *__nullable *__nullable)error; + +/** + * The OCTView that will have the video feed. + */ +- (nullable OCTView *)videoFeed; + +/** + * The preview video of the user. + * You must be in a video call for this to show. Otherwise the layer will + * just be black. + * @param completionBlock Block responsible for using the layer. This + * must not be nil. + */ +- (void)getVideoCallPreview:(void (^__nonnull)( CALayer *__nullable layer))completionBlock; + +/** + * Set the Audio bit rate. + * @param bitrate The bitrate to change to. + * @param call The Call to set the bitrate for. + * @param error Pointer to error object if there's an issue setting the bitrate. + */ +- (BOOL)setAudioBitrate:(int)bitrate forCall:(nonnull OCTCall *)call error:(NSError *__nullable *__nullable)error; + +#if ! TARGET_OS_IPHONE + +/** + * Set input source and output targets for A/V. + * + * On iPhone OS, you must pass one of the OCT[Input|Output]Device... constants + * as the deviceUniqueID. + * On OS X, you can get valid deviceUniqueID values from: + * - AVFoundation: video and audio (inputs only) (AVCaptureDevice uniqueID) + * - Core Audio: audio inputs and outputs (kAudioDevicePropertyDeviceUID). + * @param deviceUniqueID The device ID to use. May be nil, in which case + * a default device will be used + */ +- (BOOL)setAudioInputDevice:(nullable NSString *)deviceUniqueID + error:(NSError *__nullable *__nullable)error; +- (BOOL)setAudioOutputDevice:(nullable NSString *)deviceUniqueID + error:(NSError *__nullable *__nullable)error; +- (BOOL)setVideoInputDevice:(nullable NSString *)deviceUniqueID + error:(NSError *__nullable *__nullable)error; + +#else + +/** + * Send the audio to the speaker + * @param speaker YES to send audio to speaker, NO to reset to default. + * @param error Pointer to error object. + * @return YES if successful, otherwise NO. + */ +- (BOOL)routeAudioToSpeaker:(BOOL)speaker + error:(NSError *__nullable *__nullable)error; + +/** + * Use a different camera for input. + * @param front YES to use the front camera, NO to use the + * rear camera. Front camera is used by default. + * @error Pointer to error object. + * @return YES on success, otherwise NO. + */ +- (BOOL)switchToCameraFront:(BOOL)front error:(NSError *__nullable *__nullable)error; + +#endif + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerCallsDelegate.h b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerCallsDelegate.h new file mode 100644 index 0000000..bffd88b --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerCallsDelegate.h @@ -0,0 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +@class OCTCall; +@protocol OCTSubmanagerCalls; + +@protocol OCTSubmanagerCallDelegate + +/** + * This gets called when we receive a call. + **/ +- (void)callSubmanager:(id)callSubmanager receiveCall:(OCTCall *)call audioEnabled:(BOOL)audioEnabled videoEnabled:(BOOL)videoEnabled; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerChats.h b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerChats.h new file mode 100644 index 0000000..c09c93d --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerChats.h @@ -0,0 +1,84 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTToxConstants.h" + +@class OCTChat; +@class OCTFriend; +@class OCTMessageAbstract; + +@protocol OCTSubmanagerChats + +/** + * Searches for a chat with specific friend. If chat is not found creates one and returns it. + * + * @param friend Friend to get/create chat with. + * + * @return Chat with specific friend. + */ +- (OCTChat *)getOrCreateChatWithFriend:(OCTFriend *)friend; + +/** + * Removes given messages. + * + * @param messages Array with messages to remove. + * + * @warning Destructive operation! There is no way to restore messages after removal. + */ +- (void)removeMessages:(NSArray *)messages; + +/** + * Removes all messages in chat and chat itself. + * + * @param chat Chat to remove in. + * @param removeChat Whether remove chat or not + * + * @warning Destructive operation! There is no way to restore chat or messages after removal. + */ +- (void)removeAllMessagesInChat:(OCTChat *)chat removeChat:(BOOL)removeChat; + +/** + * Send text message to specific chat + * + * @param chat Chat send message to. + * @param text Text to send. + * @param type Type of message to send. + * @param successBlock Block called when message was successfully send. + * @param message Message that was send. + * @param failureBlock Block called when submanager failed to send message. + * @param error Error that occurred. See OCTToxErrorFriendSendMessage for all error codes. + */ +- (void)sendMessageToChat:(OCTChat *)chat + text:(NSString *)text + type:(OCTToxMessageType)type + successBlock:(void (^)(OCTMessageAbstract *message))userSuccessBlock + failureBlock:(void (^)(NSError *error))userFailureBlock; + +/** + * Trigger PUSH Message to yourself + */ +- (void)sendOwnPush; + +/** + * Trigger PUSH Message to Friend if the Text Message was sent in a period where the friend + * was online to us, but the friend was in fact already offline. + * so no message was sent AND not PUSH was triggered. + */ +- (void)sendMessagePushToChat:(OCTChat *)chat; + +/** + * Set our typing status for a chat. You are responsible for turning it on or off. + * + * @param isTyping Status showing whether user is typing or not. + * @param chat Chat to set typing status. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorSetTyping for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)setIsTyping:(BOOL)isTyping inChat:(OCTChat *)chat error:(NSError **)error; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerFiles.h b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerFiles.h new file mode 100644 index 0000000..6c89ac1 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerFiles.h @@ -0,0 +1,124 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@class OCTMessageAbstract; +@class OCTChat; +@protocol OCTSubmanagerFilesProgressSubscriber; + +@protocol OCTSubmanagerFiles + +/** + * Send given data to particular chat. After sending OCTMessageAbstract with messageFile will be added to this chat. + * You can monitor progress using this message. + * + * File will be saved in uploaded files directory (see OCTFileStorageProtocol). + * + * @param data Data to send. + * @param fileName Name of the file. + * @param chat Chat to send data to. + * @param failureBlock Block that will be called in case of upload failure. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTSendFileError for all error codes. + */ +- (void)sendData:(nonnull NSData *)data + withFileName:(nonnull NSString *)fileName + toChat:(nonnull OCTChat *)chat + failureBlock:(nullable void (^)(NSError *__nonnull error))failureBlock; + +/** + * Send given file to particular chat. After sending OCTMessageAbstract with messageFile will be added to this chat. + * You can monitor progress using this message. + * + * @param filePath Path of file to upload. + * @param moveToUploads If YES file will be moved to uploads directory. + * @param chat Chat to send file to. + * @param failureBlock Block that will be called in case of upload failure. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTSendFileError for all error codes. + */ +- (void)sendFileAtPath:(nonnull NSString *)filePath + moveToUploads:(BOOL)moveToUploads + toChat:(nonnull OCTChat *)chat + failureBlock:(nullable void (^)(NSError *__nonnull error))failureBlock; + +/** + * Accept file transfer. + * + * @param message Message with file transfer. Message should be incoming and have OCTMessageFile with + * fileType OCTMessageFileTypeWaitingConfirmation. Otherwise nothing will happen. + * @param failureBlock Block that will be called in case of download failure. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTAcceptFileError for all error codes. + */ +- (void)acceptFileTransfer:(nonnull OCTMessageAbstract *)message + failureBlock:(nullable void (^)(NSError *__nonnull error))failureBlock; + +/** + * Cancel file transfer. File transfer can be waiting confirmation or active. + * + * @param message Message with file transfer. Message should have OCTMessageFile. Otherwise nothing will happen. + */ +- (BOOL)cancelFileTransfer:(nonnull OCTMessageAbstract *)message error:(NSError *__nullable *__nullable)error; + +/** + * Retry to send file using same OCTMessageAbstract. This message should have Canceled type, otherwise retry will failure. + * + * @param message File transfer message to send. + * @param failureBlock Block that will be called in case of upload failure. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTSendFileError for all error codes. + */ +- (void)retrySendingFile:(nonnull OCTMessageAbstract *)message + failureBlock:(nullable void (^)(NSError *__nonnull error))failureBlock; + +/** + * Pause or resume file transfer. + * - For pausing transfer should be in Loading state or paused by friend, otherwise nothing will happen. + * - For resuming transfer should be in Paused state and paused by user, otherwise nothing will happen. + * + * @param pause Flag notifying of pausing/resuming file transfer. + * @param message Message with file transfer. Message should have OCTMessageFile. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTFileTransferError for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)pauseFileTransfer:(BOOL)pause + message:(nonnull OCTMessageAbstract *)message + error:(NSError *__nullable *__nullable)error; + +/** + * Add progress subscriber for given file transfer. Subscriber will receive progress immediately after subscribing. + * File transfer should be in Loading or Paused state, otherwise subscriber won't be added. + * + * @param subscriber Object listening to progress protocol. + * @param message Message with file transfer. Message should have OCTMessageFile. Otherwise nothing will happen. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTFileTransferError for all error codes. + * + * @return YES on success, NO on failure. + * + * @warning Subscriber will be stored as weak reference, so it is safe to dealloc it without unsubscribing. + */ +- (BOOL)addProgressSubscriber:(nonnull id)subscriber + forFileTransfer:(nonnull OCTMessageAbstract *)message + error:(NSError *__nullable *__nullable)error; + +/** + * Remove progress subscriber for given file transfer. + * + * @param subscriber Object listening to progress protocol. + * @param message Message with file transfer. Message should have OCTMessageFile. Otherwise nothing will happen. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTFileTransferError for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)removeProgressSubscriber:(nonnull id)subscriber + forFileTransfer:(nonnull OCTMessageAbstract *)message + error:(NSError *__nullable *__nullable)error; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerFilesProgressSubscriber.h b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerFilesProgressSubscriber.h new file mode 100644 index 0000000..df2b1b3 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerFilesProgressSubscriber.h @@ -0,0 +1,30 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@class OCTMessageAbstract; + +@protocol OCTSubmanagerFilesProgressSubscriber + +/** + * Method called on download/upload progress. + * + * @param progress Progress of download/upload. From 0.0 to 1.0. + * @param message File message with progress update. + */ +- (void)submanagerFilesOnProgressUpdate:(float)progress message:(nonnull OCTMessageAbstract *)message; + +/** + * Method called on download/upload eta update. + * + * @param eta Estimated time of finish of download/upload. + * @param bytesPerSecond Speed of download/upload. + * @param message File message with progress update. + */ +- (void)submanagerFilesOnEtaUpdate:(CFTimeInterval)eta + bytesPerSecond:(OCTToxFileSize)bytesPerSecond + message:(nonnull OCTMessageAbstract *)message; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerFriends.h b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerFriends.h new file mode 100644 index 0000000..f4f13bb --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerFriends.h @@ -0,0 +1,53 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@class OCTFriendRequest; +@class OCTFriend; + +@protocol OCTSubmanagerFriends + +/** + * Send friend request to given address. Automatically adds friend with this address to friend list. + * + * @param address Address of a friend. If required. + * @param message Message to send with friend request. Is required. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendAdd for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)sendFriendRequestToAddress:(NSString *)address message:(NSString *)message error:(NSError **)error; + +/** + * Approve given friend request. After approving new friend will be added and friendRequest will be removed. + * + * @param friendRequest Friend request to approve. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendAdd for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)approveFriendRequest:(OCTFriendRequest *)friendRequest error:(NSError **)error; + +/** + * Remove friend request from list. This cannot be undone. + * + * @param friendRequest Friend request to remove. + */ +- (void)removeFriendRequest:(OCTFriendRequest *)friendRequest; + +/** + * Remove friend from list. This cannot be undone. + * + * @param friend Friend to remove. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendDelete for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)removeFriend:(OCTFriend *)friend error:(NSError **)error; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerObjects.h b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerObjects.h new file mode 100644 index 0000000..b267b36 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerObjects.h @@ -0,0 +1,72 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTManagerConstants.h" + +@class OCTObject; +@class OCTFriend; +@class OCTChat; +@class RLMResults; + +@protocol OCTSubmanagerObjects + +/** + * This property can be used to save any generic data you like. + * + * The default value is nil. + */ +@property (strong, nonatomic) NSData *genericSettingsData; + +/** + * Returns fetch request for specified type. + * + * @param type Type of fetch request. + * @param predicate Predicate that represents search query. + * + * @return RLMResults with objects of specified type. + */ +- (RLMResults *)objectsForType:(OCTFetchRequestType)type predicate:(NSPredicate *)predicate; + +/** + * Returns object for specified type with uniqueIdentifier. + * + * @param uniqueIdentifier Unique identifier of object. + * @param type Type of object. + * + * @return Object of specified type or nil, if object does not exist. + */ +- (OCTObject *)objectWithUniqueIdentifier:(NSString *)uniqueIdentifier forType:(OCTFetchRequestType)type; + +#pragma mark - Friends + +/** + * Sets nickname property for friend. + * + * @param friend Friend to change. + * @param nickname New nickname. If nickname is empty or nil, it will be set to friends name. + * If friend don't have name, it will be set to friends publicKey. + */ +- (void)changeFriend:(OCTFriend *)friend nickname:(NSString *)nickname; + +#pragma mark - Chats + +/** + * Sets enteredText property for chat. + * + * @param chat Chat to change. + * @param enteredText New text. + */ +- (void)changeChat:(OCTChat *)chat enteredText:(NSString *)enteredText; + +/** + * Sets lastReadDateInterval property for chat. + * + * @param chat Chat to change. + * @param lastReadDateInterval New interval. + */ +- (void)changeChat:(OCTChat *)chat lastReadDateInterval:(NSTimeInterval)lastReadDateInterval; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerUser.h b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerUser.h new file mode 100644 index 0000000..4680f89 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/Submanagers/OCTSubmanagerUser.h @@ -0,0 +1,104 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTToxConstants.h" + +@protocol OCTSubmanagerUser; +@protocol OCTSubmanagerUserDelegate +- (void)submanagerUser:(nonnull id)submanager connectionStatusUpdate:(OCTToxConnectionStatus)connectionStatus; +@end + +@protocol OCTSubmanagerUser + +@property (weak, nonatomic, nullable) id delegate; + +/** + * Indicates if client is connected to the DHT. + */ +@property (assign, nonatomic, readonly) OCTToxConnectionStatus connectionStatus; + +/** + * Client's address. + * + * Address for Tox as a hex string. Address is kOCTToxAddressLength length and has following format: + * [publicKey (32 bytes, 64 characters)][nospam number (4 bytes, 8 characters)][checksum (2 bytes, 4 characters)] + */ +@property (strong, nonatomic, readonly, nonnull) NSString *userAddress; + +/** + * Client's Tox Public Key (long term public key) of kOCTToxPublicKeyLength. + */ +@property (strong, nonatomic, readonly, nonnull) NSString *publicKey; + +/** + * Client's nospam part of the address. Any 32 bit unsigned integer. + */ +@property (assign, nonatomic) OCTToxNoSpam nospam; + +/** + * Client's capabilities. A 64 bit unsigned integer. + */ +@property (nonatomic, readonly) OCTToxCapabilities capabilities; + +/** + * Client's user status. + */ +@property (assign, nonatomic) OCTToxUserStatus userStatus; + +/** + * Set the nickname for the client. + * + * @param name Name to be set. Minimum length of name is 1 byte. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorSetInfoCode for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)setUserName:(nullable NSString *)name error:(NSError *__nullable *__nullable)error; + +/** + * Get client's nickname. + * + * @return Client's nickname or nil in case of error. + */ +- (nullable NSString *)userName; + +/** + * Set client's status message. + * + * @param statusMessage Status message to be set. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorSetInfoCode for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)setUserStatusMessage:(nullable NSString *)statusMessage error:(NSError *__nullable *__nullable)error; + +/** + * Get client's status message. + * + * @return Client's status message. + */ +- (nullable NSString *)userStatusMessage; + +/** + * Set user avatar. Avatar should be <= kOCTManagerMaxAvatarSize. + * + * @param avatar NSData representation of avatar image. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTSetUserAvatarError for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)setUserAvatar:(nullable NSData *)avatar error:(NSError *__nullable *__nullable)error; + +/** + * Get data representation of user avatar. + * + * @return Data with user avatar if exists. + */ +- (nullable NSData *)userAvatar; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Manager/nodes.json b/local_pod_repo/objcTox/Classes/Public/Manager/nodes.json new file mode 100644 index 0000000..e55dd87 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Manager/nodes.json @@ -0,0 +1,776 @@ +{ + "last_scan": 1675968658, + "last_refresh": 1675968601, + "nodes": [ + { + "ipv4": "144.217.167.73", + "ipv6": "-", + "port": 33445, + "tcp_ports": [ + 3389, + 33445 + ], + "public_key": "7E5668E0EE09E19F320AD47902419331FFEE147BB3606769CFBE921A2A2FD34C", + "maintainer": "velusip", + "location": "CA", + "status_udp": true, + "status_tcp": true, + "version": "1000002013", + "motd": "Jera", + "last_ping": 1675968658 + }, + { + "ipv4": "tox.abilinski.com", + "ipv6": "-", + "port": 33445, + "tcp_ports": [ + 33445 + ], + "public_key": "10C00EB250C3233E343E2AEBA07115A5C28920E9C8D29492F6D00B29049EDC7E", + "maintainer": "AnthonyBilinski", + "location": "CA", + "status_udp": true, + "status_tcp": true, + "version": "1000002013", + "motd": "Running https://github.com/toktok/c-toxcore v0.2.13. qTox best Tox! Contact: AC18841E56CCDEE16E93E10E6AB2765BE54277D67F1372921B5B418A6B330D3D3FAFA60B0931", + "last_ping": 1675968658 + }, + { + "ipv4": "198.199.98.108", + "ipv6": "2604:a880:1:20::32f:1001", + "port": 33445, + "tcp_ports": [ + 33445, + 3389 + ], + "public_key": "BEF0CFB37AF874BD17B9A8F9FE64C75521DB95A37D33C5BDB00E9CF58659C04F", + "maintainer": "Cody", + "location": "US", + "status_udp": true, + "status_tcp": true, + "version": "1000002015", + "motd": "Cody's Tox node!", + "last_ping": 1675968658 + }, + { + "ipv4": "tox.kurnevsky.net", + "ipv6": "tox.kurnevsky.net", + "port": 33445, + "tcp_ports": [], + "public_key": "82EF82BA33445A1F91A7DB27189ECFC0C013E06E3DA71F588ED692BED625EC23", + "maintainer": "kurnevsky", + "location": "NL", + "status_udp": true, + "status_tcp": false, + "version": "3000002000", + "motd": "Hi from tox-rs!", + "last_ping": 1675968658 + }, + { + "ipv4": "205.185.115.131", + "ipv6": "-", + "port": 53, + "tcp_ports": [ + 53, + 443, + 33445, + 3389 + ], + "public_key": "3091C6BEB2A993F1C6300C16549FABA67098FF3D62C6D253828B531470B53D68", + "maintainer": "GDR!", + "location": "US", + "status_udp": true, + "status_tcp": true, + "version": "1000002018", + "motd": "https://gdr.name/tuntox/", + "last_ping": 1675968658 + }, + { + "ipv4": "tox2.abilinski.com", + "ipv6": "tox2.abilinski.com", + "port": 33445, + "tcp_ports": [ + 33445 + ], + "public_key": "7A6098B590BDC73F9723FC59F82B3F9085A64D1B213AAF8E610FD351930D052D", + "maintainer": "AnthonyBilinski", + "location": "US", + "status_udp": true, + "status_tcp": true, + "version": "1000002013", + "motd": "Running https://github.com/toktok/c-toxcore v0.2.13. qTox best Tox! Contact: AC18841E56CCDEE16E93E10E6AB2765BE54277D67F1372921B5B418A6B330D3D3FAFA60B0931", + "last_ping": 1675968658 + }, + { + "ipv4": "46.101.197.175", + "ipv6": "2a03:b0c0:3:d0::ac:5001", + "port": 33445, + "tcp_ports": [ + 3389, + 33445 + ], + "public_key": "CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707", + "maintainer": "kotelnik", + "location": "DE", + "status_udp": true, + "status_tcp": true, + "version": "1000002018", + "motd": "Power to Ukraine!", + "last_ping": 1675968659 + }, + { + "ipv4": "tox1.mf-net.eu", + "ipv6": "tox1.mf-net.eu", + "port": 33445, + "tcp_ports": [ + 3389, + 33445 + ], + "public_key": "B3E5FA80DC8EBD1149AD2AB35ED8B85BD546DEDE261CA593234C619249419506", + "maintainer": "2mf", + "location": "DE", + "status_udp": true, + "status_tcp": true, + "version": "1000002018", + "motd": "tox-bootstrapd", + "last_ping": 1675968660 + }, + { + "ipv4": "tox2.mf-net.eu", + "ipv6": "tox2.mf-net.eu", + "port": 33445, + "tcp_ports": [ + 33445, + 3389 + ], + "public_key": "70EA214FDE161E7432530605213F18F7427DC773E276B3E317A07531F548545F", + "maintainer": "2mf", + "location": "DE", + "status_udp": true, + "status_tcp": true, + "version": "1000002018", + "motd": "tox-bootstrapd", + "last_ping": 1675968659 + }, + { + "ipv4": "195.201.7.101", + "ipv6": "-", + "port": 33445, + "tcp_ports": [ + 3389, + 33445 + ], + "public_key": "B84E865125B4EC4C368CD047C72BCE447644A2DC31EF75BD2CDA345BFD310107", + "maintainer": "tux1973", + "location": "DE", + "status_udp": true, + "status_tcp": true, + "version": "1000002012", + "motd": "tox-bootstrapd", + "last_ping": 1675968660 + }, + { + "ipv4": "tox4.plastiras.org", + "ipv6": "-", + "port": 33445, + "tcp_ports": [ + 443, + 3389, + 33445 + ], + "public_key": "836D1DA2BE12FE0E669334E437BE3FB02806F1528C2B2782113E0910C7711409", + "maintainer": "Tha_14", + "location": "MD", + "status_udp": true, + "status_tcp": true, + "version": "1000002018", + "motd": "Add me on Tox: F0AA7C8C55552E8593B2B77AC6FCA598A40D1F5F52A26C2322690A4BF1DFCB0DD8AEDD2822FF", + "last_ping": 1675968659 + }, + { + "ipv4": "188.225.9.167", + "ipv6": "209:dead:ded:4991:49f3:b6c0:9869:3019", + "port": 33445, + "tcp_ports": [ + 33445, + 3389 + ], + "public_key": "1911341A83E02503AB1FD6561BD64AF3A9D6C3F12B5FBB656976B2E678644A67", + "maintainer": "Nikat", + "location": "RU", + "status_udp": true, + "status_tcp": true, + "version": "1000002013", + "motd": "First yggdrasil tox bootstrapd!!!\nYou can read about it here: https://yggdrasil-network.github.io/", + "last_ping": 1675968659 + }, + { + "ipv4": "122.116.39.151", + "ipv6": "2001:b011:8:2f22:1957:7f9d:e31f:96dd", + "port": 33445, + "tcp_ports": [ + 3389, + 33445 + ], + "public_key": "5716530A10D362867C8E87EE1CD5362A233BAFBBA4CF47FA73B7CAD368BD5E6E", + "maintainer": "miaoski", + "location": "TW", + "status_udp": true, + "status_tcp": true, + "version": "1000002018", + "motd": "tox-bootstrapd", + "last_ping": 1675968659 + }, + { + "ipv4": "tox3.plastiras.org", + "ipv6": "tox3.plastiras.org", + "port": 33445, + "tcp_ports": [ + 33445, + 3389 + ], + "public_key": "4B031C96673B6FF123269FF18F2847E1909A8A04642BBECD0189AC8AEEADAF64", + "maintainer": "Tha_14", + "location": "DE", + "status_udp": true, + "status_tcp": true, + "version": "1000002018", + "motd": "Add me on Tox: F0AA7C8C55552E8593B2B77AC6FCA598A40D1F5F52A26C2322690A4BF1DFCB0DD8AEDD2822FF", + "last_ping": 1675968659 + }, + { + "ipv4": "104.225.141.59", + "ipv6": "-", + "port": 43334, + "tcp_ports": [], + "public_key": "933BA20B2E258B4C0D475B6DECE90C7E827FE83EFA9655414E7841251B19A72C", + "maintainer": "Gabe", + "location": "US", + "status_udp": true, + "status_tcp": false, + "version": "1000002018", + "motd": "True peace is in Jesus Matt 11:28-30 Tox ID: 07D7B9018C5C724A2E9EB34C60782F78B7BDF64D5316946EF49F8E6A20F26B4631FEC281D6A4", + "last_ping": 1675968658 + }, + { + "ipv4": "139.162.110.188", + "ipv6": "2400:8902::f03c:93ff:fe69:bf77", + "port": 33445, + "tcp_ports": [ + 443, + 3389, + 33445 + ], + "public_key": "F76A11284547163889DDC89A7738CF271797BF5E5E220643E97AD3C7E7903D55", + "maintainer": "ToxTom", + "location": "CA", + "status_udp": true, + "status_tcp": true, + "version": "1000002013", + "motd": "ToxTom", + "last_ping": 1675968659 + }, + { + "ipv4": "198.98.49.206", + "ipv6": "2605:6400:10:caa:1:be:a:7001", + "port": 33445, + "tcp_ports": [ + 33445 + ], + "public_key": "28DB44A3CEEE69146469855DFFE5F54DA567F5D65E03EFB1D38BBAEFF2553255", + "maintainer": "Cüber", + "location": "US", + "status_udp": true, + "status_tcp": true, + "version": "1000002013", + "motd": "Tox", + "last_ping": 1675968660 + }, + { + "ipv4": "172.105.109.31", + "ipv6": "2600:3c04::f03c:92ff:fe30:5df", + "port": 33445, + "tcp_ports": [ + 33445 + ], + "public_key": "D46E97CF995DC1820B92B7D899E152A217D36ABE22730FEA4B6BF1BFC06C617C", + "maintainer": "amr", + "location": "CA", + "status_udp": true, + "status_tcp": true, + "version": "1000002018", + "motd": "FrozenDev Node: tox-bootstrapd Add me on tox: A625D9E9EAAA7B40C399F50BA8B255836EE5A09B6DD0C54CF0E190E24544DC39237D6389FAED", + "last_ping": 1675968660 + }, + { + "ipv4": "91.146.66.26", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "B5E7DAC610DBDE55F359C7F8690B294C8E4FCEC4385DE9525DBFA5523EAD9D53", + "maintainer": "Toxdaemon", + "location": "EE", + "status_udp": true, + "status_tcp": false, + "version": "1000002013", + "motd": "tox-bootstrapd 91.146.66.26", + "last_ping": 1675968658 + }, + { + "ipv4": "tox01.ky0uraku.xyz", + "ipv6": "tox01.ky0uraku.xyz", + "port": 33445, + "tcp_ports": [ + 33445 + ], + "public_key": "FD04EB03ABC5FC5266A93D37B4D6D6171C9931176DC68736629552D8EF0DE174", + "maintainer": "ky0uraku", + "location": "NL", + "status_udp": true, + "status_tcp": true, + "version": "1000002013", + "motd": "ky0uraku tox01 node", + "last_ping": 1675968660 + }, + { + "ipv4": "tox02.ky0uraku.xyz", + "ipv6": "tox02.ky0uraku.xyz", + "port": 33445, + "tcp_ports": [ + 33445 + ], + "public_key": "D3D6D7C0C7009FC75406B0A49E475996C8C4F8BCE1E6FC5967DE427F8F600527", + "maintainer": "ky0uraku", + "location": "FR", + "status_udp": true, + "status_tcp": true, + "version": "1000002016", + "motd": "ky0uraku tox02 node", + "last_ping": 1675968660 + }, + { + "ipv4": "tox.plastiras.org", + "ipv6": "tox.plastiras.org", + "port": 33445, + "tcp_ports": [ + 33445, + 443 + ], + "public_key": "8E8B63299B3D520FB377FE5100E65E3322F7AE5B20A0ACED2981769FC5B43725", + "maintainer": "Tha_14", + "location": "LU", + "status_udp": true, + "status_tcp": true, + "version": "1000002018", + "motd": "Add me on Tox: F0AA7C8C55552E8593B2B77AC6FCA598A40D1F5F52A26C2322690A4BF1DFCB0DD8AEDD2822FF", + "last_ping": 1675968659 + }, + { + "ipv4": "kusoneko.moe", + "ipv6": "kusoneko.moe", + "port": 33445, + "tcp_ports": [ + 33445 + ], + "public_key": "BE7ED53CD924813507BA711FD40386062E6DC6F790EFA122C78F7CDEEE4B6D1B", + "maintainer": "Kusoneko", + "location": "CA", + "status_udp": true, + "status_tcp": true, + "version": "1000002013", + "motd": "Managed by kusoneko (ID:D8E4A5E926A4E7A85FA40F8CA55D47554F043D3C5CDB457187726F19CE20E52C0D7C3FCE9466)", + "last_ping": 1675968658 + }, + { + "ipv4": "tox2.plastiras.org", + "ipv6": "tox2.plastiras.org", + "port": 33445, + "tcp_ports": [ + 33445, + 3389 + ], + "public_key": "B6626D386BE7E3ACA107B46F48A5C4D522D29281750D44A0CBA6A2721E79C951", + "maintainer": "Tha_14", + "location": "DE", + "status_udp": true, + "status_tcp": true, + "version": "1000002018", + "motd": "Add me on Tox: F0AA7C8C55552E8593B2B77AC6FCA598A40D1F5F52A26C2322690A4BF1DFCB0DD8AEDD2822FF", + "last_ping": 1675968659 + }, + { + "ipv4": "172.104.215.182", + "ipv6": "2600:3c03::f03c:93ff:fe7f:6096", + "port": 33445, + "tcp_ports": [ + 443, + 33445, + 3389 + ], + "public_key": "DA2BD927E01CD05EBCC2574EBE5BEBB10FF59AE0B2105A7D1E2B40E49BB20239", + "maintainer": "zero-one", + "location": "US", + "status_udp": true, + "status_tcp": true, + "version": "1000002018", + "motd": "tox-bootstrapd", + "last_ping": 1675968658 + }, + { + "ipv4": "5.19.249.240", + "ipv6": "-", + "port": 38296, + "tcp_ports": [ + 38296, + 3389 + ], + "public_key": "DA98A4C0CD7473A133E115FEA2EBDAEEA2EF4F79FD69325FC070DA4DE4BA3238", + "maintainer": "Toxdaemon", + "location": "RU", + "status_udp": false, + "status_tcp": true, + "version": "", + "motd": "", + "last_ping": 1675968659 + }, + { + "ipv4": "NONE", + "ipv6": "2607:f130:0:f8::4c85:a645", + "port": 33445, + "tcp_ports": [ + 3389, + 33445 + ], + "public_key": "8AFE1FC6426E5B77AB80318ED64F5F76341695B9FB47AB8AC9537BF5EE9E9D29", + "maintainer": "Busindre", + "location": "US", + "status_udp": false, + "status_tcp": true, + "version": "", + "motd": "", + "last_ping": 1675968658 + }, + { + "ipv4": "91.219.59.156", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832", + "maintainer": "ray65536", + "location": "RU", + "status_udp": false, + "status_tcp": false, + "version": "1000002013", + "motd": "Ray's Tox Node. TOX ID:3C3D6DB24D24754393679E59F198EF45EE26835AEF7EA3E3ECEA40E204F2B828BE86DF012ABF", + "last_ping": 1669411200 + }, + { + "ipv4": "85.143.221.42", + "ipv6": "2a04:ac00:1:9f00:5054:ff:fe01:becd", + "port": 33445, + "tcp_ports": [], + "public_key": "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43", + "maintainer": "MAH69K", + "location": "RU", + "status_udp": false, + "status_tcp": false, + "version": "1000002013", + "motd": "Saluton! Mia Tox ID: B229B7BD68FC66C2716EAB8671A461906321C764782D7B3EDBB650A315F6C458EF744CE89F07. Scribu! ;)", + "last_ping": 1667642040 + }, + { + "ipv4": "tox.verdict.gg", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976", + "maintainer": "Deliran", + "location": "DE", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + }, + { + "ipv4": "78.46.73.141", + "ipv6": "2a01:4f8:120:4091::3", + "port": 33445, + "tcp_ports": [], + "public_key": "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46", + "maintainer": "Sorunome", + "location": "DE", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + }, + { + "ipv4": "tox.initramfs.io", + "ipv6": "tox.initramfs.io", + "port": 33445, + "tcp_ports": [], + "public_key": "3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25", + "maintainer": "initramfs", + "location": "TW", + "status_udp": false, + "status_tcp": false, + "version": "1000002018", + "motd": "initramfs' tox bootstrap node", + "last_ping": 1674742139 + }, + { + "ipv4": "46.229.50.168", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "813C8F4187833EF0655B10F7752141A352248462A567529A38B6BBF73E979307", + "maintainer": "Stranger", + "location": "UA", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + }, + { + "ipv4": "mk.tox.dcntrlzd.network", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "5E815C25A4E58910A7350EC64ECB32BC9E1919F86844DC97125735C2C30FBE6E", + "maintainer": "cryptogospod", + "location": "MK", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + }, + { + "ipv4": "tox.novg.net", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "D527E5847F8330D628DAB1814F0A422F6DC9D0A300E6C357634EE2DA88C35463", + "maintainer": "blind_oracle", + "location": "NL", + "status_udp": false, + "status_tcp": false, + "version": "1000002012", + "motd": "tox-bootstrapd", + "last_ping": 1673387100 + }, + { + "ipv4": "87.118.126.207", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "0D303B1778CA102035DA01334E7B1855A45C3EFBC9A83B9D916FFDEBC6DD3B2E", + "maintainer": "quux", + "location": "DE", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + }, + { + "ipv4": "81.169.136.229", + "ipv6": "2a01:238:4254:2a00:7aca:fe8c:68e0:27ec", + "port": 33445, + "tcp_ports": [], + "public_key": "E0DB78116AC6500398DDBA2AEEF3220BB116384CAB714C5D1FCD61EA2B69D75E", + "maintainer": "9ofSpades", + "location": "DE", + "status_udp": false, + "status_tcp": false, + "version": "1000002009", + "motd": "🂩 wishes happy toxing. 📡", + "last_ping": 1674603960 + }, + { + "ipv4": "floki.blog", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "6C6AF2236F478F8305969CCFC7A7B67C6383558FF87716D38D55906E08E72667", + "maintainer": "Floki", + "location": "GB", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + }, + { + "ipv4": "bg.tox.dcntrlzd.network", + "ipv6": "bg.tox.dcntrlzd.network", + "port": 33445, + "tcp_ports": [], + "public_key": "20AD2A54D70E827302CDF5F11D7C43FA0EC987042C36628E64B2B721A1426E36", + "maintainer": "cryptogospod", + "location": "BG", + "status_udp": false, + "status_tcp": false, + "version": "1000002015", + "motd": "BG tox-bootstrapd node, courtesy of DCNTRLZD NETWORK. Get in touch at 03279E64D53B1B71DFB35F4F5568B448B015F467C0C6F73FB52C9CCF86C78A45DB83C24E7DF2", + "last_ping": 1667250480 + }, + { + "ipv4": "46.146.229.184", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "94750E94013586CCD989233A621747E2646F08F31102339452CADCF6DC2A760A", + "maintainer": "GS", + "location": "RU", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + }, + { + "ipv4": "gt.sot-te.ch", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "F4F4856F1A311049E0262E9E0A160610284B434F46299988A9CB42BD3D494618", + "maintainer": "SOT-TECH", + "location": "JP", + "status_udp": false, + "status_tcp": false, + "version": "1000002018", + "motd": "SOT-TECH NPO", + "last_ping": 1672337220 + }, + { + "ipv4": "209.59.144.175", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "214B7FEA63227CAEC5BCBA87F7ABEEDB1A2FF6D18377DD86BF551B8E094D5F1E", + "maintainer": "LasersAreGreat", + "location": "US", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + }, + { + "ipv4": "rs.tox.dcntrlzd.network", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "FC4BADF62DCAF17168A4E3ACAD5D656CF424EDB5E0C0C2B9D77E509E74BD8F0D", + "maintainer": "cryptogospod", + "location": "RS", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + }, + { + "ipv4": "195.123.208.139", + "ipv6": "2a02:27ac::3ff", + "port": 33445, + "tcp_ports": [], + "public_key": "534A589BA7427C631773D13083570F529238211893640C99D1507300F055FE73", + "maintainer": "Cüber", + "location": "LV", + "status_udp": false, + "status_tcp": false, + "version": "1000002013", + "motd": "😺 meow moew", + "last_ping": 1672186498 + }, + { + "ipv4": "208.38.228.104", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "3634666A51CA5BE1579C031BD31B20059280EB7C05406ED466BD9DFA53373271", + "maintainer": "LasersAreGreat", + "location": "US", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + }, + { + "ipv4": "lunarfire.spdns.org", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "E61F5963268A6306CCFE7AF98716345235763529957BD5F45889484654EE052B", + "maintainer": "Merlinoz", + "location": "DE", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + }, + { + "ipv4": "ru.tox.dcntrlzd.network", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "DBB2E896990ECC383DA2E68A01CA148105E34F9B3B9356F2FE2B5096FDB62762", + "maintainer": "cryptogospod", + "location": "RU", + "status_udp": false, + "status_tcp": false, + "version": "1000002015", + "motd": "RU tox-bootstrapd node, courtesy of DCNTRLZD NETWORK. Get in touch @ 03279E64D53B1B71DFB35F4F5568B448B015F467C0C6F73FB52C9CCF86C78A45DB83C24E7DF2", + "last_ping": 1666732500 + }, + { + "ipv4": "43.231.185.239", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "27D4029A96C9674C15B958011C62F63D4D35A23142EF2BA5CD9AF164162B3448", + "maintainer": "Jskmm", + "location": "US", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + }, + { + "ipv4": "141.95.108.234", + "ipv6": "-", + "port": 33445, + "tcp_ports": [], + "public_key": "2DEF3156812324B1593A6442C937EAE0A8BD98DE529D2D4A7DD4BA6CB3ECF262", + "maintainer": "CTL.DPC.RE", + "location": "DE", + "status_udp": false, + "status_tcp": false, + "version": "1000002013", + "motd": "tox-bootstrapd", + "last_ping": 1661962379 + }, + { + "ipv4": "NONE", + "ipv6": "200:832f:2e56:91a6:678e:aaaf:80bf:4a8a", + "port": 33445, + "tcp_ports": [], + "public_key": "444361B1717AD5E10D9C03EA1C714A846C9D3B16A875186D0034DC516A49F013", + "maintainer": "Dima(Yggdrasil)", + "location": "RU", + "status_udp": false, + "status_tcp": false, + "version": "", + "motd": "", + "last_ping": 0 + } + ] +} diff --git a/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTTox.h b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTTox.h new file mode 100644 index 0000000..975a8bb --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTTox.h @@ -0,0 +1,567 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTToxDelegate.h" +#import "OCTToxConstants.h" + +@class OCTToxOptions; + +@interface OCTTox : NSObject + +@property (weak, nonatomic) id delegate; + +/** + * Indicates if we are connected to the DHT. + */ +@property (assign, nonatomic, readonly) OCTToxConnectionStatus connectionStatus; + +/** + * Our address. + * + * Address for Tox as a hex string. Address is kOCTToxAddressLength length and has following format: + * [publicKey (32 bytes, 64 characters)][nospam number (4 bytes, 8 characters)][checksum (2 bytes, 4 characters)] + */ +@property (strong, nonatomic, readonly) NSString *userAddress; + +/** + * Our Tox Public Key (long term public key) of kOCTToxPublicKeyLength. + */ +@property (strong, nonatomic, readonly) NSString *publicKey; + +/** + * Our secret key of kOCTToxSecretKeyLength. + */ +@property (strong, nonatomic, readonly) NSString *secretKey; + +/** + * Client's nospam part of the address. Any 32 bit unsigned integer. + */ +@property (assign, nonatomic) OCTToxNoSpam nospam; + +/** + * Client's capabilities. A 64 bit unsigned integer. + */ +@property (nonatomic, readonly) OCTToxCapabilities capabilities; + +/** + * Client's user status. + */ +@property (assign, nonatomic) OCTToxUserStatus userStatus; + +#pragma mark - Class methods + +/** + * Return toxcore version in format X.Y.Z, where + * X - The major version number. Incremented when the API or ABI changes in an incompatible way. + * Y - The minor version number. Incremented when functionality is added without breaking the API or ABI. + * Set to 0 when the major version number is incremented. + * Z - The patch or revision number. Incremented when bugfixes are applied without changing any functionality or API or ABI. + */ ++ (NSString *)version; + +/** + * The major version number of toxcore. Incremented when the API or ABI changes in an incompatible way. + */ ++ (NSUInteger)versionMajor; + +/** + * The minor version number of toxcore. Incremented when functionality is added without breaking the API or ABI. + * Set to 0 when the major version number is incremented. + */ ++ (NSUInteger)versionMinor; + +/** + * The patch or revision number of toxcore. Incremented when bugfixes are applied without changing any functionality or API or ABI. + */ ++ (NSUInteger)versionPatch; + +#pragma mark - Lifecycle + +/** + * Creates new Tox object with configuration options and loads saved data. + * + * @param options Configuration options. + * @param data Data load Tox from previously stored by `-save` method. Pass nil if there is no saved data. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorInitCode for all error codes. + * + * @return New instance of Tox or nil if fatal error occured during loading. + * + * @warning If loading failed or succeeded only partially, the new or partially loaded instance is returned and + * an error is set. + */ +- (instancetype)initWithOptions:(OCTToxOptions *)options savedData:(NSData *)data error:(NSError **)error; + +/** + * Saves Tox into NSData. + * + * @return NSData with Tox save. + */ +- (NSData *)save; + +/** + * Starts the main loop of the Tox on it's own unique queue. + * + * @warning Tox won't do anything without calling this method. + */ +- (void)start; + +/** + * Stops the main loop of the Tox. + */ +- (void)stop; + +#pragma mark - Methods + +/** + * Sends a "get nodes" request to the given bootstrap node with IP, port, and + * public key to setup connections. + * + * This function will attempt to connect to the node using UDP and TCP at the + * same time. + * + * Tox will use the node as a TCP relay in case OCTToxOptions.UDPEnabled was + * YES, and also to connect to friends that are in TCP-only mode. Tox will + * also use the TCP connection when NAT hole punching is slow, and later switch + * to UDP if hole punching succeeds. + * + * @param host The hostname or an IP address (IPv4 or IPv6) of the node. + * @param port The port on the host on which the bootstrap Tox instance is listening. + * @param publicKey Public key of the node (of kOCTToxPublicKeyLength length). + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorBootstrapCode for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)bootstrapFromHost:(NSString *)host port:(OCTToxPort)port publicKey:(NSString *)publicKey error:(NSError **)error; + +/** + * Adds additional host:port pair as TCP relay. + * + * This function can be used to initiate TCP connections to different ports on + * the same bootstrap node, or to add TCP relays without using them as + * bootstrap nodes. + * + * @param host The hostname or IP address (IPv4 or IPv6) of the TCP relay. + * @param port The port on the host on which the TCP relay is listening. + * @param publicKey Public key of the node (of kOCTToxPublicKeyLength length). + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorBootstrapCode for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)addTCPRelayWithHost:(NSString *)host port:(OCTToxPort)port publicKey:(NSString *)publicKey error:(NSError **)error; + +/** + * Add a friend. + * + * @param address Address of a friend to add. Must be exactry kOCTToxAddressLength length. + * @param message Message that would be send with friend request. Minimum length - 1 byte. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendAdd for all error codes. + * + * @return On success returns friend number. On failure returns kOCTToxFriendNumberFailure and fills `error` parameter. + * + * @warning You can check maximum length of message with `-checkLengthOfString:withCheckType:` method with + * OCTToxCheckLengthTypeFriendRequest type. + */ +- (OCTToxFriendNumber)addFriendWithAddress:(NSString *)address message:(NSString *)message error:(NSError **)error; + +/** + * Add a friend without sending friend request. + * + * This function is used to add a friend in response to a friend request. If the + * client receives a friend request, it can be reasonably sure that the other + * client added this client as a friend, eliminating the need for a friend + * request. + * + * This function is also useful in a situation where both instances are + * controlled by the same entity, so that this entity can perform the mutual + * friend adding. In this case, there is no need for a friend request, either. + * + * @param publicKey Public key of a friend to add. Public key is hex string, must be exactry kOCTToxPublicKeyLength length. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendAdd for all error codes. + * + * @return On success returns friend number. On failure returns kOCTToxFriendNumberFailure. + */ +- (OCTToxFriendNumber)addFriendWithNoRequestWithPublicKey:(NSString *)publicKey error:(NSError **)error; + +/** + * Remove a friend from the friend list. + * + * This does not notify the friend of their deletion. After calling this + * function, this client will appear offline to the friend and no communication + * can occur between the two. + * + * @param friendNumber Friend number to remove. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendDelete for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)deleteFriendWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +/** + * Return the friend number associated with that Public Key. + * + * @param publicKey Public key of a friend. Public key is hex string, must be exactry kOCTToxPublicKeyLength length. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendByPublicKey for all error codes. + * + * @return The friend number on success, kOCTToxFriendNumberFailure on failure. + */ +- (OCTToxFriendNumber)friendNumberWithPublicKey:(NSString *)publicKey error:(NSError **)error; + +/** + * Get public key from associated friend number. + * + * @param friendNumber Associated friend number + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendGetPublicKey for all error codes. + * + * @return Public key of a friend. Public key is hex string, must be exactry kOCTToxPublicKeyLength length. If there is no such friend returns nil. + */ +- (NSString *)publicKeyFromFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +/** + * Checks if there exists a friend with given friendNumber. + * + * @param friendNumber Friend number to check. + * + * @return YES if friend exists, NO otherwise. + */ +- (BOOL)friendExistsWithFriendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Return a date of the last time the friend associated with a given friend number was seen online. + * + * @param friendNumber The friend number you want to query. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendGetLastOnline for all error codes. + * + * @return Date of the last time friend was seen online. + */ +- (NSDate *)friendGetLastOnlineWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +/** + * Return Friend's capabilities. A 64 bit unsigned integer. + */ +- (OCTToxCapabilities)friendGetCapabilitiesWithFriendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Return the friend's user status (away/busy/...). If the friend number is + * invalid, the return value is unspecified. + * + * @param friendNumber Friend number to check status. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendQuery for all error codes. + * + * @return Returns friend status. + */ +- (OCTToxUserStatus)friendStatusWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +/** + * Check whether a friend is currently connected to this client. + * + * @param friendNumber Friend number to check status. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendQuery for all error codes. + * + * @return Returns friend connection status. + */ +- (OCTToxConnectionStatus)friendConnectionStatusWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +/** + * Send a custom lossless bytes to an online friend. + * + * @param friendNumber Friend number to send the bytes. + * @param pktid the Tox Packet ID. + * @param data String that will be converted into bytes using UTF-8 encoding. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * + * @return YES on success, NO on failure. + */ +- (BOOL)sendLosslessPacketWithFriendNumber:(OCTToxFriendNumber)friendNumber + pktid:(uint8_t)pktid + data:(NSString *)data + error:(NSError **)error; +/** + * Send a text chat message to an online friend. + * + * @param friendNumber Friend number to send a message. + * @param type Type of the message. + * @param message Message that would be send. + * @param msgv3HashHex the messagev3 Hash Hexstring or nil. + * @param msgv3tssec the messagev3 sendtimestamp in unixtimestamp or "0" if not used. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendSendMessage for all error codes. + * + * @return The message id. Message IDs are unique per friend. The first message ID is 0. Message IDs are + * incremented by 1 each time a message is sent. If UINT32_MAX messages were sent, the next message ID is 0. + * + * @warning You can check maximum length of message with `-checkLengthOfString:withCheckType:` method with + * OCTToxCheckLengthTypeSendMessage type. + */ +- (OCTToxMessageId)sendMessageWithFriendNumber:(OCTToxFriendNumber)friendNumber + type:(OCTToxMessageType)type + message:(NSString *)message + msgv3HashHex:(NSString *)msgv3HashHex + msgv3tssec:(UInt32)msgv3tssec + error:(NSError **)error; + +/** + * Set the nickname for the Tox client. + * + * @param name Name to be set. Minimum length of name is 1 byte. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorSetInfoCode for all error codes. + * + * @return YES on success, NO on failure. + * + * @warning You can check maximum length of message with `-checkLengthOfString:withCheckType:` method with + * OCTToxCheckLengthTypeName type. + */ +- (BOOL)setNickname:(NSString *)name error:(NSError **)error; + +/** + * Get your nickname. + * + * @return Your nickname or nil in case of error. + */ +- (NSString *)userName; + +/** + * Get name of friendNumber. + * + * @param friendNumber Friend number to get name. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendQuery for all error codes. + * + * @return Name of friend or nil in case of error. + */ +- (NSString *)friendNameWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +/** + * Set our status message. + * + * @param statusMessage Status message to be set. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorSetInfoCode for all error codes. + * + * @return YES on success, NO on failure. + * + * @warning You can check maximum length of message with `-checkLengthOfString:withCheckType:` method with + * OCTToxCheckLengthTypeStatusMessage type. + */ +- (BOOL)setUserStatusMessage:(NSString *)statusMessage error:(NSError **)error; + +/** + * Get our status message. + * + * @return Our status message. + */ +- (NSString *)userStatusMessage; + +/** + * Get status message of a friend. + * + * @param friendNumber Friend number to get status message. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendQuery for all error codes. + * + * @return Status message of a friend. + */ +- (NSString *)friendStatusMessageWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +/** + * Set our typing status for a friend. You are responsible for turning it on or off. + * + * @param isTyping Status showing whether user is typing or not. + * @param friendNumber Friend number to set typing status. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorSetTyping for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)setUserIsTyping:(BOOL)isTyping forFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +/** + * Get the typing status of a friend. + * + * @param friendNumber Friend number to get typing status. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFriendQuery for all error codes. + * + * @return YES if friend is typing, otherwise NO. + */ +- (BOOL)isFriendTypingWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +/** + * Return the number of friends. + * + * @return Return the number of friends. + */ +- (NSUInteger)friendsCount; + +/** + * Return an array of valid friend IDs. + * + * @return Return an array of valid friend IDs. Array contain NSNumbers with IDs. + */ +- (NSArray *)friendsArray; + +/** + * Generates a cryptographic hash of the given data. + * This function may be used by clients for any purpose, but is provided primarily for + * validating cached avatars. This use is highly recommended to avoid unnecessary avatar + * updates. + * + * @param data Data to be hashed + * + * @return Hash generated from data. + */ +- (NSData *)hashData:(NSData *)data; + +/** + * Sends a file control command to a friend for a given file transfer. + * + * @param fileNumber The friend-specific identifier for the file transfer. + * @param friendNumber The friend number of the friend the file is being transferred to or received from. + * @param control The control command to send. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFileControl for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)fileSendControlForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + control:(OCTToxFileControl)control + error:(NSError **)error; + +/** + * Sends a file seek control command to a friend for a given file transfer. + * + * This function can only be called to resume a file transfer right before + * OCTToxFileControlResume is sent. + * + * @param fileNumber The friend-specific identifier for the file transfer. + * @param friendNumber The friend number of the friend the file is being received from. + * @param position The position that the file should be seeked to. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFileSeek for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)fileSeekForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + position:(OCTToxFileSize)position + error:(NSError **)error; + +/** + * Get the file id associated to the file transfer. + * + * @param fileNumber The friend-specific identifier for the file transfer. + * @param friendNumber The friend number of the friend the file is being transferred to or received from. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFileGet for all error codes. + * + * @return File id on success, nil on failure. + */ +- (NSData *)fileGetFileIdForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + error:(NSError **)error; + +/** + * Send a file transmission request. + * + * Maximum filename length is kOCTToxMaxFileNameLength bytes. The filename should generally just be + * a file name, not a path with directory names. + * + * If a non-zero file size is provided, this can be used by both sides to + * determine the sending progress. File size can be set to zero for streaming + * data of unknown size. + * + * File transmission occurs in chunks, which are requested through the + * `fileChunkRequest` callback. + * + * When a friend goes offline, all file transfers associated with the friend are + * purged from core. + * + * If the file contents change during a transfer, the behaviour is unspecified + * in general. What will actually happen depends on the mode in which the file + * was modified and how the client determines the file size. + * + * - If the file size was increased + * - and sending mode was streaming (fileSize = kOCTToxFileSizeUnknown), the behaviour will be as + * expected. + * - and sending mode was file (fileSize != kOCTToxFileSizeUnknown), the fileChunkRequest + * callback will receive length = 0 when Core thinks the file transfer has + * finished. If the client remembers the file size as it was when sending + * the request, it will terminate the transfer normally. If the client + * re-reads the size, it will think the friend cancelled the transfer. + * - If the file size was decreased + * - and sending mode was streaming, the behaviour is as expected. + * - and sending mode was file, the callback will return 0 at the new + * (earlier) end-of-file, signalling to the friend that the transfer was + * cancelled. + * - If the file contents were modified + * - at a position before the current read, the two files (local and remote) + * will differ after the transfer terminates. + * - at a position after the current read, the file transfer will succeed as + * expected. + * - In either case, both sides will regard the transfer as complete and + * successful. + * + * @param friendNumber The friend number of the friend the file send request should be sent to. + * @param kind The meaning of the file to be sent. + * @param fileSize Size in bytes of the file the client wants to send, kOCTToxFileSizeUnknown if unknown or streaming. + * @param fileId A file identifier of length kOCTToxFileIdLength that can be used to + * uniquely identify file transfers across core restarts. If nil, a random one will + * be generated by core. It can then be obtained by using `fileGetFileId`. + * @param fileName Name of the file. Does not need to be the actual name. This + * name will be sent along with the file send request. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFileSend for all error codes. + * + * @return A file number used as an identifier in subsequent callbacks. This + * number is per friend. File numbers are reused after a transfer terminates. + * on failure, this function returns kOCTToxFileNumberFailure. + */ +- (OCTToxFileNumber)fileSendWithFriendNumber:(OCTToxFriendNumber)friendNumber + kind:(OCTToxFileKind)kind + fileSize:(OCTToxFileSize)fileSize + fileId:(NSData *)fileId + fileName:(NSString *)fileName + error:(NSError **)error; + +/** + * Send a chunk of file data to a friend. + * + * This method is called in response to the `fileChunkRequest` callback. The + * length of data should be equal to the one received though the callback. + * If it is zero, the transfer is assumed complete. For files with known size, + * Core will know that the transfer is complete after the last byte has been + * received, so it is not necessary (though not harmful) to send a zero-length + * chunk to terminate. For streams, core will know that the transfer is finished + * if a chunk with length less than the length requested in the callback is sent. + * + * @param friendNumber The friend number of the receiving friend for this file. + * @param fileNumber The file transfer identifier returned by fileSend. + * @param position The file or stream position from which to continue reading. + * @param data Data of chunk to send. May be nil, then transfer is assumed complete. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxErrorFileSendChunk for all error codes. + * + * @return YES on success, NO on failure. + */ +- (BOOL)fileSendChunkForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + position:(OCTToxFileSize)position + data:(NSData *)data + error:(NSError **)error; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxAV.h b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxAV.h new file mode 100644 index 0000000..09a5659 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxAV.h @@ -0,0 +1,148 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTToxAVConstants.h" +#import "OCTToxConstants.h" +#import "OCTToxAVDelegate.h" + +@class OCTTox; + +@interface OCTToxAV : NSObject + +@property (weak, nonatomic) id delegate; + +#pragma mark - Lifecycle + +/** + * Creates a new Toxav object. + * @param tox Tox object to be initialized with. + * @param error If an error occurs, this pointer is set to an actual error object. + */ +- (instancetype)initWithTox:(OCTTox *)tox error:(NSError **)error; + +/** + * Starts the main loop of the ToxAV on it's own unique queue. + * + * @warning ToxAV won't do anything without calling this method. + */ +- (void)start; + +/** + * Stops the main loop of the ToxAV. + */ +- (void)stop; + +#pragma mark - Call Methods + +/** + * Call a friend. This will start ringing the friend. + * It is the client's responsibility to stop ringing after a certain timeout, + * if such behaviour is desired. If the client does not stop ringing, the + * library will not stop until the friend is disconnected. + * @param friendNumber The friend number of the friend that should be called. + * @param audioBitRate Audio bit rate in Kb/sec. Set this to kOCTToxAVAudioBitRateDisable to disable audio sending. + * @param videoBitRate Video bit rate in Kb/sec. Set this to kOCTToxAVVideoBitRateDisable to disable video sending. + * video sending. + * @param error If an error occurs, this pointer is set to an actual error object. + */ +- (BOOL)callFriendNumber:(OCTToxFriendNumber)friendNumber audioBitRate:(OCTToxAVAudioBitRate)audioBitRate videoBitRate:(OCTToxAVVideoBitRate)videoBitRate error:(NSError **)error; + +/** + * Accept an incoming call. + * + * If answering fails for any reason, the call will still be pending and it is + * possible to try and answer it later. + * + * @param friendNumber The friend number of the friend that is calling. + * @param audioBitRate Audio bit rate in Kb/sec. Set this to kOCTToxAVAudioBitRateDisable to disable + * audio sending. + * @param videoBitRate Video bit rate in Kb/sec. Set this to kOCTToxAVVideoBitRateDisable to disable + * video sending. + */ +- (BOOL)answerIncomingCallFromFriend:(OCTToxFriendNumber)friendNumber audioBitRate:(OCTToxAVAudioBitRate)audioBitRate videoBitRate:(OCTToxAVVideoBitRate)videoBitrate error:(NSError **)error; + +/** + * Send a call control to a friend + * @param control The control command to send. + * @param friendNumber The friend number of the friend this client is in a call with. + */ +- (BOOL)sendCallControl:(OCTToxAVCallControl)control toFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +#pragma mark - Controlling bit rates +/** + * Set the audio bit rate to be used in subsequent audio frames. If the passed + * bit rate is the same as the current bit rate this function will return true + * without calling a callback. If there is an active non forceful setup with the + * passed audio bit rate and the new set request is forceful, the bit rate is + * forcefully set and the previous non forceful request is cancelled. The active + * non forceful setup will be canceled in favour of new non forceful setup. + * @param bitRate The new audio bit rate in Kb/sec. Set to kOCTToxAVAudioBitRateDisable to disable audio sending. + * @param friendNumber The friend for which to set the audio bit rate. + * @param error If an error occurs, this pointer is set to an actual error object. + */ +- (BOOL)setAudioBitRate:(OCTToxAVAudioBitRate)bitRate force:(BOOL)force forFriend:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +/** + * Set the video bit rate to be used in subsequent video frames. If the passed + * bit rate is the same as the current bit rate this function will return true + * without calling a callback. If there is an active non forceful setup with the + * passed video bit rate and the new set request is forceful, the bit rate is + * forcefully set and the previous non forceful request is cancelled. The active + * non forceful setup will be canceled in favour of new non forceful setup. + * @param bitRate The new video bit rate in Kb/sec. Set to kOCTToxAVVideoBitRateDisable to disable video sending. + * @param friendNumber The friend for which to set the video bit rate. + * @param error If an error occurs, this pointer is set to an actual error object. + */ +- (BOOL)setVideoBitRate:(OCTToxAVVideoBitRate)bitRate force:(BOOL)force forFriend:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +#pragma mark - Sending frames + +/** + * Send an audio frame to a friend. + * + * The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]... + * Meaning: sample 1 for channel 1, sample 1 for channel 2, ... + * For mono audio, this has no meaning, every sample is subsequent. For stereo, + * this means the expected format is LRLRLR... with samples for left and right + * alternating. + * @param pcm An array of audio samples. The size of this array must be + * sample_count * channels. + * @param sampleCount Number of samples in this frame. Valid numbers here are + * ((sample rate) * (audio length) / 1000), where audio length can be + * 2.5, 5, 10, 20, 40 or 60 millseconds. + * @param channels Number of audio channels. Supported values are 1 and 2. + * @param samplingRate Audio sampling rate used in this frame. Valid sampling + * rates are 8000, 12000, 16000, 24000, or 48000. + * @param friendNumber The friend number of the friend to which to send an + * audio frame. + * @param error If an error occurs, this pointer is set to an actual error object. + */ +- (BOOL)sendAudioFrame:(OCTToxAVPCMData *)pcm sampleCount:(OCTToxAVSampleCount)sampleCount + channels:(OCTToxAVChannels)channels sampleRate:(OCTToxAVSampleRate)sampleRate + toFriend:(OCTToxFriendNumber)friendNumber error:(NSError **)error; + +/** + * Send a video frame to a friend. + * + * Y - plane should be of size: height * width + * U - plane should be of size: (height/2) * (width/2) + * V - plane should be of size: (height/2) * (width/2) + * + * @param friendNumber The friend number of the friend to which to send a video + * frame. + * @param width Width of the frame in pixels. + * @param height Height of the frame in pixels. + * @param y Y (Luminance) plane data. + * @param u U (Chroma) plane data. + * @param v V (Chroma) plane data. + * @param error If an error occurs, this pointer is set to an actual error object. + */ +- (BOOL)sendVideoFrametoFriend:(OCTToxFriendNumber)friendNumber + width:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height + yPlane:(OCTToxAVPlaneData *)yPlane uPlane:(OCTToxAVPlaneData *)uPlane + vPlane:(OCTToxAVPlaneData *)vPlane + error:(NSError **)error; +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxAVConstants.h b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxAVConstants.h new file mode 100644 index 0000000..56d227f --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxAVConstants.h @@ -0,0 +1,339 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +typedef const int16_t OCTToxAVPCMData; +typedef size_t OCTToxAVSampleCount; +typedef uint8_t OCTToxAVChannels; +typedef uint32_t OCTToxAVSampleRate; + +typedef int OCTToxAVVideoBitRate; +typedef uint16_t OCTToxAVVideoWidth; +typedef uint16_t OCTToxAVVideoHeight; +typedef const uint8_t OCTToxAVPlaneData; +typedef const int32_t OCTToxAVStrideData; + +extern const OCTToxAVVideoBitRate kOCTToxAVVideoBitRateDisable; + +extern NSString *const kOCTToxAVErrorDomain; + +/******************************************************************************* + * + * Call state graph + * + ******************************************************************************/ + +typedef NS_OPTIONS(NSInteger, OCTToxAVCallState) { + + /** + * The call is paused by the friend. + */ + OCTToxAVFriendCallStatePaused = 0, + + /** + * Set by the AV core if an error occurred on the remote end or if friend + * timed out. This is the final state after which no more state + * transitions can occur for the call. This call state will never be triggered + * in combination with other call states. + */ + OCTToxAVFriendCallStateError = 1 << 0, + + /** + * The call has finished. This is the final state after which no more state + * transitions can occur for the call. This call state will never be + * triggered in combination with other call states. + */ + OCTToxAVFriendCallStateFinished = 1 << 1, + + /** + * The flag that marks that friend is sending audio. + */ + OCTToxAVFriendCallStateSendingAudio = 1 << 2, + + /** + * The flag that marks that friend is sending video. + */ + OCTToxAVFriendCallStateSendingVideo = 1 << 3, + + /** + * The flag that marks that friend is accepting audio. + */ + OCTToxAVFriendCallStateAcceptingAudio = 1 << 4, + + /** + * The flag that marks that friend is accepting video. + */ + OCTToxAVFriendCallStateAcceptingVideo = 1 << 5, +}; + +/******************************************************************************* + * + * Error Codes + * + ******************************************************************************/ + +/** + * Error codes for init method. + */ +typedef NS_ENUM(NSInteger, OCTToxAVErrorInitCode) { + OCTToxAVErrorInitCodeUnknown, + /** + * One of the arguments to the function was NULL when it was not expected. + */ + OCTToxAVErrorInitNULL, + + /** + * Memory allocation failure while trying to allocate structures required for + * the A/V session. + */ + OCTToxAVErrorInitCodeMemoryError, + + /** + * Attempted to create a second session for the same Tox instance. + */ + OCTToxAVErrorInitMultiple, +}; + +/** + * Error codes for call setup. + */ +typedef NS_ENUM(NSInteger, OCTToxAVErrorCall) { + OCTToxAVErrorCallUnknown, + + /** + * A resource allocation error occurred while trying to create the structures + * required for the call. + */ + OCTToxAVErrorCallMalloc, + + /** + * Synchronization error occurred. + */ + OCTToxAVErrorCallSync, + + /** + * The friend number did not designate a valid friend. + */ + OCTToxAVErrorCallFriendNotFound, + + /** + * The friend was valid, but not currently connected. + */ + OCTToxAVErrorCallFriendNotConnected, + + /** + * Attempted to call a friend while already in an audio or video call with + * them. + */ + OCTToxAVErrorCallAlreadyInCall, + + /** + * Audio or video bit rate is invalid. + */ + OCTToxAVErrorCallInvalidBitRate, +}; + +/** + * Error codes for answer calls. + */ +typedef NS_ENUM(NSInteger, OCTToxAVErrorAnswer) { + OCTToxAVErrorAnswerUnknown, + + /** + * Synchronization error occurred. + */ + OCTToxAVErrorAnswerSync, + + /** + * Failed to initialize codecs for call session. Note that codec initiation + * will fail if there is no receive callback registered for either audio or + * video. + */ + OCTToxAVErrorAnswerCodecInitialization, + + /** + * The friend number did not designate a valid friend. + */ + OCTToxAVErrorAnswerFriendNotFound, + + /** + * The friend was valid, but they are not currently trying to initiate a call. + * This is also returned if this client is already in a call with the friend. + */ + OCTToxAVErrorAnswerFriendNotCalling, + + /** + * Audio or video bit rate is invalid. + */ + OCTToxAVErrorAnswerInvalidBitRate, +}; + +/** + * Error codes for when sending controls. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorCallControl) { + OCTToxAVErrorControlUnknown, + + /** + * Synchronization error occurred. + */ + OCTToxAVErrorControlSync, + + /** + * The friend number passed did not designate a valid friend. + */ + OCTToxAVErrorControlFriendNotFound, + + /** + * This client is currently not in a call with the friend. Before the call is + * answered, only CANCEL is a valid control. + */ + OCTToxAVErrorControlFriendNotInCall, + + /** + * Happens if user tried to pause an already paused call or if trying to + * resume a call that is not paused. + */ + OCTToxAVErrorControlInvaldTransition, + +}; + +/** + * Error codes for setting the bit rate. + */ +typedef NS_ENUM(NSInteger, OCTToxAVErrorSetBitRate) { + OCTToxAVErrorSetBitRateUnknown, + + /** + * Synchronization error occured. + */ + OCTToxAVErrorSetBitRateSync, + + /** + * The bit rate passed was not one of the supported values. + */ + OCTToxAVErrorSetBitRateInvalidBitRate, + + /** + * The friend number passed did not designate a valid friend. + */ + OCTToxAVErrorSetBitRateFriendNotFound, + + /** + * This client is currently not in a call with the friend. + */ + OCTToxAVErrorSetBitRateFriendNotInCall, +}; + +/** + * Error codes for sending audio/video frames + */ +typedef NS_ENUM(NSInteger, OCTToxAVErrorSendFrame) { + OCTToxAVErrorSendFrameUnknown, + + /** + * In case of video, one of Y, U, or V was NULL. In case of audio, the samples + * data pointer was NULL. + */ + OCTToxAVErrorSendFrameNull, + + /** + * The friend number passed did not designate a valid friend. + */ + OCTToxAVErrorSendFrameFriendNotFound, + + /** + * This client is currently not in a call with the friend. + */ + OCTToxAVErrorSendFrameFriendNotInCall, + + /** + * Synchronization error occurred. + */ + OCTToxAVErrorSendFrameSync, + + /** + * One of the frame parameters was invalid. E.g. the resolution may be too + * small or too large, or the audio sampling rate may be unsupported. + */ + OCTToxAVErrorSendFrameInvalid, + + /** + * Bit rate for this payload type was not set up. + */ + OCTToxAVErrorSendFramePayloadTypeDisabled, + + /** + * Failed to push frame through rtp interface. + */ + OCTToxAVErrorSendFrameRTPFailed, +}; + +/******************************************************************************* + * + * Call control + * + ******************************************************************************/ +typedef NS_ENUM(NSInteger, OCTToxAVCallControl) { + /** + * Resume a previously paused call. Only valid if the pause was caused by this + * client, if not, this control is ignored. Not valid before the call is accepted. + */ + OCTToxAVCallControlResume, + + /** + * Put a call on hold. Not valid before the call is accepted. + */ + OCTToxAVCallControlPause, + + /** + * Reject a call if it was not answered, yet. Cancel a call after it was + * answered. + */ + OCTToxAVCallControlCancel, + + /** + * Request that the friend stops sending audio. Regardless of the friend's + * compliance, this will cause the audio_receive_frame event to stop being + * triggered on receiving an audio frame from the friend. + */ + OCTToxAVCallControlMuteAudio, + + /** + * Calling this control will notify client to start sending audio again. + */ + OCTToxAVCallControlUnmuteAudio, + + /** + * Request that the friend stops sending video. Regardless of the friend's + * compliance, this will cause the video_receive_frame event to stop being + * triggered on receiving an video frame from the friend. + */ + OCTToxAVCallControlHideVideo, + + /** + * Calling this control will notify client to start sending video again. + */ + OCTToxAVCallControlShowVideo, +}; + +/******************************************************************************* + * + * Audio Bitrates. All bitrates are in kb/s. + * + ******************************************************************************/ +typedef NS_ENUM(NSInteger, OCTToxAVAudioBitRate) { + OCTToxAVAudioBitRateDisabled = 0, + + OCTToxAVAudioBitRate8 = 8, + + OCTToxAVAudioBitRate16 = 16, + + OCTToxAVAudioBitRate24 = 24, + + OCTToxAVAudioBitRate32 = 32, + + OCTToxAVAudioBitRate48 = 48, +}; diff --git a/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxAVDelegate.h b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxAVDelegate.h new file mode 100644 index 0000000..5236aca --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxAVDelegate.h @@ -0,0 +1,95 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTToxAVConstants.h" +#import "OCTToxConstants.h" + +@class OCTToxAV; + +/** + * All delegate methods will be called on main thread. + */ +@protocol OCTToxAVDelegate + +@optional + +/** + * Receiving call from friend. + * @param audio YES audio is enabled. NO otherwise. + * @param video YES video is enabled. NO otherwise. + * @param friendNumber Friend number who is calling. + */ +- (void)toxAV:(OCTToxAV *)toxAV receiveCallAudioEnabled:(BOOL)audio videoEnabled:(BOOL)video friendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Call state has changed. + * @param state The new state. + * @param friendNumber Friend number whose state has changed. + */ +- (void)toxAV:(OCTToxAV *)toxAV callStateChanged:(OCTToxAVCallState)state friendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * The event is triggered when the network becomes too saturated for + * current bit rates at which point core suggests new bit rates. + * @param audioBitRate Suggested maximum audio bit rate in Kb/sec. + * @param friendNumber The friend number of the friend for which to set the bit rate. + */ +- (void)toxAV:(OCTToxAV *)toxAV audioBitRateStatus:(OCTToxAVAudioBitRate)audioBitRate forFriendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * The event is triggered when the network becomes too saturated for + * current bit rates at which point core suggests new bit rates. + * @param videoBitRate Suggested maximum video bit rate in Kb/sec. + * @param friendNumber The friend number of the friend for which to set the bit rate. + */ +- (void)toxAV:(OCTToxAV *)toxAV videoBitRateStatus:(OCTToxAVVideoBitRate)videoBitRate forFriendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Received audio frame from friend. + * @param pcm An array of audio samples (sample_count * channels elements). + * @param sampleCount The number of audio samples per channel in the PCM array. + * @param channels Number of audio channels. + * @param sampleRate Sampling rate used in this frame. + * @param friendNumber The friend number of the friend who sent an audio frame. + */ + +- (void) toxAV:(OCTToxAV *)toxAV + receiveAudio:(OCTToxAVPCMData *)pcm + sampleCount:(OCTToxAVSampleCount)sampleCount + channels:(OCTToxAVChannels)channels + sampleRate:(OCTToxAVSampleRate)sampleRate + friendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Received video frame from friend. + * @param width Width of the frame in pixels. + * @param height Height of the frame in pixels. + * @param yPlane + * @param uPlane + * @param vPlane Plane data. + * The size of plane data is derived from width and height where + * Y = MAX(width, abs(ystride)) * height, + * U = MAX(width/2, abs(ustride)) * (height/2) and + * V = MAX(width/2, abs(vstride)) * (height/2). + * @param yStride + * @param uStride + * @param vStride Strides data. Strides represent padding for each plane + * that may or may not be present. You must handle strides in + * your image processing code. Strides are negative if the + * image is bottom-up hence why you MUST abs() it when + * calculating plane buffer size. + * @param friendNumber The friend number of the friend who sent an audio frame. + */ + +- (void) toxAV:(OCTToxAV *)toxAV + receiveVideoFrameWithWidth:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height + yPlane:(OCTToxAVPlaneData *)yPlane uPlane:(OCTToxAVPlaneData *)uPlane + vPlane:(OCTToxAVPlaneData *)vPlane + yStride:(OCTToxAVStrideData)yStride uStride:(OCTToxAVStrideData)uStride + vStride:(OCTToxAVStrideData)vStride + friendNumber:(OCTToxFriendNumber)friendNumber; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxConstants.h b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxConstants.h new file mode 100644 index 0000000..6675e2b --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxConstants.h @@ -0,0 +1,557 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +typedef uint32_t OCTToxNoSpam; +typedef uint16_t OCTToxPort; +typedef int OCTToxFriendNumber; +typedef int OCTToxMessageId; +typedef uint32_t OCTToxFileNumber; +typedef long long OCTToxFileSize; +typedef uint64_t OCTToxCapabilities; + +extern const OCTToxFriendNumber kOCTToxFriendNumberFailure; +extern const OCTToxFileNumber kOCTToxFileNumberFailure; +extern const OCTToxFileSize kOCTToxFileSizeUnknown; + +extern NSString *const kOCTToxErrorDomain; + +/** + * Length of address. Address is hex string, has following format: + * [publicKey (32 bytes, 64 characters)][nospam number (4 bytes, 8 characters)][checksum (2 bytes, 4 characters)] + */ +extern const NSUInteger kOCTToxAddressLength; + +/** + * Length of public key. It is hex string, 32 bytes, 64 characters. + */ +extern const NSUInteger kOCTToxPublicKeyLength; +/** + * Length of secret key. It is hex string, 32 bytes, 64 characters. + */ +extern const NSUInteger kOCTToxSecretKeyLength; + +extern const NSUInteger kOCTToxMaxNameLength; +extern const NSUInteger kOCTToxMaxStatusMessageLength; +extern const NSUInteger kOCTToxMaxFriendRequestLength; +extern const NSUInteger kOCTToxMaxMessageLength; +extern const NSUInteger kOCTToxMaxCustomPacketSize; +extern const NSUInteger kOCTToxMaxFileNameLength; + +extern const NSUInteger kOCTToxHashLength; +extern const NSUInteger kOCTToxFileIdLength; + +typedef NS_ENUM(NSInteger, OCTToxProxyType) { + OCTToxProxyTypeNone, + OCTToxProxyTypeSocks5, + OCTToxProxyTypeHTTP, +}; + +typedef NS_ENUM(NSInteger, OCTToxConnectionStatus) { + /** + * There is no connection. This instance, or the friend the state change is about, is now offline. + */ + OCTToxConnectionStatusNone, + + /** + * A TCP connection has been established. For the own instance, this means it + * is connected through a TCP relay, only. For a friend, this means that the + * connection to that particular friend goes through a TCP relay. + */ + OCTToxConnectionStatusTCP, + + /** + * A UDP connection has been established. For the own instance, this means it + * is able to send UDP packets to DHT nodes, but may still be connected to + * a TCP relay. For a friend, this means that the connection to that + * particular friend was built using direct UDP packets. + */ + OCTToxConnectionStatusUDP, +}; + +typedef NS_ENUM(NSInteger, OCTToxUserStatus) { + /** + * User is online and available. + */ + OCTToxUserStatusNone, + /** + * User is away. Clients can set this e.g. after a user defined + * inactivity time. + */ + OCTToxUserStatusAway, + /** + * User is busy. Signals to other clients that this client does not + * currently wish to communicate. + */ + OCTToxUserStatusBusy, +}; + +typedef NS_ENUM(NSInteger, OCTToxMessageType) { + /** + * Normal text message. Similar to PRIVMSG on IRC. + */ + OCTToxMessageTypeNormal, + /** + * A message describing an user action. This is similar to /me (CTCP ACTION) + * on IRC. + */ + OCTToxMessageTypeAction, + /** + * msgV3 HIGH LEVEL ACK message. + */ + OCTToxMessageTypeHighlevelack, +}; + +typedef NS_ENUM(NSInteger, OCTToxFileKind) { + /** + * Arbitrary file data. Clients can choose to handle it based on the file name + * or magic or any other way they choose. + */ + OCTToxFileKindData, + + /** + * Avatar filename. This consists of toxHash(image). + * Avatar data. This consists of the image data. + * + * Avatars can be sent at any time the client wishes. Generally, a client will + * send the avatar to a friend when that friend comes online, and to all + * friends when the avatar changed. A client can save some traffic by + * remembering which friend received the updated avatar already and only send + * it if the friend has an out of date avatar. + * + * Clients who receive avatar send requests can reject it (by sending + * OCTToxFileControl before any other controls), or accept it (by + * sending OCTToxFileControlResume). The fileId of length kOCTToxHashLength bytes + * (same length as kOCTToxFileIdLength) will contain the hash. A client can compare + * this hash with a saved hash and send OCTToxFileControlCancel to terminate the avatar + * transfer if it matches. + * + * When fileSize is set to 0 in the transfer request it means that the client has no + * avatar. + */ + OCTToxFileKindAvatar, +}; + +typedef NS_ENUM(NSInteger, OCTToxFileControl) { + /** + * Sent by the receiving side to accept a file send request. Also sent after a + * OCTToxFileControlPause command to continue sending or receiving. + */ + OCTToxFileControlResume, + + /** + * Sent by clients to pause the file transfer. The initial state of a file + * transfer is always paused on the receiving side and running on the sending + * side. If both the sending and receiving side pause the transfer, then both + * need to send OCTToxFileControlResume for the transfer to resume. + */ + OCTToxFileControlPause, + + /** + * Sent by the receiving side to reject a file send request before any other + * commands are sent. Also sent by either side to terminate a file transfer. + */ + OCTToxFileControlCancel, +}; + + +/******************************************************************************* + * + * Error Codes + * + ******************************************************************************/ + +/** + * Error codes for init method. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorInitCode) { + OCTToxErrorInitCodeUnknown, + /** + * Was unable to allocate enough memory to store the internal structures for the Tox object. + */ + OCTToxErrorInitCodeMemoryError, + + /** + * Was unable to bind to a port. This may mean that all ports have already been bound, + * e.g. by other Tox instances, or it may mean a permission error. + */ + OCTToxErrorInitCodePortAlloc, + + /** + * proxyType was invalid. + */ + OCTToxErrorInitCodeProxyBadType, + + /** + * proxyAddress had an invalid format or was nil (while proxyType was set). + */ + OCTToxErrorInitCodeProxyBadHost, + + /** + * proxyPort was invalid. + */ + OCTToxErrorInitCodeProxyBadPort, + + /** + * The proxy host passed could not be resolved. + */ + OCTToxErrorInitCodeProxyNotFound, + + /** + * The saved data to be loaded contained an encrypted save. + */ + OCTToxErrorInitCodeEncrypted, + + /** + * The data format was invalid. This can happen when loading data that was + * saved by an older version of Tox, or when the data has been corrupted. + * When loading from badly formatted data, some data may have been loaded, + * and the rest is discarded. Passing an invalid length parameter also + * causes this error. + */ + OCTToxErrorInitCodeLoadBadFormat, +}; + +/** + * Error codes for bootstrap and addTCPRelay methods. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorBootstrapCode) { + OCTToxErrorBootstrapCodeUnknown, + + /** + * The host could not be resolved to an IP address, or the IP address passed was invalid. + */ + OCTToxErrorBootstrapCodeBadHost, + + /** + * The port passed was invalid. The valid port range is (1, 65535). + */ + OCTToxErrorBootstrapCodeBadPort, +}; + +/** + * Common error codes for all methods that set a piece of user-visible client information. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorSetInfoCode) { + OCTToxErrorSetInfoCodeUnknow, + + /** + * Information length exceeded maximum permissible size. + */ + OCTToxErrorSetInfoCodeTooLong, +}; + +/** + * Error codes for addFriend method. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorFriendAdd) { + OCTToxErrorFriendAddUnknown, + + /** + * The length of the friend request message exceeded kOCTToxMaxFriendRequestLength. + */ + OCTToxErrorFriendAddTooLong, + + /** + * The friend request message was empty. + */ + OCTToxErrorFriendAddNoMessage, + + /** + * The friend address belongs to the sending client. + */ + OCTToxErrorFriendAddOwnKey, + + /** + * A friend request has already been sent, or the address belongs to a friend + * that is already on the friend list. + */ + OCTToxErrorFriendAddAlreadySent, + + /** + * The friend address checksum failed. + */ + OCTToxErrorFriendAddBadChecksum, + + /** + * The friend was already there, but the nospam value was different. + */ + OCTToxErrorFriendAddSetNewNospam, + + /** + * A memory allocation failed when trying to increase the friend list size. + */ + OCTToxErrorFriendAddMalloc, +}; + +/** + * Error codes for deleteFriend method. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorFriendDelete) { + /** + * There was no friend with the given friend number. No friends were deleted. + */ + OCTToxErrorFriendDeleteNotFound, +}; + +/** + * Error codes for friendNumberWithPublicKey + */ +typedef NS_ENUM(NSInteger, OCTToxErrorFriendByPublicKey) { + OCTToxErrorFriendByPublicKeyUnknown, + + /** + * No friend with the given Public Key exists on the friend list. + */ + OCTToxErrorFriendByPublicKeyNotFound, +}; + +/** + * Error codes for publicKeyFromFriendNumber. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorFriendGetPublicKey) { + /** + * No friend with the given number exists on the friend list. + */ + OCTToxErrorFriendGetPublicKeyFriendNotFound, +}; + +/** + * Error codes for last online methods. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorFriendGetLastOnline) { + /** + * No friend with the given number exists on the friend list. + */ + OCTToxErrorFriendGetLastOnlineFriendNotFound, +}; + +/** + * Error codes for friend state query methods. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorFriendQuery) { + OCTToxErrorFriendQueryUnknown, + + /** + * The friendNumber did not designate a valid friend. + */ + OCTToxErrorFriendQueryFriendNotFound, +}; + +/** + * Error codes for changing isTyping. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorSetTyping) { + /** + * The friend number did not designate a valid friend. + */ + OCTToxErrorSetTypingFriendNotFound, +}; + +/** + * Error codes for sending message. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorFriendSendMessage) { + OCTToxErrorFriendSendMessageUnknown, + + /** + * The friend number did not designate a valid friend. + */ + OCTToxErrorFriendSendMessageFriendNotFound, + + /** + * This client is currently not connected to the friend. + */ + OCTToxErrorFriendSendMessageFriendNotConnected, + + /** + * An allocation error occurred while increasing the send queue size. + */ + OCTToxErrorFriendSendMessageAlloc, + + /** + * Message length exceeded kOCTToxMaxMessageLength. + */ + OCTToxErrorFriendSendMessageTooLong, + + /** + * Attempted to send a zero-length message. + */ + OCTToxErrorFriendSendMessageEmpty, +}; + +/** + * Error codes for sending file control. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorFileControl) { + /** + * The friendNumber passed did not designate a valid friend. + */ + OCTToxErrorFileControlFriendNotFound, + + /** + * This client is currently not connected to the friend. + */ + OCTToxErrorFileControlFriendNotConnected, + + /** + * No file transfer with the given file number was found for the given friend. + */ + OCTToxErrorFileControlNotFound, + + /** + * A OCTToxFileControlResume control was sent, but the file transfer is running normally. + */ + OCTToxErrorFileControlNotPaused, + + /** + * A OCTToxFileControlResume control was sent, but the file transfer was paused by the other + * party. Only the party that paused the transfer can resume it. + */ + OCTToxErrorFileControlDenied, + + /** + * A OCTToxFileControlPause control was sent, but the file transfer was already paused. + */ + OCTToxErrorFileControlAlreadyPaused, + + /** + * Packet queue is full. + */ + OCTToxErrorFileControlSendq, +}; + +/** + * Error codes for file seek method. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorFileSeek) { + /** + * The friendNumber passed did not designate a valid friend. + */ + OCTToxErrorFileSeekFriendNotFound, + + /** + * This client is currently not connected to the friend. + */ + OCTToxErrorFileSeekFriendNotConnected, + + /** + * No file transfer with the given file number was found for the given friend. + */ + OCTToxErrorFileSeekNotFound, + + /** + * File was not in a state where it could be seeked. + */ + OCTToxErrorFileSeekDenied, + + /** + * Seek position was invalid + */ + OCTToxErrorFileSeekInvalidPosition, + + /** + * Packet queue is full. + */ + OCTToxErrorFileSeekSendq, +}; + +/** + * Error codes for fileGetFileId method. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorFileGet) { + /** + * Internal error. + **/ + OCTToxErrorFileGetInternal, + + /** + * The friendNumber passed did not designate a valid friend. + */ + OCTToxErrorFileGetFriendNotFound, + + /** + * No file transfer with the given file number was found for the given friend. + */ + OCTToxErrorFileGetNotFound, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + OCTToxErrorFileGetNULL, +}; + +/** + * Error codes for fileSend method. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorFileSend) { + OCTToxErrorFileSendUnknown, + + /** + * The friendNumber passed did not designate a valid friend. + */ + OCTToxErrorFileSendFriendNotFound, + + /** + * This client is currently not connected to the friend. + */ + OCTToxErrorFileSendFriendNotConnected, + + /** + * Filename length exceeded kOCTToxMaxFileNameLength bytes. + */ + OCTToxErrorFileSendNameTooLong, + + /** + * Too many ongoing transfers. The maximum number of concurrent file transfers + * is 256 per friend per direction (sending and receiving). + */ + OCTToxErrorFileSendTooMany, +}; + +/** + * Error codes for fileSendChunk method. + */ +typedef NS_ENUM(NSInteger, OCTToxErrorFileSendChunk) { + OCTToxErrorFileSendChunkUnknown, + + /** + * The friendNumber passed did not designate a valid friend. + */ + OCTToxErrorFileSendChunkFriendNotFound, + + /** + * This client is currently not connected to the friend. + */ + OCTToxErrorFileSendChunkFriendNotConnected, + + /** + * No file transfer with the given file number was found for the given friend. + */ + OCTToxErrorFileSendChunkNotFound, + + /** + * File transfer was found but isn't in a transferring state: (paused, done, + * broken, etc...) (happens only when not called from the request chunk callback). + */ + OCTToxErrorFileSendChunkNotTransferring, + + /** + * Attempted to send more or less data than requested. The requested data size is + * adjusted according to maximum transmission unit and the expected end of + * the file. Trying to send less or more than requested will return this error. + */ + OCTToxErrorFileSendChunkInvalidLength, + + /** + * Packet queue is full. + */ + OCTToxErrorFileSendChunkSendq, + + /** + * Position parameter was wrong. + */ + OCTToxErrorFileSendChunkWrongPosition, +}; diff --git a/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxDelegate.h b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxDelegate.h new file mode 100644 index 0000000..249008b --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxDelegate.h @@ -0,0 +1,219 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTToxConstants.h" + +@class OCTTox; + +/** + * All delegate methods will be called on main thread. + */ +@protocol OCTToxDelegate + +@optional + +/** + * User connection status changed. + * + * @param connectionStatus New connection status of the user. + */ +- (void)tox:(OCTTox *)tox connectionStatus:(OCTToxConnectionStatus)connectionStatus; + +/** + * Received friend request from a new friend. + * + * @param message Message sent with request. + * @param publicKey New friend public key. + */ +- (void)tox:(OCTTox *)tox friendRequestWithMessage:(NSString *)message publicKey:(NSString *)publicKey; + +/** + * Message received from a friend. + * + * @param message Received message. + * @param type Type of the message. + * @param friendNumber Friend number of appropriate friend. + * @param msgv3HashHex messageV3 Hash as Hexstring or nil. + * @param sendTimestamp unixtimestamp. if msgv3HashHex is nil then timestamp is ignored and current time is used. + */ +- (void)tox:(OCTTox *)tox friendMessage:(NSString *)message + type:(OCTToxMessageType)type + friendNumber:(OCTToxFriendNumber)friendNumber + msgv3HashHex:(NSString *)msgv3HashHex + sendTimestamp:(uint32_t)sendTimestamp; + +/** + * Send msgV3 high level ACK message. + * + * @param message Message text. + * @param friendNumber Friend number of appropriate friend. + * @param msgv3HashHex messageV3 Hash as Hexstring. + * @param sendTimestamp unixtimestamp. + */ +- (void)tox:(OCTTox *)tox sendFriendHighlevelACK:(NSString *)message + friendNumber:(OCTToxFriendNumber)friendNumber + msgv3HashHex:(NSString *)msgv3HashHex + sendTimestamp:(uint32_t)sendTimestamp; +/** + * Friend's name was updated. + * + * @param name Updated name. + * @param friendNumber Friend number of appropriate friend. + */ +- (void)tox:(OCTTox *)tox friendNameUpdate:(NSString *)name friendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Friend's Push Token was updated. + * + * @param pushToken Updated Push Token. + * @param friendNumber Friend number of appropriate friend. + */ +- (void)tox:(OCTTox *)tox friendPushTokenUpdate:(NSString *)pushToken friendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Friend's status message was updated. + * + * @param statusMessage Updated status message. + * @param friendNumber Friend number of appropriate friend. + */ +- (void)tox:(OCTTox *)tox friendStatusMessageUpdate:(NSString *)statusMessage friendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Friend's status was updated. + * + * @param status Updated status. + * @param friendNumber Friend number of appropriate friend. + */ +- (void)tox:(OCTTox *)tox friendStatusUpdate:(OCTToxUserStatus)status friendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Friend's isTyping was updated + * + * @param isTyping Updated typing status. + * @param friendNumber Friend number of appropriate friend. + */ +- (void)tox:(OCTTox *)tox friendIsTypingUpdate:(BOOL)isTyping friendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Friend's Msgv3Capability needs to be updated + * + * @param msgv3Capability Updated msgV3 status. + * @param friendNumber Friend number of appropriate friend. + */ +- (void)tox:(OCTTox *)tox friendSetMsgv3Capability:(BOOL)msgv3Capability friendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Message that was previously sent by us has been delivered to a friend. + * + * @param messageId Id of message. You could get in in sendMessage method. + * @param friendNumber Friend number of appropriate friend. + */ +- (void)tox:(OCTTox *)tox messageDelivered:(OCTToxMessageId)messageId friendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * Message that was previously sent by us has been delivered to a friend. + * + * @param message Received message. UNUSED for now. + * @param friendNumber Friend number of appropriate friend. + * @param msgv3HashHex messageV3 Hash as Hexstring. + * @param sendTimestamp unixtimestamp. + */ +- (void)tox:(OCTTox *)tox friendHighLevelACK:(NSString *)message + friendNumber:(OCTToxFriendNumber)friendNumber + msgv3HashHex:(NSString *)msgv3HashHex + sendTimestamp:(uint32_t)sendTimestamp; + +/** + * Friend's connection status changed. + * + * @param status Updated status. + * @param friendNumber Friend number of appropriate friend. + */ +- (void)tox:(OCTTox *)tox friendConnectionStatusChanged:(OCTToxConnectionStatus)status friendNumber:(OCTToxFriendNumber)friendNumber; + +/** + * This event is triggered when a file control command is received from a friend. + * + * When receiving OCTToxFileControlCancel, the client should release the + * resources associated with the file number and consider the transfer failed. + * + * @param control The control command to send. + * @param friendNumber The friend number of the friend the file is being transferred to or received from. + * @param fileNumber The friend-specific identifier for the file transfer. + */ +- (void) tox:(OCTTox *)tox fileReceiveControl:(OCTToxFileControl)control + friendNumber:(OCTToxFriendNumber)friendNumber + fileNumber:(OCTToxFileNumber)fileNumber; + +/** + * If the length parameter is 0, the file transfer is finished, and the client's + * resources associated with the file number should be released. After a call + * with zero length, the file number can be reused for future file transfers. + * + * If the requested position is not equal to the client's idea of the current + * file or stream position, it will need to seek. In case of read-once streams, + * the client should keep the last read chunk so that a seek back can be + * supported. A seek-back only ever needs to read from the last requested chunk. + * This happens when a chunk was requested, but the send failed. A seek-back + * request can occur an arbitrary number of times for any given chunk. + * + * In response to receiving this callback, the client should call the method + * `fileSendChunk` with the requested chunk. If the number of bytes sent + * through that method is zero, the file transfer is assumed complete. A + * client must send the full length of data requested with this callback. + * + * @param friendNumber The friend number of the receiving friend for this file. + * @param fileNumber The file transfer identifier returned by fileSend. + * @param position The file or stream position from which to continue reading. + * @param length The number of bytes requested for the current chunk. + */ +- (void) tox:(OCTTox *)tox fileChunkRequestForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + position:(OCTToxFileSize)position + length:(size_t)length; + +/** + * The client should acquire resources to be associated with the file transfer. + * Incoming file transfers start in the PAUSED state. After this callback + * returns, a transfer can be rejected by sending a OCTToxFileControlCancel + * control command before any other control commands. It can be accepted by + * sending OCTToxFileControlResume. + * + * @param fileNumber The friend-specific file number the data received is associated with. + * @param friendNumber The friend number of the friend who is sending the file transfer request. + * @param kind The meaning of the file to be sent. + * @param fileSize Size in bytes of the file about to be received from the client, kOCTToxFileSizeUnknown if unknown or streaming. + * @param fileName The name of the file. + */ +- (void) tox:(OCTTox *)tox fileReceiveForFileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + kind:(OCTToxFileKind)kind + fileSize:(OCTToxFileSize)fileSize + fileName:(NSString *)fileName; + +/** + * This method is first called when a file transfer request is received, and + * subsequently when a chunk of file data for an accepted request was received. + * + * When chunk is nil, the transfer is finished and the client should release the + * resources it acquired for the transfer. After a call with chunk = nil, the + * file number can be reused for new file transfers. + * + * If position is equal to fileSize (received in the fileReceive callback) + * when the transfer finishes, the file was received completely. Otherwise, if + * fileSize was kOCTToxFileSizeUnknown, streaming ended successfully when chunk is nil. + * + * @param chunk A data containing the received chunk. + * @param fileNumber The friend-specific file number the data received is associated with. + * @param friendNumber The friend number of the friend who is sending the file. + * @param position The file position of the first byte in data. + */ +- (void) tox:(OCTTox *)tox fileReceiveChunk:(NSData *)chunk + fileNumber:(OCTToxFileNumber)fileNumber + friendNumber:(OCTToxFriendNumber)friendNumber + position:(OCTToxFileSize)position; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxEncryptSave.h b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxEncryptSave.h new file mode 100644 index 0000000..de66ac6 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxEncryptSave.h @@ -0,0 +1,93 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +/** + * This class is used for encryption/decryption of save data. + * + * You can use class methods or create instance and use it's methods. + * Note that instance encryption/decryption methods are much faster because + * instance stores generated encryption key. + */ +@interface OCTToxEncryptSave : NSObject + +/** + * Determines whether or not the given data is encrypted (by checking the magic number). + * + * @param data Data to check. + * + * @return YES if data is encrypted, NO otherwise. + */ ++ (BOOL)isDataEncrypted:(nonnull NSData *)data; + +/** + * Encrypts the given data with the given passphrase. + * + * @param data Data to encrypt. + * @param passphrase Passphrase used to encrypt the data. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxEncryptSaveEncryptionError for all error codes. + * + * @return Encrypted data on success, nil on failure. + */ ++ (nullable NSData *)encryptData:(nonnull NSData *)data + withPassphrase:(nonnull NSString *)passphrase + error:(NSError *__nullable *__nullable)error; + +/** + * Decrypts the given data with the given passphrase. + * + * @param data Data to decrypt. + * @param passphrase Passphrase used to decrypt the data. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxEncryptSaveDecryptionError for all error codes. + * + * @return Decrypted data on success, nil on failure. + */ ++ (nullable NSData *)decryptData:(nonnull NSData *)data + withPassphrase:(nonnull NSString *)passphrase + error:(NSError *__nullable *__nullable)error; + +/** + * Creates new instance of OCTToxEncryptSave object with given passphrase. This instance can be used + * to encrypt and decrypt given data. + * Encryption key is generated and stored in this method. Due to that encrypting/decrypting data + * using instance instead of class methods is much faster, as key derivation is very expensive compared + * to the actual encryption. + * + * @param passphrase Passphrase used to encrypt/decrypt the data. + * @param toxData If you have toxData that you would like to decrypt, you have to pass it here. Salt will be extracted from data and used for key generation. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxEncryptSaveKeyDerivationError for all error codes. + * + * @return Created instance or nil in case of error. + */ +- (nullable instancetype)initWithPassphrase:(nonnull NSString *)passphrase + toxData:(nullable NSData *)toxData + error:(NSError *__nullable *__nullable)error; + +/** + * Encrypts the given data. + * + * @param data Data to encrypt. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxEncryptSaveEncryptionError for all error codes. + * + * @return Encrypted data on success, nil on failure. + */ +- (nullable NSData *)encryptData:(nonnull NSData *)data error:(NSError *__nullable *__nullable)error; + +/** + * Decrypts the given data. + * + * @param data Data to decrypt. + * @param error If an error occurs, this pointer is set to an actual error object containing the error information. + * See OCTToxEncryptSaveDecryptionError for all error codes. + * + * @return Decrypted data on success, nil on failure. + */ +- (nullable NSData *)decryptData:(nonnull NSData *)data error:(NSError *__nullable *__nullable)error; + +@end diff --git a/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxEncryptSaveConstants.h b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxEncryptSaveConstants.h new file mode 100644 index 0000000..765cc0a --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxEncryptSaveConstants.h @@ -0,0 +1,41 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +typedef NS_ENUM(NSInteger, OCTToxEncryptSaveKeyDerivationError) { + OCTToxEncryptSaveKeyDerivationErrorNone, + OCTToxEncryptSaveKeyDerivationErrorFailed, +}; + +typedef NS_ENUM(NSInteger, OCTToxEncryptSaveEncryptionError) { + OCTToxEncryptSaveEncryptionErrorNone, + + /** + * Some input data was empty. + */ + OCTToxEncryptSaveEncryptionErrorNull, + + /** + * Encryption failed. + */ + OCTToxEncryptSaveEncryptionErrorFailed, +}; + +typedef NS_ENUM(NSInteger, OCTToxEncryptSaveDecryptionError) { + OCTToxEncryptSaveDecryptionErrorNone, + + /** + * Some input data was empty. + */ + OCTToxEncryptSaveDecryptionErrorNull, + + /** + * The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted). + */ + OCTToxEncryptSaveDecryptionErrorBadFormat, + + /** + * The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect. + */ + OCTToxEncryptSaveDecryptionErrorFailed, +}; diff --git a/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxOptions.h b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxOptions.h new file mode 100644 index 0000000..45f8e55 --- /dev/null +++ b/local_pod_repo/objcTox/Classes/Public/Wrapper/OCTToxOptions.h @@ -0,0 +1,103 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTToxConstants.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * After creation OCTToxOptions will have predefined default values. + */ +@interface OCTToxOptions : NSObject + +/** + * The type of socket to create. + * + * If this is set to false, an IPv4 socket is created, which subsequently + * only allows IPv4 communication. + * If it is set to true, an IPv6 socket is created, allowing both IPv4 and + * IPv6 communication. + */ +@property (nonatomic, assign) BOOL ipv6Enabled; + +/** + * Enable the use of UDP communication when available. + * + * Setting this to false will force Tox to use TCP only. Communications will + * need to be relayed through a TCP relay node, potentially slowing them down. + * Disabling UDP support is necessary when using anonymous proxies or Tor. + */ +@property (nonatomic, assign) BOOL udpEnabled; + +/** + * Enable local network peer discovery. + * + * Disabling this will cause Tox to not look for peers on the local network. + */ +@property (nonatomic, assign) BOOL localDiscoveryEnabled; + +/** + * Pass communications through a proxy. + */ +@property (nonatomic, assign) OCTToxProxyType proxyType; + +/** + * The IP address or DNS name of the proxy to be used. + * + * If used, this must be non-NULL and be a valid DNS name. The name must not + * exceed 255 characters. + * + * This member is ignored (it can be nil) if proxyType is OCTToxProxyTypeNone. + */ +@property (nonatomic, copy, nullable) NSString *proxyHost; + +/** + * The port to use to connect to the proxy server. + * + * Ports must be in the range (1, 65535). The value is ignored if + * proxyType is OCTToxProxyTypeNone. + */ +@property (nonatomic, assign) uint16_t proxyPort; + +/** + * The start port of the inclusive port range to attempt to use. + * + * If both start_port and end_port are 0, the default port range will be + * used: [33445, 33545]. + * + * If either start_port or end_port is 0 while the other is non-zero, the + * non-zero port will be the only port in the range. + * + * Having start_port > end_port will yield the same behavior as if start_port + * and end_port were swapped. + */ +@property (nonatomic, assign) uint16_t startPort; + +/** + * The end port of the inclusive port range to attempt to use. + */ +@property (nonatomic, assign) uint16_t endPort; + +/** + * The port to use for the TCP server (relay). If 0, the TCP server is + * disabled. + * + * Enabling it is not required for Tox to function properly. + * + * When enabled, your Tox instance can act as a TCP relay for other Tox + * instance. This leads to increased traffic, thus when writing a client + * it is recommended to enable TCP server only if the user has an option + * to disable it. + */ +@property (nonatomic, assign) uint16_t tcpPort; + +/** + * Enables or disables UDP hole-punching in toxcore. (Default: enabled). + */ +@property (nonatomic, assign) BOOL holePunchingEnabled; + +@end + +NS_ASSUME_NONNULL_END diff --git a/local_pod_repo/objcTox/Gemfile b/local_pod_repo/objcTox/Gemfile new file mode 100644 index 0000000..b731c6b --- /dev/null +++ b/local_pod_repo/objcTox/Gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +gem 'activesupport', '~> 4.2' +gem 'cocoapods', '~> 1.0.1' +gem 'xcpretty', '~> 0.2.4' diff --git a/local_pod_repo/objcTox/OSXDemo/AppDelegate.h b/local_pod_repo/objcTox/OSXDemo/AppDelegate.h new file mode 100644 index 0000000..c014a71 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/AppDelegate.h @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@interface AppDelegate : NSObject + + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/AppDelegate.m b/local_pod_repo/objcTox/OSXDemo/AppDelegate.m new file mode 100644 index 0000000..2727842 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/AppDelegate.m @@ -0,0 +1,34 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "AppDelegate.h" +#import "OCTMainWindowController.h" + +#import "DDLog.h" +#import "DDASLLogger.h" +#import "DDTTYLogger.h" + +@interface AppDelegate () + +@property (strong, nonatomic) IBOutlet OCTMainWindowController *window; + +@end + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification +{ + [DDLog addLogger:[DDASLLogger sharedInstance]]; + [DDLog addLogger:[DDTTYLogger sharedInstance]]; + + self.window = [[OCTMainWindowController alloc] initWithWindowNibName:@"MainWindow"]; + [self.window showWindow:self]; +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification +{ + // Insert code here to tear down your application +} + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/Base.lproj/MainMenu.xib b/local_pod_repo/objcTox/OSXDemo/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..95addb1 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/Base.lproj/MainMenu.xib @@ -0,0 +1,667 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_pod_repo/objcTox/OSXDemo/Images.xcassets/AppIcon.appiconset/Contents.json b/local_pod_repo/objcTox/OSXDemo/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2db2b1c --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/local_pod_repo/objcTox/OSXDemo/Info.plist b/local_pod_repo/objcTox/OSXDemo/Info.plist new file mode 100644 index 0000000..6863744 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + me.dvor.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2015 dvor. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/local_pod_repo/objcTox/OSXDemo/MainWindow.xib b/local_pod_repo/objcTox/OSXDemo/MainWindow.xib new file mode 100644 index 0000000..c8d41fa --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/MainWindow.xib @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_pod_repo/objcTox/OSXDemo/OCTBootStrap.xib b/local_pod_repo/objcTox/OSXDemo/OCTBootStrap.xib new file mode 100644 index 0000000..49d3016 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTBootStrap.xib @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_pod_repo/objcTox/OSXDemo/OCTBootStrapViewController.h b/local_pod_repo/objcTox/OSXDemo/OCTBootStrapViewController.h new file mode 100644 index 0000000..e44eafe --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTBootStrapViewController.h @@ -0,0 +1,21 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTManagerConfiguration.h" + +@class OCTBootStrapViewController; +@protocol OCTBootStrapViewDelegate + +- (void)didBootStrap:(OCTBootStrapViewController *)controller; + +@end + +@interface OCTBootStrapViewController : NSViewController + +@property (weak, nonatomic) id delegate; + +- (instancetype)initWithConfiguration:(OCTManagerConfiguration *)configuration; + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTBootStrapViewController.m b/local_pod_repo/objcTox/OSXDemo/OCTBootStrapViewController.m new file mode 100644 index 0000000..762d7b7 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTBootStrapViewController.m @@ -0,0 +1,42 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTBootStrapViewController.h" + +static NSString *const kNibName = @"OCTBootStrap"; + +@interface OCTBootStrapViewController () + +@property (strong, nonatomic) OCTManagerConfiguration *configuration; +@property (weak) IBOutlet NSButton *ipv6Button; +@property (weak) IBOutlet NSButton *udpButton; + +@end + +@implementation OCTBootStrapViewController + +- (instancetype)initWithConfiguration:(OCTManagerConfiguration *)configuration +{ + self = [super initWithNibName:kNibName bundle:nil]; + + if (! self) { + return nil; + } + + _configuration = configuration; + + return self; +} + +#pragma mark - Private + +- (IBAction)bootstrapButtonTapped:(NSButton *)sender +{ + self.configuration.options.udpEnabled = self.udpButton.state; + self.configuration.options.ipv6Enabled = self.ipv6Button.state; + + [self.delegate didBootStrap:self]; +} + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTCallsViewController.h b/local_pod_repo/objcTox/OSXDemo/OCTCallsViewController.h new file mode 100644 index 0000000..8dc92f4 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTCallsViewController.h @@ -0,0 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@protocol OCTManager; + +@interface OCTCallsViewController : NSViewController + +- (instancetype)initWithManager:(id)manager; + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTCallsViewController.m b/local_pod_repo/objcTox/OSXDemo/OCTCallsViewController.m new file mode 100644 index 0000000..6c3f001 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTCallsViewController.m @@ -0,0 +1,203 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTCallsViewController.h" +#import "OCTSubmanagerCalls.h" +#import "OCTManager.h" +#import "OCTSubmanagerObjects.h" +#import "OCTCall.h" +#import "RLMCollectionChange+IndexSet.h" + +static NSString *const kCellIdent = @"cellIdent"; + +@interface OCTCallsViewController () + +@property (weak, nonatomic) id manager; + +@property (strong, nonatomic) RLMResults *calls; +@property (strong, nonatomic) RLMNotificationToken *callsNotificationToken; + +@property (weak) IBOutlet NSTableView *callsTableView; +@property (weak) IBOutlet NSView *videoContainerView; +@property (strong, nonatomic) NSView *videoView; + +@end + +@implementation OCTCallsViewController + +- (instancetype)initWithManager:(id)manager +{ + self = [super init]; + + if (! self) { + return nil; + } + + _manager = manager; + + [manager.calls setupAndReturnError:nil]; + + _calls = [manager.objects objectsForType:OCTFetchRequestTypeCall predicate:nil]; + + return self; +} + +- (void)dealloc +{ + [self.callsNotificationToken invalidate]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + __weak typeof(self)weakSelf = self; + + self.callsNotificationToken = [self.calls addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *error) { + if (error) { + NSLog(@"Failed to open Realm on background worker: %@", error); + return; + } + + NSTableView *tableView = weakSelf.callsTableView; + + // Initial run of the query will pass nil for the change information + if (! changes) { + [tableView reloadData]; + return; + } + + [tableView beginUpdates]; + [tableView removeRowsAtIndexes:[changes deletionsSet] withAnimation:NSTableViewAnimationSlideLeft]; + [tableView insertRowsAtIndexes:[changes insertionsSet] withAnimation:NSTableViewAnimationSlideRight]; + [tableView reloadDataForRowIndexes:[changes modificationsSet] columnIndexes:[NSIndexSet indexSetWithIndex:0]]; + [tableView endUpdates]; + }]; +} + +- (void)setupVideoView +{ + if (self.videoView == self.manager.calls.videoFeed) { + return; + } + + if (self.videoView) { + [self.videoView removeFromSuperview]; + self.videoView = nil; + } + + self.videoView = self.manager.calls.videoFeed; + [self.videoContainerView addSubview:self.videoView]; + + NSLayoutConstraint *centerX = [NSLayoutConstraint constraintWithItem:self.videoView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.videoContainerView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]; + NSLayoutConstraint *centerY = [NSLayoutConstraint constraintWithItem:self.videoView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.videoContainerView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]; + NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:self.videoView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.videoContainerView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]; + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self.videoView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.videoContainerView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0.0]; + + self.videoView.translatesAutoresizingMaskIntoConstraints = NO; + [self.videoView setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationVertical]; + [self.videoView setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal]; + + [self.videoContainerView addConstraints:@[centerX, centerY, widthConstraint, heightConstraint]]; +} + +#pragma mark - NSTableViewDelegate + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return self.calls.count; +} + +- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row +{ + return 300.0; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + [self doSomeThingWithCallWithBlock:^(OCTCall *call) { + if (call.videoIsEnabled || call.friendSendingVideo) { + [self setupVideoView]; + } + }]; +} + +#pragma mark - NSTableViewDataSource + +- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + OCTCall *call = self.calls[row]; + + NSTextField *textField = [self.callsTableView makeViewWithIdentifier:kCellIdent owner:nil]; + + if (! textField) { + textField = [NSTextField new]; + textField.identifier = kCellIdent; + } + + textField.stringValue = [NSString stringWithFormat:@"Friend name:%@\n" + @"Call\n" + @"Chat identifier %@\n" + @"call status: %ld\n" + @"callDuration: %f\n" + @"friend sending audio: %d\n" + @"friend receiving audio: %d\n" + @"friend sending video: %d\n" + @"friend receiving Video: %d\n", + call.caller.name, + call.chat.uniqueIdentifier, (long)call.status, call.callDuration, call.friendSendingAudio, call.friendAcceptingAudio, + call.friendSendingVideo, call.friendAcceptingVideo]; + ; + + return textField; +} + +#pragma mark - Actions + +- (IBAction)callActionButtonPressed:(NSButton *)sender +{ + [self doSomeThingWithCallWithBlock:^(OCTCall *call) { + [self.manager.calls answerCall:call enableAudio:YES enableVideo:YES error:nil]; + }]; +} + +- (IBAction)sendCallControlSelected:(NSPopUpButton *)sender +{ + [self doSomeThingWithCallWithBlock:^(OCTCall *call) { + OCTToxAVCallControl control = (OCTToxAVCallControl)[sender indexOfSelectedItem]; + [self.manager.calls sendCallControl:control toCall:call error:nil]; + }]; +} + +#pragma mark - Private + +- (OCTCall *)callForCurrentlySelectedRow +{ + NSInteger selectedRow = self.callsTableView.selectedRow; + + if (selectedRow < 0) { + return nil; + } + + OCTCall *call = self.calls[selectedRow]; + + return call; +} + +- (BOOL)doSomeThingWithCallWithBlock:(void (^)(OCTCall *))block +{ + NSInteger selectedRow = self.callsTableView.selectedRow; + + if (selectedRow < 0) { + return NO; + } + + OCTCall *call = self.calls[selectedRow]; + + block(call); + + return YES; +} + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTCallsViewController.xib b/local_pod_repo/objcTox/OSXDemo/OCTCallsViewController.xib new file mode 100644 index 0000000..5e928be --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTCallsViewController.xib @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_pod_repo/objcTox/OSXDemo/OCTConversationViewController.h b/local_pod_repo/objcTox/OSXDemo/OCTConversationViewController.h new file mode 100644 index 0000000..1045cac --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTConversationViewController.h @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTManager.h" + +@interface OCTConversationViewController : NSViewController + +- (instancetype)initWithManager:(id)manager; + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTConversationViewController.m b/local_pod_repo/objcTox/OSXDemo/OCTConversationViewController.m new file mode 100644 index 0000000..4def06f --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTConversationViewController.m @@ -0,0 +1,270 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTConversationViewController.h" +#import "OCTSubmanagerObjects.h" +#import "OCTSubmanagerChats.h" +#import "OCTFriend.h" +#import "OCTChat.h" +#import "OCTMessageAbstract.h" +#import "OCTSubmanagerUser.h" +#import "OCTSubmanagerCalls.h" +#import "OCTSubmanagerFiles.h" +#import "OCTMessageText.h" +#import "RLMCollectionChange+IndexSet.h" + +static NSString *const kCellIdent = @"cellIdent"; + +@interface OCTConversationViewController () + +@property (weak) IBOutlet NSTableView *chatsViewController; +@property (weak, nonatomic) id manager; + +@property (strong, nonatomic) RLMResults *allChats; +@property (strong, nonatomic) RLMNotificationToken *allChatsNotificationToken; +@property (strong, nonatomic) RLMResults *conversationMessages; +@property (strong, nonatomic) RLMNotificationToken *conversationMessagesNotificationToken; + +@property (weak) IBOutlet NSTableView *chatsTableView; +@property (weak) IBOutlet NSTableView *conversationTableView; + +@end + +@implementation OCTConversationViewController + +- (instancetype)initWithManager:(id)manager +{ + self = [super init]; + + if (! self) { + return nil; + } + + _manager = manager; + + _allChats = [self.manager.objects objectsForType:OCTFetchRequestTypeChat predicate:nil]; + _conversationMessages = nil; + + return self; +} + +- (void)dealloc +{ + [self.allChatsNotificationToken invalidate]; + [self.conversationMessagesNotificationToken invalidate]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + __weak typeof(self)weakSelf = self; + + self.allChatsNotificationToken = [self.allChats addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *error) { + if (error) { + NSLog(@"Failed to open Realm on background worker: %@", error); + return; + } + + NSTableView *tableView = weakSelf.chatsTableView; + + // Initial run of the query will pass nil for the change information + if (! changes) { + [tableView reloadData]; + return; + } + + [tableView beginUpdates]; + [tableView removeRowsAtIndexes:[changes deletionsSet] withAnimation:NSTableViewAnimationSlideLeft]; + [tableView insertRowsAtIndexes:[changes insertionsSet] withAnimation:NSTableViewAnimationSlideRight]; + [tableView reloadDataForRowIndexes:[changes modificationsSet] columnIndexes:[NSIndexSet indexSetWithIndex:0]]; + [tableView endUpdates]; + }]; +} + +#pragma mark - Actions + +- (IBAction)deleteChatButtonPressed:(NSButton *)sender +{ + NSInteger selectedRow = self.chatsTableView.selectedRow; + + if (selectedRow < 0) { + return; + } + + OCTChat *chat = self.allChats[selectedRow]; + + [self.manager.chats removeAllMessagesInChat:chat removeChat:YES]; +} + +- (IBAction)callUserButtonPressed:(NSButton *)sender +{ + NSInteger selectedRow = self.chatsTableView.selectedRow; + + if (selectedRow < 0) { + return; + } + + OCTChat *chat = self.allChats[selectedRow]; + + [self.manager.calls callToChat:chat enableAudio:YES enableVideo:YES error:nil]; +} + +- (IBAction)sendFileButtonPressed:(id)sender +{ + NSInteger selectedRow = self.chatsTableView.selectedRow; + + if (selectedRow < 0) { + return; + } + + OCTChat *chat = self.allChats[selectedRow]; + + NSOpenPanel *panel = [NSOpenPanel openPanel]; + + [panel runModal]; + + NSString *path = [panel.URL path]; + + [self.manager.files sendFileAtPath:path moveToUploads:NO toChat:chat failureBlock:nil]; +} + +#pragma mark - NSTextFieldDelegate + +- (IBAction)chatTextFieldEntered:(NSTextField *)sender +{ + NSInteger selectedRow = self.chatsTableView.selectedRow; + + if (selectedRow < 0) { + return; + } + + OCTChat *chat = self.allChats[selectedRow]; + + [self.manager.chats sendMessageToChat:chat + text:sender.stringValue + type:OCTToxMessageTypeNormal + successBlock:nil + failureBlock:nil]; + sender.stringValue = @""; +} + +#pragma mark - NSTableViewDelegate + +- (NSView *) tableView:(NSTableView *)tableView + viewForTableColumn:(NSTableColumn *)tableColumn + row:(NSInteger)row +{ + NSTextField *field = [tableView + makeViewWithIdentifier:kCellIdent + owner:self]; + + if (! field) { + field = [NSTextField new]; + field.identifier = kCellIdent; + } + + field.selectable = YES; + field.editable = NO; + + + if (tableView == self.chatsTableView) { + OCTChat *chat = self.allChats[row]; + OCTFriend *friend = [chat.friends firstObject]; + + field.stringValue = (friend.isConnected) ? ([NSString stringWithFormat:@"%@ : Online", friend.nickname]) : friend.nickname; + + } + else { + OCTMessageAbstract *messageAbstract = self.conversationMessages[row]; + if (messageAbstract.messageText) { + if ([messageAbstract isOutgoing]) { + field.stringValue = [NSString stringWithFormat:@"%@: %@", self.manager.user.userName, messageAbstract.messageText.text]; + } + else { + OCTFriend *friend = (OCTFriend *)[self.manager.objects objectWithUniqueIdentifier:messageAbstract.senderUniqueIdentifier + forType:OCTFetchRequestTypeFriend]; + + field.stringValue = [NSString stringWithFormat:@"%@: %@", friend.nickname, messageAbstract.messageText.text]; + } + } + } + + return field; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + if (notification.object != self.chatsTableView) { + return; + } + + NSInteger selectedRow = self.chatsTableView.selectedRow; + + if (selectedRow < 0) { + return; + } + + OCTChat *chat = self.allChats[selectedRow]; + + [self updateConversationControllerForChat:chat]; + + [self.conversationTableView reloadData]; +} + +- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row +{ + return 50.0; +} + +#pragma mark - NSTableViewDataSource + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + if (tableView == self.chatsTableView) { + return self.allChats.count; + } + else { + return self.conversationMessages.count; + } + + return 0; +} + +#pragma mark - Private + +- (void)updateConversationControllerForChat:(OCTChat *)chat +{ + [self.conversationMessagesNotificationToken invalidate]; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@", chat.uniqueIdentifier]; + self.conversationMessages = [self.manager.objects objectsForType:OCTFetchRequestTypeMessageAbstract predicate:predicate]; + + __weak typeof(self)weakSelf = self; + + self.conversationMessagesNotificationToken = [self.conversationMessages addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *error) { + if (error) { + NSLog(@"Failed to open Realm on background worker: %@", error); + return; + } + + NSTableView *tableView = weakSelf.conversationTableView; + + // Initial run of the query will pass nil for the change information + if (! changes) { + [tableView reloadData]; + return; + } + + [tableView beginUpdates]; + [tableView removeRowsAtIndexes:[changes deletionsSet] withAnimation:NSTableViewAnimationSlideLeft]; + [tableView insertRowsAtIndexes:[changes insertionsSet] withAnimation:NSTableViewAnimationSlideRight]; + [tableView reloadDataForRowIndexes:[changes modificationsSet] columnIndexes:[NSIndexSet indexSetWithIndex:0]]; + [tableView endUpdates]; + }]; +} + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTConversationViewController.xib b/local_pod_repo/objcTox/OSXDemo/OCTConversationViewController.xib new file mode 100644 index 0000000..955acc9 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTConversationViewController.xib @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_pod_repo/objcTox/OSXDemo/OCTFilesViewController.h b/local_pod_repo/objcTox/OSXDemo/OCTFilesViewController.h new file mode 100644 index 0000000..9bd1eab --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTFilesViewController.h @@ -0,0 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@protocol OCTManager; + +@interface OCTFilesViewController : NSViewController + +- (instancetype)initWithManager:(id)manager; + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTFilesViewController.m b/local_pod_repo/objcTox/OSXDemo/OCTFilesViewController.m new file mode 100644 index 0000000..c3ad559 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTFilesViewController.m @@ -0,0 +1,236 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFilesViewController.h" +#import "OCTManager.h" +#import "OCTSubmanagerObjects.h" +#import "OCTSubmanagerFiles.h" +#import "OCTSubmanagerFilesProgressSubscriber.h" +#import "OCTMessageAbstract.h" +#import "OCTMessageFile.h" + +static NSString *const kCellIdentifier = @"fileCell"; + +@interface OCTFilesViewController () + +@property (weak, nonatomic) id manager; +@property (weak) IBOutlet NSTableView *tableView; + +@property (strong, nonatomic) RLMResults *fileMessages; +@property (strong, nonatomic) RLMNotificationToken *fileMessagesNotificationToken; + +@end + +@implementation OCTFilesViewController + +#pragma mark - Lifecycle + +- (instancetype)initWithManager:(id)manager +{ + self = [super init]; + + if (! self) { + return nil; + } + + _manager = manager; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"messageFile != nil"]; + _fileMessages = [manager.objects objectsForType:OCTFetchRequestTypeMessageAbstract predicate:predicate]; + + return self; +} + +- (void)dealloc +{ + [self.fileMessagesNotificationToken invalidate]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + __weak typeof(self)weakSelf = self; + + self.fileMessagesNotificationToken = [self.fileMessages addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *error) { + [weakSelf.tableView reloadData]; + }]; +} + +- (IBAction)receiveButtonPressed:(id)sender +{ + OCTMessageAbstract *message = [self messageForCurrentlySelectedRow]; + + if (! message) { + return; + } + + [self.manager.files acceptFileTransfer:message failureBlock:nil]; + [self.manager.files addProgressSubscriber:self forFileTransfer:message error:nil]; +} + +- (IBAction)declineButtonPressed:(id)sender +{ + OCTMessageAbstract *message = [self messageForCurrentlySelectedRow]; + + if (! message) { + return; + } + + [self.manager.files cancelFileTransfer:message error:nil]; +} + +- (IBAction)pauseButtonPressed:(id)sender +{ + OCTMessageAbstract *message = [self messageForCurrentlySelectedRow]; + + if (! message) { + return; + } + + [self.manager.files pauseFileTransfer:YES message:message error:nil]; +} + +- (IBAction)resumeButtonPressed:(id)sender +{ + OCTMessageAbstract *message = [self messageForCurrentlySelectedRow]; + + if (! message) { + return; + } + + [self.manager.files pauseFileTransfer:NO message:message error:nil]; +} + +#pragma mark - NSTableViewDelegate + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return self.fileMessages.count; +} + +- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row +{ + return 150.0; +} + +#pragma mark - NSTableViewDataSource + +- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + OCTMessageAbstract *message = self.fileMessages[row]; + + NSTextField *textField = [self.tableView makeViewWithIdentifier:kCellIdentifier owner:nil]; + + if (! textField) { + textField = [NSTextField new]; + textField.identifier = kCellIdentifier; + } + + if ([tableColumn.identifier isEqualToString:@"AutomaticTableColumnIdentifier.0"]) { + textField.stringValue = [NSString stringWithFormat: + @"fileType = %@\n" + @"pausedBy = %@\n" + @"fileSize = %lld\n" + @"fileName = %@\n" + @"filePath = %@\n" + @"fileUTI = %@\n" + @"fileNumber = %d\n" + @"senderUniqueIdentifier = %@", + [self stringFromFileType:message.messageFile.fileType], + [self stringFromPausedBy:message.messageFile.pausedBy], + message.messageFile.fileSize, + message.messageFile.fileName, + message.messageFile.filePath, + message.messageFile.fileUTI, + message.messageFile.internalFileNumber, + message.senderUniqueIdentifier]; + } + else if ([tableColumn.identifier isEqualToString:@"AutomaticTableColumnIdentifier.1"]) { + textField.stringValue = @""; + } + + return textField; +} + +#pragma mark - OCTSubmanagerFilesProgressSubscriber + +- (void)submanagerFilesOnProgressUpdate:(float)progress message:(nonnull OCTMessageAbstract *)message +{} + +- (void)submanagerFilesOnEtaUpdate:(CFTimeInterval)eta + bytesPerSecond:(OCTToxFileSize)bytesPerSecond + message:(nonnull OCTMessageAbstract *)message +{ + NSUInteger index = [self.fileMessages indexOfObject:message]; + + if (index == NSNotFound) { + [self.manager.files removeProgressSubscriber:self forFileTransfer:message error:nil]; + return; + } + + NSTextField *textField = [self.tableView viewAtColumn:1 row:index makeIfNecessary:NO]; + + if (! textField) { + return; + } + + textField.stringValue = [NSString stringWithFormat: + @"bytesPerSecond = %lld\n" + @"eta = %g", + bytesPerSecond, + eta]; +} + +#pragma mark - Private + +- (NSString *)stringFromFileType:(OCTMessageFileType)type +{ + switch (type) { + case OCTMessageFileTypeWaitingConfirmation: + return @"Waiting confirmaion"; + case OCTMessageFileTypeLoading: + return @"Loading"; + case OCTMessageFileTypePaused: + return @"Paused"; + case OCTMessageFileTypeCanceled: + return @"Canceled"; + case OCTMessageFileTypeReady: + return @"Ready"; + } +} + +- (NSString *)stringFromPausedBy:(OCTMessageFilePausedBy)pausedBy +{ + if (pausedBy == OCTMessageFilePausedByNone) { + return @"None"; + } + + NSString *string = @""; + + if (pausedBy & OCTMessageFilePausedByUser) { + string = [string stringByAppendingString:@" user"]; + } + + if (pausedBy & OCTMessageFilePausedByFriend) { + string = [string stringByAppendingString:@" friend"]; + } + + return string; +} + +- (OCTMessageAbstract *)messageForCurrentlySelectedRow +{ + NSInteger selectedRow = self.tableView.selectedRow; + + if (selectedRow < 0) { + return nil; + } + + OCTMessageAbstract *message = self.fileMessages[selectedRow]; + + return message; +} + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTFilesViewController.xib b/local_pod_repo/objcTox/OSXDemo/OCTFilesViewController.xib new file mode 100644 index 0000000..6f522a5 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTFilesViewController.xib @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_pod_repo/objcTox/OSXDemo/OCTFriendsViewController.h b/local_pod_repo/objcTox/OSXDemo/OCTFriendsViewController.h new file mode 100644 index 0000000..4fb8b3e --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTFriendsViewController.h @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTManager.h" + +@interface OCTFriendsViewController : NSViewController + +- (instancetype)initWithManager:(id)manager; + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTFriendsViewController.m b/local_pod_repo/objcTox/OSXDemo/OCTFriendsViewController.m new file mode 100644 index 0000000..1b7a16d --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTFriendsViewController.m @@ -0,0 +1,255 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTFriendsViewController.h" +#import "OCTFriend.h" +#import "OCTFriendRequest.h" +#import "OCTSubmanagerObjects.h" +#import "OCTSubmanagerFriends.h" +#import "OCTSubmanagerChats.h" +#import "RLMCollectionChange+IndexSet.h" + +static NSString *const kNibName = @"OCTFriendsViewController"; +static NSString *const kCellIdent = @"cellIdent"; + +@interface OCTFriendsViewController () + +@property (weak, nonatomic) id manager; + +@property (strong, nonatomic) RLMResults *friends; +@property (strong, nonatomic) RLMNotificationToken *friendsNotificationToken; +@property (strong, nonatomic) RLMResults *friendRequests; +@property (strong, nonatomic) RLMNotificationToken *friendRequestsNotificationToken; + +@property (weak) IBOutlet NSButton *acceptButton; +@property (weak) IBOutlet NSButton *rejectButton; +@property (unsafe_unretained) IBOutlet NSTextView *friendInfoTextField; + +@property (weak) IBOutlet NSTableView *friendsTableView; +@property (weak) IBOutlet NSTableView *requestsTableView; +@property (weak) IBOutlet NSImageView *avatarImageView; + +@end + +@implementation OCTFriendsViewController + +- (instancetype)initWithManager:(id)manager +{ + self = [super initWithNibName:kNibName bundle:nil]; + + if (! self) { + return nil; + } + + _manager = manager; + + _friends = [self.manager.objects objectsForType:OCTFetchRequestTypeFriend predicate:nil]; + _friendRequests = [self.manager.objects objectsForType:OCTFetchRequestTypeFriendRequest predicate:nil]; + + return self; +} + +- (void)dealloc +{ + [self.friendsNotificationToken invalidate]; + [self.friendRequestsNotificationToken invalidate]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + __weak typeof(self)weakSelf = self; + + self.friendsNotificationToken = [self.friends addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *error) { + [weakSelf realmWasUpdated:changes tableView:weakSelf.friendsTableView error:error]; + }]; + + self.friendRequestsNotificationToken = [self.friendRequests addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *error) { + [weakSelf realmWasUpdated:changes tableView:weakSelf.requestsTableView error:error]; + }]; +} + +#pragma mark - NSTableViewDataSource + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + + if (tableView == self.friendsTableView) { + return self.friends.count; + } + else if (tableView == self.requestsTableView) { + return self.friendRequests.count; + } + + return 0; +} + +#pragma mark - NSTableViewDelegate + +- (NSView *) tableView:(NSTableView *)tableView + viewForTableColumn:(NSTableColumn *)tableColumn + row:(NSInteger)row +{ + NSTextField *field = [tableView + makeViewWithIdentifier:kCellIdent + owner:self]; + + if (! field) { + field = [NSTextField new]; + field.identifier = kCellIdent; + } + + field.selectable = YES; + field.editable = NO; + + if (tableView == self.friendsTableView) { + OCTFriend *friend = self.friends[row]; + field.stringValue = (friend.isConnected) ? ([NSString stringWithFormat:@"%@ : Online", friend.nickname]) : friend.nickname; + + } + else if (tableView == self.requestsTableView) { + OCTFriendRequest *request = self.friendRequests[row]; + field.stringValue = request.publicKey; + } + + return field; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + if (notification.object == self.friendsTableView) { + OCTFriend *friend = self.friends[self.friendsTableView.selectedRow]; + + self.avatarImageView.image = [[NSImage alloc] initWithData:friend.avatarData]; + self.friendInfoTextField.string = [NSString stringWithFormat: + @"friendNumber %u\n" + @"publicKey %@\n" + @"name %@\n" + @"nickname %@\n" + @"statusMessage %@\n" + @"status %@\n" + @"isConnected %d\n" + @"connectionStatus %@\n" + @"lastSeenOnline %@\n" + @"isTyping %d", + friend.friendNumber, + friend.publicKey, + friend.name, + friend.nickname, + friend.statusMessage, + [self stringFromUserStatus:friend.status], + friend.isConnected, + [self stringFromConnectionStatus:friend.connectionStatus], + friend.lastSeenOnline, + friend.isTyping]; + + } +} + +#pragma mark - Actions + +- (IBAction)addFriendReturn:(NSTextField *)sender +{ + [self.manager.friends + sendFriendRequestToAddress:sender.stringValue + message:@"Friend request from objcTox Mac OSX Demo" + error:nil]; +} + +- (IBAction)removeFriendButtonPressed:(NSButton *)sender +{ + NSInteger selectedRow = self.friendsTableView.selectedRow; + + if (selectedRow < 0) { + return; + } + + OCTFriend *friend = self.friends[selectedRow]; + + [self.manager.friends removeFriend:friend error:nil]; +} + +- (IBAction)createChatButtonPressed:(NSButton *)sender +{ + NSInteger selectedRow = self.friendsTableView.selectedRow; + + if (selectedRow < 0) { + return; + } + + OCTFriend *friend = self.friends[selectedRow]; + + [self.manager.chats getOrCreateChatWithFriend:friend]; +} + +- (IBAction)proceedWithFriendRequest:(NSButton *)sender +{ + NSInteger selectedRow = self.requestsTableView.selectedRow; + + if (selectedRow < 0) { + return; + } + + OCTFriendRequest *request = self.friendRequests[selectedRow]; + + if (sender == self.acceptButton) { + [self.manager.friends approveFriendRequest:request error:nil]; + } + else { + [self.manager.friends removeFriendRequest:request]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.requestsTableView reloadData]; + }); +} + +#pragma mark - Private + +- (NSString *)stringFromUserStatus:(OCTToxUserStatus)status +{ + switch (status) { + case OCTToxUserStatusNone: + return @"None"; + case OCTToxUserStatusAway: + return @"Away"; + case OCTToxUserStatusBusy: + return @"Busy"; + } +} + +- (NSString *)stringFromConnectionStatus:(OCTToxConnectionStatus)status +{ + switch (status) { + case OCTToxConnectionStatusNone: + return @"None"; + case OCTToxConnectionStatusTCP: + return @"TCP"; + case OCTToxConnectionStatusUDP: + return @"UDP"; + } +} + +- (void)realmWasUpdated:(RLMCollectionChange *)changes tableView:(NSTableView *)tableView error:(NSError *)error +{ + if (error) { + NSLog(@"Failed to open Realm on background worker: %@", error); + return; + } + + // Initial run of the query will pass nil for the change information + if (! changes) { + [tableView reloadData]; + return; + } + + [tableView beginUpdates]; + [tableView removeRowsAtIndexes:[changes deletionsSet] withAnimation:NSTableViewAnimationSlideLeft]; + [tableView insertRowsAtIndexes:[changes insertionsSet] withAnimation:NSTableViewAnimationSlideRight]; + [tableView reloadDataForRowIndexes:[changes modificationsSet] columnIndexes:[NSIndexSet indexSetWithIndex:0]]; + [tableView endUpdates]; +} + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTFriendsViewController.xib b/local_pod_repo/objcTox/OSXDemo/OCTFriendsViewController.xib new file mode 100644 index 0000000..d7bd9fc --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTFriendsViewController.xib @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_pod_repo/objcTox/OSXDemo/OCTMainWindowController.h b/local_pod_repo/objcTox/OSXDemo/OCTMainWindowController.h new file mode 100644 index 0000000..6d40ee1 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTMainWindowController.h @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@interface OCTMainWindowController : NSWindowController + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTMainWindowController.m b/local_pod_repo/objcTox/OSXDemo/OCTMainWindowController.m new file mode 100644 index 0000000..40b90d8 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTMainWindowController.m @@ -0,0 +1,100 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTMainWindowController.h" +#import "OCTBootStrapViewController.h" +#import "OCTManagerConfiguration.h" +#import "OCTManager.h" +#import "OCTManagerFactory.h" +#import "OCTSubmanagerBootstrap.h" +#import "OCTUserViewController.h" +#import "OCTFriendsViewController.h" +#import "OCTConversationViewController.h" +#import "OCTCallsViewController.h" +#import "OCTFilesViewController.h" + +@interface OCTMainWindowController () + +@property (weak) IBOutlet NSView *mainView; +@property (strong, nonatomic) NSViewController *currentViewController; +@property (strong, nonatomic) NSViewController *userViewController; +@property (strong, nonatomic) NSViewController *friendUserViewController; +@property (strong, nonatomic) NSViewController *conversationViewController; +@property (strong, nonatomic) NSViewController *callsViewController; +@property (strong, nonatomic) NSViewController *filesViewController; + +@property (strong, nonatomic) OCTManagerConfiguration *configuration; +@property (strong, nonatomic) id manager; +@property (weak) IBOutlet NSTabView *tabView; + +@end + +@implementation OCTMainWindowController + +- (void)windowDidLoad +{ + [super windowDidLoad]; + + self.configuration = [OCTManagerConfiguration defaultConfiguration]; + self.tabView.hidden = YES; + + [self setupBootstrapViewController]; +} + +- (void)setupBootstrapViewController +{ + OCTBootStrapViewController *bootstrapVC = [[OCTBootStrapViewController alloc] + initWithConfiguration:self.configuration]; + bootstrapVC.delegate = self; + + self.currentViewController = bootstrapVC; + [self.mainView addSubview:self.currentViewController.view]; + [self.currentViewController.view setFrame:self.mainView.bounds]; +} + +#pragma mark - Bootstrap delegate + +- (void)didBootStrap:(OCTBootStrapViewController *)controller +{ + [self.currentViewController.view removeFromSuperview]; + self.currentViewController = nil; + + __weak OCTMainWindowController *weakSelf = self; + [OCTManagerFactory managerWithConfiguration:self.configuration encryptPassword:@"123" successBlock:^(id < OCTManager > manager) { + weakSelf.manager = manager; + [weakSelf.manager.bootstrap addPredefinedNodes]; + [weakSelf.manager.bootstrap bootstrap]; + + weakSelf.tabView.hidden = NO; + + [weakSelf setupTabControllers]; + } failureBlock:nil]; +} + +#pragma mark - Private + +- (void)setupTabControllers +{ + NSTabViewItem *userViewItem = [self.tabView tabViewItemAtIndex:0]; + self.userViewController = [[OCTUserViewController alloc] initWithManager:self.manager.user]; + userViewItem.view = self.userViewController.view; + + NSTabViewItem *friendViewItem = [self.tabView tabViewItemAtIndex:1]; + self.friendUserViewController = [[OCTFriendsViewController alloc] initWithManager:self.manager]; + friendViewItem.view = self.friendUserViewController.view; + + NSTabViewItem *conversationViewItem = [self.tabView tabViewItemAtIndex:2]; + self.conversationViewController = [[OCTConversationViewController alloc] initWithManager:self.manager]; + conversationViewItem.view = self.conversationViewController.view; + + NSTabViewItem *callViewItem = [self.tabView tabViewItemAtIndex:3]; + self.callsViewController = [[OCTCallsViewController alloc] initWithManager:self.manager]; + callViewItem.view = self.callsViewController.view; + + NSTabViewItem *fileViewItem = [self.tabView tabViewItemAtIndex:4]; + self.filesViewController = [[OCTFilesViewController alloc] initWithManager:self.manager]; + fileViewItem.view = self.filesViewController.view; +} + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTUserViewController.h b/local_pod_repo/objcTox/OSXDemo/OCTUserViewController.h new file mode 100644 index 0000000..d5174bd --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTUserViewController.h @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTSubmanagerUser.h" + +@interface OCTUserViewController : NSViewController + +- (instancetype)initWithManager:(id)userManager; + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTUserViewController.m b/local_pod_repo/objcTox/OSXDemo/OCTUserViewController.m new file mode 100644 index 0000000..aaab92f --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTUserViewController.m @@ -0,0 +1,268 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTUserViewController.h" +#import "OCTSubmanagerUser.h" + +typedef NS_ENUM(NSUInteger, Row) { + RowConnectionStatus, + RowAddress, + RowPublicKey, + RowNospam, + RowStatus, + RowName, + RowStatusMessage, +}; + +static NSString *const kNibName = @"OCTUserViewController"; +static NSString *const kTableViewIdentifier = @"userTableViewIdent"; + +@interface OCTUserViewController () + +@property (weak, nonatomic) id userManager; +@property (weak) IBOutlet NSTableView *userTableView; +@property (strong, nonatomic) NSArray *userData; +@property (weak) IBOutlet NSTableColumn *firstColumn; +@property (weak) IBOutlet NSTableColumn *secondColumn; + +@property (weak) IBOutlet NSImageView *avatarImageView; + +@end + +@implementation OCTUserViewController + +- (instancetype)initWithManager:(id)userManager +{ + self = [super initWithNibName:kNibName bundle:nil]; + + if (! self) { + return nil; + } + + _userData = @[ + @(RowConnectionStatus), + @(RowAddress), + @(RowPublicKey), + @(RowNospam), + @(RowStatus), + @(RowName), + @(RowStatusMessage), + ]; + + _userManager = userManager; + userManager.delegate = self; + + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + [self updateAvatarImageView]; +} + +#pragma mark - NSTableViewDataSource + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return self.userData.count; +} + +#pragma mark - NSTableViewDelegate + +- (NSView *) tableView:(NSTableView *)tableView + viewForTableColumn:(NSTableColumn *)tableColumn + row:(NSInteger)row +{ + NSTextField *textField = [tableView makeViewWithIdentifier:kTableViewIdentifier owner:self]; + + if (! textField) { + textField = [NSTextField new]; + textField.identifier = kTableViewIdentifier; + } + + if (tableColumn == self.firstColumn) { + textField.stringValue = [self nameLabelForRow:row]; + textField.editable = NO; + } + + if (tableColumn == self.secondColumn) { + + if (row == RowStatus) { + NSPopUpButton *popUpButton = [NSPopUpButton new]; + popUpButton.tag = row; + [popUpButton addItemsWithTitles:@[@"None", @"Away", @"Busy"]]; + [popUpButton selectItemAtIndex:self.userManager.userStatus]; + popUpButton.action = @selector(selectionMadeForUserStatus:); + popUpButton.target = self; + + return popUpButton; + } + textField.stringValue = [self descriptionLabelForRow:row]; + textField.delegate = self; + textField.selectable = (row < 3); + textField.editable = (row >= 3); + } + + return textField; +} + +#pragma mark - NSButtonTargetAction + +- (void)selectionMadeForUserStatus:(NSPopUpButton *)sender +{ + self.userManager.userStatus = sender.indexOfSelectedItem; +} + +- (IBAction)avatarButtonPressed:(id)sender +{ + NSOpenPanel *panel = [NSOpenPanel openPanel]; + + [panel runModal]; + + NSString *path = [panel.URL path]; + NSData *data = [NSData dataWithContentsOfFile:path]; + + [self.userManager setUserAvatar:data error:nil]; + [self updateAvatarImageView]; +} + +- (IBAction)removeAvatarButtonPressed:(id)sender +{ + [self.userManager setUserAvatar:nil error:nil]; + [self updateAvatarImageView]; +} + +#pragma mark - NSTextFieldDelegate + +- (void)controlTextDidEndEditing:(NSNotification *)obj +{ + NSTextField *field = obj.object; + + NSInteger row = [self.userTableView rowForView:field]; + NSString *string = field.stringValue; + switch (row) { + case RowNospam: + self.userManager.nospam = (OCTToxNoSpam)[string integerValue]; + break; + case RowName: + [self.userManager setUserName:string error:nil]; + break; + case RowStatusMessage: + [self.userManager setUserStatusMessage:string error:nil]; + break; + default: + break; + } +} + +#pragma mark - OCTSubmanagerUserDelegate + +- (void)submanagerUser:(nonnull id)submanager connectionStatusUpdate:(OCTToxConnectionStatus)connectionStatus +{ + [self.userTableView reloadData]; +} + +#pragma mark - Private + +- (NSString *)nameLabelForRow:(NSInteger)row +{ + NSString *label; + switch (row) { + case RowConnectionStatus: + label = @"connectionStatus"; + break; + case RowAddress: + label = @"userAddress"; + break; + case RowPublicKey: + label = @"publicKey"; + break; + case RowNospam: + label = @"nospam"; + break; + case RowStatus: + label = @"user status"; + break; + case RowName: + label = @"userName"; + break; + case RowStatusMessage: + label = @"userStatusMessage"; + break; + default: + break; + } + + return label; +} + +- (NSString *)descriptionLabelForRow:(NSInteger)row +{ + NSString *label; + switch (row) { + case RowConnectionStatus: + label = [self stringFromConnectionStatus:self.userManager.connectionStatus]; + break; + case RowAddress: + label = self.userManager.userAddress; + break; + case RowPublicKey: + label = self.userManager.publicKey; + break; + case RowNospam: + label = [NSString stringWithFormat:@"%u", self.userManager.nospam]; + break; + case RowStatus: + label = [self stringFromUserStatus:self.userManager.userStatus]; + break; + case RowName: + label = (self.userManager.userName) ? self.userManager.userName : @""; + break; + case RowStatusMessage: + label = (self.userManager.userStatusMessage) ? self.userManager + .userStatusMessage : @""; + break; + default: + break; + } + + return label; +} + + +- (NSString *)stringFromConnectionStatus:(OCTToxConnectionStatus)status +{ + switch (status) { + case OCTToxConnectionStatusNone: + return @"None"; + case OCTToxConnectionStatusTCP: + return @"TCP"; + case OCTToxConnectionStatusUDP: + return @"UDP"; + } +} + +- (NSString *)stringFromUserStatus:(OCTToxUserStatus)status +{ + switch (status) { + case OCTToxUserStatusNone: + return @"None"; + case OCTToxUserStatusAway: + return @"Away"; + case OCTToxUserStatusBusy: + return @"Busy"; + } +} + +- (void)updateAvatarImageView +{ + self.avatarImageView.image = [[NSImage alloc] initWithData:self.userManager.userAvatar]; +} + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/OCTUserViewController.xib b/local_pod_repo/objcTox/OSXDemo/OCTUserViewController.xib new file mode 100644 index 0000000..1d8cda3 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/OCTUserViewController.xib @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_pod_repo/objcTox/OSXDemo/RLMCollectionChange+IndexSet.h b/local_pod_repo/objcTox/OSXDemo/RLMCollectionChange+IndexSet.h new file mode 100644 index 0000000..17ec733 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/RLMCollectionChange+IndexSet.h @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import + +@interface RLMCollectionChange (IndexSet) + +- (NSIndexSet *)deletionsSet; +- (NSIndexSet *)insertionsSet; +- (NSIndexSet *)modificationsSet; + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/RLMCollectionChange+IndexSet.m b/local_pod_repo/objcTox/OSXDemo/RLMCollectionChange+IndexSet.m new file mode 100644 index 0000000..c1ac871 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/RLMCollectionChange+IndexSet.m @@ -0,0 +1,39 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "RLMCollectionChange+IndexSet.h" + +@implementation RLMCollectionChange (IndexSet) + +#pragma mark - Public + +- (NSIndexSet *)deletionsSet +{ + return [self indexSetFromNumbersArray:[self deletions]]; +} + +- (NSIndexSet *)insertionsSet +{ + return [self indexSetFromNumbersArray:[self insertions]]; +} + +- (NSIndexSet *)modificationsSet +{ + return [self indexSetFromNumbersArray:[self modifications]]; +} + +#pragma mark - Private + +- (NSIndexSet *)indexSetFromNumbersArray:(NSArray *)numbersArray +{ + NSMutableIndexSet *set = [NSMutableIndexSet new]; + + for (NSNumber *number in numbersArray) { + [set addIndex:number.integerValue]; + } + + return [set copy]; +} + +@end diff --git a/local_pod_repo/objcTox/OSXDemo/main.m b/local_pod_repo/objcTox/OSXDemo/main.m new file mode 100644 index 0000000..fb9f63e --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemo/main.m @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +int main(int argc, const char *argv[]) +{ + return NSApplicationMain(argc, argv); +} diff --git a/local_pod_repo/objcTox/OSXDemoTests/Info.plist b/local_pod_repo/objcTox/OSXDemoTests/Info.plist new file mode 100644 index 0000000..74d0101 --- /dev/null +++ b/local_pod_repo/objcTox/OSXDemoTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + me.dvor.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/local_pod_repo/objcTox/Podfile b/local_pod_repo/objcTox/Podfile new file mode 100644 index 0000000..f8df356 --- /dev/null +++ b/local_pod_repo/objcTox/Podfile @@ -0,0 +1,45 @@ +source 'https://github.com/CocoaPods/Specs.git' + +# ignore all warnings from all pods +inhibit_all_warnings! + +def common_pods + pod "toxcore", :git => 'https://github.com/Zoxcore/toxcore.git', :commit => '5cf8c66a8fc3f2ab0708a15e3d77ba9e77f48a81' + pod 'CocoaLumberjack', '~> 1.9.2' + pod 'Realm', '10.36.0' + pod 'TPCircularBuffer', '~> 0.0.1' +end + +def demo_pods + pod 'Masonry', '~> 0.6.1' + pod 'BlocksKit', '~> 2.2.5' +end + +def test_pods + pod 'OCMock', '3.3.1' +end + + +target :iOSDemo do + platform :ios, '8.0' + common_pods + demo_pods +end + +target :iOSDemoTests do + platform :ios, '8.0' + common_pods + test_pods +end + +target :OSXDemo do + platform :osx, '10.9' + common_pods + demo_pods +end + +target :OSXDemoTests do + platform :osx, '10.9' + common_pods + test_pods +end diff --git a/local_pod_repo/objcTox/README.md b/local_pod_repo/objcTox/README.md new file mode 100644 index 0000000..e93b9f3 --- /dev/null +++ b/local_pod_repo/objcTox/README.md @@ -0,0 +1,12 @@ +# objcTox + +Objective-C wrapper for [toxcore](https://github.com/Zoxcore/Antidote/blob/develop/local_pod_repo/toxcore). + +## Authors + +Dmytro Vorobiov, d@dvor.me
+https://github.com/Zoxcore + +## License + +objcTox is available under Mozilla Public License Version 2.0. See the [LICENSE](https://github.com/Zoxcore/Antidote/blob/develop/LICENSE) file for more info. diff --git a/local_pod_repo/objcTox/Tests/CoreAudioMocks.h b/local_pod_repo/objcTox/Tests/CoreAudioMocks.h new file mode 100644 index 0000000..072d49c --- /dev/null +++ b/local_pod_repo/objcTox/Tests/CoreAudioMocks.h @@ -0,0 +1,25 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#define PATCH_FAILING(funcname) funcname = (void *)FAILING ## funcname +#define PATCH_PASSING(funcname) funcname = (void *)PASSING ## funcname + +// Restores all the CoreAudio function pointers to passing. +#define RESTORE_PATCHES \ + PATCH_PASSING(_AudioQueueAllocateBuffer); \ + PATCH_PASSING(_AudioQueueDispose); \ + PATCH_PASSING(_AudioQueueEnqueueBuffer); \ + PATCH_PASSING(_AudioQueueFreeBuffer); \ + PATCH_PASSING(_AudioQueueNewInput); \ + PATCH_PASSING(_AudioQueueNewOutput); \ + PATCH_PASSING(_AudioQueueSetProperty); \ + PATCH_PASSING(_AudioQueueStart); \ + PATCH_PASSING(_AudioQueueStop); + +// Implements a function named PASSING that takes any number of args +// and just returns 0. Handy when there's no need for special functionality. +// They are not exactly valid C but it works for now (OS X 10.11.1, 64 bit) +#define DECLARE_GENERIC_PASS(funcname) OSStatus PASSING ## funcname(void *a, ...) { return 0; } +// Same, but return a failing error code. +#define DECLARE_GENERIC_FAIL(funcname) OSStatus FAILING ## funcname(void *a, ...) { return -1; } diff --git a/local_pod_repo/objcTox/Tests/OCTAudioEngineTests.m b/local_pod_repo/objcTox/Tests/OCTAudioEngineTests.m new file mode 100644 index 0000000..cd1b347 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTAudioEngineTests.m @@ -0,0 +1,231 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import +#import "OCTCAsserts.h" +#import "OCTAudioEngine+Private.h" +#import "OCTAudioQueue.h" +#import "OCTManagerConstants.h" + +@import AVFoundation; + +static void *refToSelf; + +@interface OCTAudioEngineTests : XCTestCase + +@property (strong, nonatomic) OCTAudioQueue *outputMock; +@property (strong, nonatomic) OCTAudioQueue *inputMock; +@property (strong, nonatomic) id audioSession; +@property (strong, nonatomic) OCTAudioEngine *realAudioEngine; +@property (strong, nonatomic) OCTAudioEngine *audioEngine; + +@property (strong, nonatomic) void (^sendDataBlock)(void *, OCTToxAVSampleCount, OCTToxAVSampleRate, OCTToxAVChannels); + +@end + +@implementation OCTAudioEngineTests + +- (void)setUp +{ + [super setUp]; + + refToSelf = (__bridge void *)(self); + +#if TARGET_OS_IPHONE + self.audioSession = OCMClassMock([AVAudioSession class]); + OCMStub([self.audioSession sharedInstance]).andReturn(self.audioSession); + OCMStub([self.audioSession sampleRate]).andReturn(44100.00); + [OCMStub([self.audioSession setPreferredSampleRate:0 error:[OCMArg anyObjectRef]]).andReturn(YES) ignoringNonObjectArgs]; + OCMStub([self.audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:[OCMArg anyObjectRef]]).andReturn(YES); + [OCMStub([self.audioSession setPreferredIOBufferDuration:0 error:[OCMArg anyObjectRef]]).andReturn(YES) ignoringNonObjectArgs]; + [OCMStub([self.audioSession overrideOutputAudioPort:0 error:[OCMArg anyObjectRef]]).andReturn(YES) ignoringNonObjectArgs]; + OCMStub(([self.audioSession setMode:AVAudioSessionModeVoiceChat error:[OCMArg anyObjectRef]])).andReturn(YES); + OCMStub([self.audioSession setActive:YES error:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.audioSession setActive:NO error:[OCMArg anyObjectRef]]).andReturn(YES); + // Put setup code here. This method is called before the invocation of each test method in the class. +#endif + + self.realAudioEngine = [[OCTAudioEngine alloc] init]; + self.audioEngine = OCMPartialMock(self.realAudioEngine); + + self.inputMock = OCMClassMock([OCTAudioQueue class]); + self.outputMock = OCMClassMock([OCTAudioQueue class]); +} + +- (void)tearDown +{ + refToSelf = NULL; + + self.audioSession = nil; + self.audioEngine = nil; + self.outputMock = nil; + self.inputMock = nil; + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)enableMockQueues +{ + OCMStub([(id)self.audioEngine makeQueues:[OCMArg anyObjectRef]]).andDo(^(NSInvocation *invocation) { + id ae; + [invocation getArgument:&ae atIndex:0]; + + [ae setOutputQueue:self.outputMock]; + [ae setInputQueue:self.inputMock]; + }); +} + +- (void)testStartStopAudioFlow +{ + [self enableMockQueues]; + + id toxav = OCMClassMock([OCTToxAV class]); + OCMStub([toxav sendAudioFrame:[OCMArg anyPointer] sampleCount:0 channels:0 sampleRate:0 toFriend:0 error:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.audioEngine toxav]).andReturn(toxav); + + OCMStub([self.inputMock begin:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.outputMock begin:[OCMArg anyObjectRef]]).andReturn(YES); + + OCMStub([self.inputMock stop:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.outputMock stop:[OCMArg anyObjectRef]]).andReturn(YES); + + NSError *error = nil; + XCTAssertTrue([self.audioEngine startAudioFlow:&error]); + XCTAssertTrue([self.audioEngine stopAudioFlow:&error]); +} + +- (void)testStartStopAudioFlowFail +{ + [self enableMockQueues]; + + OCMStub([self.inputMock begin:[OCMArg anyObjectRef]]).andDo(^(NSInvocation *invocation) { + NSError *__autoreleasing *err; + [invocation getArgument:&err atIndex:2]; + + if (err) { + *err = [NSError new]; + } + + BOOL no = NO; + [invocation setReturnValue:&no]; + }); + + OCMStub([self.outputMock begin:[OCMArg anyObjectRef]]).andDo(^(NSInvocation *invocation) { + NSError *__autoreleasing *err; + [invocation getArgument:&err atIndex:2]; + + if (err) { + *err = [NSError new]; + } + + BOOL no = NO; + [invocation setReturnValue:&no]; + }); + + + NSError *error = nil; + XCTAssertFalse([self.audioEngine startAudioFlow:&error]); + XCTAssertNotNil(error); +} + +- (void)testSettingDevice +{ +#if ! TARGET_OS_IPHONE + XCTAssertTrue([self.audioEngine setOutputDeviceID:@"Sayle" error:nil]); + XCTAssertTrue([self.audioEngine setInputDeviceID:@"Laives" error:nil]); + + // Device ID should stay in sync with set. + XCTAssertEqualObjects(self.audioEngine.outputDeviceID, @"Sayle"); + XCTAssertEqualObjects(self.audioEngine.inputDeviceID, @"Laives"); +#else + XCTAssertTrue([self.audioEngine routeAudioToSpeaker:YES error:nil]); + OCMVerify([self.audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:[OCMArg anyObjectRef]]); +#endif +} + +// No test for iOS because it doesn't do anything anyway +- (void)testSettingDevicesLive +{ +#if ! TARGET_OS_IPHONE + NSError *err; + [self enableMockQueues]; + + OCMStub([self.inputMock begin:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.outputMock begin:[OCMArg anyObjectRef]]).andReturn(YES); + XCTAssertTrue([self.audioEngine startAudioFlow:&err]); + + OCMStub([self.outputMock setDeviceID:@"Crystinger" error:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.inputMock setDeviceID:@"Daxx" error:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.outputMock setDeviceID:@"Niles" error:[OCMArg anyObjectRef]]).andReturn(NO); + + XCTAssertTrue([self.audioEngine setOutputDeviceID:@"Crystinger" error:nil]); + XCTAssertTrue([self.audioEngine setInputDeviceID:@"Daxx" error:nil]); + + // Device ID should stay in sync with set. + XCTAssertEqualObjects(self.audioEngine.outputDeviceID, @"Crystinger"); + XCTAssertEqualObjects(self.audioEngine.inputDeviceID, @"Daxx"); + + // Failed sets should not update the stored id. + XCTAssertFalse([self.audioEngine setOutputDeviceID:@"Niles" error:nil]); + XCTAssertNotEqualObjects(self.audioEngine.outputDeviceID, @"Niles"); +#endif +} + +- (void)testSendDataBlock +{ + [self enableMockQueues]; + + id toxav = OCMClassMock([OCTToxAV class]); + OCMStub([self.audioEngine toxav]).andReturn(toxav); + + OCMStub([self.inputMock setSendDataBlock:[OCMArg any]]).andDo(^(NSInvocation *invoc) { + void (^sendDataBlock)(void *, OCTToxAVSampleCount, OCTToxAVSampleRate, OCTToxAVChannels); + [invoc getArgument:&sendDataBlock atIndex:2]; + self.sendDataBlock = sendDataBlock; + }); + + OCMStub([self.inputMock begin:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.outputMock begin:[OCMArg anyObjectRef]]).andReturn(YES); + + NSError *error = nil; + self.audioEngine.friendNumber = 0; + XCTAssertTrue([self.audioEngine startAudioFlow:&error]); + + OCTToxAVPCMData pcm[] = {0x52, 0x2d, 0x4f, 0x2d, 0x4d, 0x2d, 0x41, 0x2d, 0x4e, 0x2d, 0x54, 0x2d, 0x49, 0x2d, 0x43, 0x2d, 0x21, 0x21}; + self.sendDataBlock((void *)pcm, 9, 48000, 1); + + OCMVerify([toxav sendAudioFrame:pcm sampleCount:9 channels:1 sampleRate:48000 toFriend:0 error:[OCMArg anyObjectRef]]); +} + +- (void)testReceiveAudioPackets +{ + [self enableMockQueues]; + + TPCircularBuffer buffer; + TPCircularBufferInit(&buffer, 256); + + OCMStub([self.inputMock begin:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.outputMock begin:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.outputMock getBufferPointer]).andReturn(&buffer); + + NSError *error = nil; + self.audioEngine.friendNumber = 0; + XCTAssertTrue([self.audioEngine startAudioFlow:&error]); + + OCTToxAVPCMData pcm[] = {0x52, 0x2d, 0x4f, 0x2d, 0x4d, 0x2d, 0x41, 0x2d, 0x4e, 0x2d, 0x54, 0x2d, 0x49, 0x2d, 0x43, 0x2d, 0x21, 0x21}; + [self.audioEngine provideAudioFrames:pcm sampleCount:9 channels:1 sampleRate:12 fromFriend:0]; + + int32_t nbytes; + void *samples = TPCircularBufferTail(&buffer, &nbytes); + + XCTAssertTrue(nbytes == 18); + XCTAssertTrue(memcmp(samples, pcm, 18) == 0); + + TPCircularBufferCleanup(&buffer); + + OCMVerify([self.outputMock updateSampleRate:12 numberOfChannels:1 error:[OCMArg anyObjectRef]]); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTAudioQueueTests.m b/local_pod_repo/objcTox/Tests/OCTAudioQueueTests.m new file mode 100644 index 0000000..bcde888 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTAudioQueueTests.m @@ -0,0 +1,450 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import +#import "OCTCAsserts.h" +#import "OCTAudioEngine+Private.h" +#import "OCTAudioQueue.h" +#import "OCTManagerConstants.h" + +#include "CoreAudioMocks.h" + +@import AVFoundation; + +static void *refToSelf; +static AudioQueueInputCallback callForInput; +static AudioQueueOutputCallback callForOutput; + +const OCTToxAVPCMData pcm[8] = {2, 4, 6, 8, 10, 12, 14, 16}; + +OSStatus PASSING_AudioQueueNewOutput(const AudioStreamBasicDescription *inFormat, + AudioQueueOutputCallback inCallbackProc, + void *inUserData, + CFRunLoopRef inCallbackRunLoop, + CFStringRef inCallbackRunLoopMode, + UInt32 inFlags, + AudioQueueRef *outAQ) +{ + *outAQ = (void *)0x1234567; + callForOutput = inCallbackProc; + return 0; +} + + +OSStatus PASSING_AudioQueueNewInput(const AudioStreamBasicDescription *inFormat, + AudioQueueInputCallback inCallbackProc, + void *inUserData, + CFRunLoopRef inCallbackRunLoop, + CFStringRef inCallbackRunLoopMode, + UInt32 inFlags, + AudioQueueRef *outAQ) +{ + *outAQ = (void *)0x1234567; + callForInput = inCallbackProc; + return 0; +} + +typedef struct NotAudioQueueBuffer { + UInt32 mAudioDataBytesCapacity; // 4 (8) + void *mAudioData; // 8 + UInt32 mAudioDataByteSize; // 4 (8) + void *__nullable mUserData; // 8 + + const UInt32 mPacketDescriptionCapacity; // 4 (8) + AudioStreamPacketDescription *const __nullable mPacketDescriptions; // (8) + UInt32 mPacketDescriptionCount; //  4 +} NotAudioQueueBuffer; + +OSStatus PASSING_AudioQueueAllocateBuffer(AudioQueueRef inAQ, UInt32 inBufferByteSize, AudioQueueBufferRef *outBuffer) +{ + NotAudioQueueBuffer *buf = calloc(sizeof(AudioQueueBuffer) + inBufferByteSize, 1); + buf->mAudioData = ((uint8_t *)buf) + sizeof(struct AudioQueueBuffer); + buf->mAudioDataBytesCapacity = inBufferByteSize; + buf->mAudioDataByteSize = inBufferByteSize; + + *outBuffer = (AudioQueueBufferRef)buf; + return 0; +} + +OSStatus PASSING_AudioQueueFreeBuffer(AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) +{ + free(inBuffer); + return 0; +} + +#if ! TARGET_OS_IPHONE +OSStatus PASSING_AudioObjectGetPropertyData(AudioObjectID inObjectID, + const AudioObjectPropertyAddress *inAddress, + UInt32 inQualifierDataSize, + const void *inQualifierData, + UInt32 *ioDataSize, + void *outData) +{ + if (inObjectID == kAudioObjectSystemObject) { + CCCAssertTrue(outData != NULL); + CCCAssertTrue(*ioDataSize == sizeof(AudioDeviceID)); + *(AudioDeviceID *)outData = 0x1234567; + return 0; + } + + if (inAddress->mSelector == kAudioDevicePropertyDeviceUID) { + CFStringRef ret = CFSTR("PXS-04G"); + *(CFStringRef *)outData = CFStringCreateCopy(nil, ret); + return 0; + } + + return -1; +} + +OSStatus FAILING_AudioObjectGetPropertyData(AudioObjectID inObjectID, + const AudioObjectPropertyAddress *inAddress, + UInt32 inQualifierDataSize, + const void *inQualifierData, + UInt32 *ioDataSize, + void *outData) +{ + if (inObjectID == kAudioObjectSystemObject) { + return -1; + } + return -1; +} + +OSStatus FAILING_AudioObjectGetPropertyData2(AudioObjectID inObjectID, + const AudioObjectPropertyAddress *inAddress, + UInt32 inQualifierDataSize, + const void *inQualifierData, + UInt32 *ioDataSize, + void *outData) +{ + if (inObjectID == kAudioObjectSystemObject) { + *(AudioDeviceID *)outData = 0x1234567; + return 0; + } + if (inAddress->mSelector == kAudioDevicePropertyDeviceUID) { + return -1; + } + return -1; +} +#endif + +OSStatus FAILING_AudioQueueSetProperty(AudioQueueRef inAQ, + AudioQueuePropertyID inID, + const void *inData, + UInt32 inDataSize) +{ + if (inID == kAudioQueueProperty_CurrentDevice) { + CCCAssertNotEqual(inData, nil); + CCCAssertEqual(inDataSize, sizeof(CFStringRef *)); + + return -1; + } + return 0; +} + +OSStatus PASSING_AudioQueueStart(AudioQueueRef inAQ, + const AudioTimeStamp *inStartTime) +{ + CCCAssert(inAQ != NULL); + return 0; +} + + +DECLARE_GENERIC_PASS(_AudioQueueDispose) +DECLARE_GENERIC_PASS(_AudioQueueEnqueueBuffer) +DECLARE_GENERIC_PASS(_AudioQueueSetProperty) +DECLARE_GENERIC_PASS(_AudioQueueStop) + +DECLARE_GENERIC_FAIL(_AudioQueueDispose) +DECLARE_GENERIC_FAIL(_AudioQueueEnqueueBuffer) +DECLARE_GENERIC_FAIL(_AudioQueueNewInput) +DECLARE_GENERIC_FAIL(_AudioQueueNewOutput) +DECLARE_GENERIC_FAIL(_AudioQueueStart) +DECLARE_GENERIC_FAIL(_AudioQueueStop) + +@interface OCTAudioQueueTests : XCTestCase + +@property (strong, nonatomic) id audioSession; + +@end + +@implementation OCTAudioQueueTests + +- (void)setUp +{ + [super setUp]; + + refToSelf = (__bridge void *)(self); + +#if TARGET_OS_IPHONE + OCMStub([self.audioSession sampleRate]).andReturn(44100.00); + // Put setup code here. This method is called before the invocation of each test method in the class. +#endif + + RESTORE_PATCHES + +#if ! TARGET_OS_IPHONE + _AudioObjectGetPropertyData = PASSING_AudioObjectGetPropertyData; +#endif +} + +- (void)tearDown +{ + refToSelf = NULL; + + self.audioSession = nil; + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testInitOutput +{ + NSError *error = nil; + OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithOutputDeviceID:@"Crystinger" error:&error]; + + XCTAssertNotNil(oq); + XCTAssertNotEqual([oq getBufferPointer], NULL); +} + +- (void)testInitInput +{ + NSError *error = nil; + OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithInputDeviceID:@"Daxx" error:&error]; + + XCTAssertNotNil(oq); + XCTAssertNotEqual([oq getBufferPointer], NULL); +} + +- (void)testInitFail +{ + PATCH_FAILING(_AudioQueueNewOutput); + + NSError *error = nil; + OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithOutputDeviceID:@"Vint" error:&error]; + + XCTAssertNil(oq); + XCTAssertNotNil(error); + + + PATCH_FAILING(_AudioQueueNewInput); + + error = nil; + oq = [[OCTAudioQueue alloc] initWithInputDeviceID:@"Rita" error:&error]; + + XCTAssertNil(oq); + XCTAssertNotNil(error); +} + +- (void)testInitLater +{ + PATCH_FAILING(_AudioQueueSetProperty); + + NSError *error = nil; + OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithOutputDeviceID:@"Elgarde" error:&error]; + + XCTAssertNil(oq); + XCTAssertNotNil(error); + + error = nil; + oq = [[OCTAudioQueue alloc] initWithInputDeviceID:@"Kirin" error:&error]; + + XCTAssertNil(oq); + XCTAssertNotNil(error); +} + +// setting devices only available on OS X +- (void)testSetDevice +{ +#if ! TARGET_OS_IPHONE + NSError *error = nil; + OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithInputDeviceID:@"Undine" error:&error]; + XCTAssertNotNil(oq); + + BOOL ok = [oq setDeviceID:@"PXS-04G" error:&error]; + XCTAssertTrue(ok); + + PATCH_FAILING(_AudioQueueSetProperty); + ok = [oq setDeviceID:@"Nyx" error:&error]; + XCTAssertFalse(ok); + XCTAssertNotNil(error); +#endif +} + +- (void)testSetDefaultDevice +{ + NSError *error = nil; + OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithInputDeviceID:@"Sayle" error:&error]; + XCTAssertNotNil(oq); + +#if ! TARGET_OS_IPHONE + BOOL ok = [oq setDeviceID:nil error:&error]; + XCTAssertTrue(ok); + + _AudioObjectGetPropertyData = FAILING_AudioObjectGetPropertyData; + ok = [oq setDeviceID:nil error:&error]; + XCTAssertFalse(ok); + XCTAssertNotNil(error); + + _AudioObjectGetPropertyData = FAILING_AudioObjectGetPropertyData2; + error = nil; + ok = [oq setDeviceID:nil error:&error]; + XCTAssertFalse(ok); + XCTAssertNotNil(error); +#endif +} + +- (void)testStartStop +{ + NSError *error = nil; + OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithOutputDeviceID:@"Naivy" error:&error]; + + XCTAssertNotNil(oq); + XCTAssertNotEqual([oq getBufferPointer], NULL); + + BOOL ok = [oq begin:&error]; + XCTAssertTrue(ok); + + ok = [oq stop:&error]; + XCTAssertTrue(ok); +} + +- (void)testStartFail +{ + NSError *error = nil; + OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithOutputDeviceID:@"Vervain" error:&error]; + + XCTAssertNotNil(oq); + + PATCH_FAILING(_AudioQueueStart); + + BOOL ok = [oq begin:&error]; + XCTAssertFalse(ok); + XCTAssertNotNil(error); + + ok = [oq stop:&error]; + XCTAssertTrue(ok); +} + +- (void)testStopFail +{ + NSError *error = nil; + OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithOutputDeviceID:@"Raijin" error:&error]; + + XCTAssertNotNil(oq); + + PATCH_FAILING(_AudioQueueStop); + + BOOL ok = [oq begin:&error]; + XCTAssertTrue(ok); + + ok = [oq stop:&error]; + XCTAssertFalse(ok); + XCTAssertNotNil(error); +} + +- (void)testChangeSampleRate +{ + NSError *error = nil; + OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithOutputDeviceID:@"Minari" error:&error]; + + XCTAssertNotNil(oq); + + BOOL ok = [oq begin:&error]; + XCTAssertTrue(ok); + + ok = [oq updateSampleRate:96000.0 numberOfChannels:2.0 error:&error]; + XCTAssertTrue(ok); +} + +- (void)testChangeSampleRateFail +{ + NSError *error = nil; + OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithOutputDeviceID:@"Medis" error:&error]; + + XCTAssertNotNil(oq); + + BOOL ok = [oq begin:&error]; + XCTAssertTrue(ok); + + PATCH_FAILING(_AudioQueueNewOutput); + + ok = [oq updateSampleRate:96000.0 numberOfChannels:2.0 error:&error]; + XCTAssertFalse(ok); + XCTAssertNotNil(error); + + PATCH_PASSING(_AudioQueueNewOutput); + + ok = [oq begin:&error]; + XCTAssertTrue(ok); +} + +- (void)testFillOutput +{ + // TODO investigate failing test on travis + + // NSError *error = nil; + // OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithOutputDeviceID:@"Shadogan" error:&error]; + + // XCTAssertNotNil(oq); + + // BOOL ok = [oq begin:&error]; + // XCTAssertTrue(ok); + + // AudioQueueBufferRef buf; + + // // Allocate some extra space because the implementation is supposed to fill + // // it with 0 if there's not enough data in the ring. + + // PASSING_AudioQueueAllocateBuffer(nil, 32, &buf); + // XCTAssertNotEqual(buf, NULL); + + // TPCircularBufferProduceBytes([oq getBufferPointer], pcm, 16); + // callForOutput((__bridge void *)(oq), (void *)0x1234567, buf); + + // OCTToxAVPCMData checkPCM[16]; + // memset((void *)checkPCM, 0, 32); + // memcpy((void *)checkPCM, pcm, 16); + + // XCTAssertTrue(memcmp(buf->mAudioData, checkPCM, 32) == 0); + // PASSING_AudioQueueFreeBuffer(nil, buf); +} + +- (void)testFillInput +{ + NSError *error = nil; + OCTAudioQueue *oq = [[OCTAudioQueue alloc] initWithInputDeviceID:@"Nelly" error:&error]; + + XCTAssertNotNil(oq); + + BOOL ok = [oq begin:&error]; + XCTAssertTrue(ok); + + AudioQueueBufferRef buf; + PASSING_AudioQueueAllocateBuffer(nil, 4, &buf); + XCTAssertNotEqual(buf, NULL); + + __block BOOL sbreak = NO; + + oq.sendDataBlock = ^(void *data, OCTToxAVSampleCount samples, OCTToxAVSampleRate srate, OCTToxAVChannels nchan) { + sbreak = YES; + }; + + int times = 0; + while (! sbreak && times < 2000) { + memcpy(buf->mAudioData, pcm, 4); + callForInput((__bridge void *_Nullable)(oq), + (void *)0x1234567, + buf, + (void *)0x1, + 0, + NULL); + } + + XCTAssertTrue(times < 2000); + // we just have to make sure sendDataBlock is called + XCTAssertTrue(sbreak); + PASSING_AudioQueueFreeBuffer(nil, buf); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTCAsserts.h b/local_pod_repo/objcTox/Tests/OCTCAsserts.h new file mode 100644 index 0000000..54d4721 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTCAsserts.h @@ -0,0 +1,31 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef objcTox_OCTCAsserts_h +#define objcTox_OCTCAsserts_h + +#import + +/** + * Macros for testing to use in C functions. + * + * Define `void *refToSelf` in you test file. Set it to test object in setUp, to nil in tearDown. + */ + +#define CCCAssert(expression, ...) \ + _XCTPrimitiveAssertTrue((__bridge id)refToSelf, expression, @#expression, __VA_ARGS__) + +#define CCCAssertFalse(expression, ...) \ + _XCTPrimitiveAssertFalse((__bridge id)refToSelf, expression, @#expression, __VA_ARGS__) + +#define CCCAssertTrue(expression, ...) \ + _XCTPrimitiveAssertTrue((__bridge id)refToSelf, expression, @#expression, __VA_ARGS__) + +#define CCCAssertEqual(expression1, expression2, ...) \ + _XCTPrimitiveAssertEqual((__bridge id)refToSelf, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__) + +#define CCCAssertNotEqual(expression1, expression2, ...) \ + _XCTPrimitiveAssertNotEqual((__bridge id)refToSelf, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__) + +#endif diff --git a/local_pod_repo/objcTox/Tests/OCTCallTimerTests.m b/local_pod_repo/objcTox/Tests/OCTCallTimerTests.m new file mode 100644 index 0000000..f55db5f --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTCallTimerTests.m @@ -0,0 +1,73 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTCallTimer.h" +#import "OCTCall.h" +#import "OCTRealmTests.h" + +@interface OCTCallTimer (Tests) + +@property (strong, nonatomic) dispatch_source_t timer; +@property (weak, nonatomic) OCTRealmManager *realmManager; + +@end +@interface OCTCallTimerTests : OCTRealmTests + +@property (strong, nonatomic) OCTCallTimer *callTimer; + +@end + +@implementation OCTCallTimerTests + +- (void)setUp +{ + [super setUp]; + + self.callTimer = [[OCTCallTimer alloc] initWithRealmManager:self.realmManager]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testInit +{ + XCTAssertNil(self.callTimer.timer); + XCTAssertNotNil(self.callTimer.realmManager); +} + +- (void)testStartTimer +{ + OCTFriend *friend = [self createFriendWithFriendNumber:9]; + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + OCTChat *chat = [self.realmManager getOrCreateChatWithFriend:friend]; + OCTCall *call = [self.realmManager createCallWithChat:chat status:OCTCallStatusActive]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Test Timer"]; + + [self.callTimer startTimerForCall:call]; + + dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 1.5 * NSEC_PER_SEC); + + dispatch_after(delayTime, dispatch_get_main_queue(), ^{ + if (call.callDuration > 1) { + [expectation fulfill]; + } + }); + + [self waitForExpectationsWithTimeout:3.0 handler:^(NSError *error) { + [self.callTimer stopTimer]; + }]; +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTChatTests.m b/local_pod_repo/objcTox/Tests/OCTChatTests.m new file mode 100644 index 0000000..5098b30 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTChatTests.m @@ -0,0 +1,76 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTChat.h" +#import "OCTMessageAbstract.h" + +@interface OCTChatTests : XCTestCase + +@end + +@implementation OCTChatTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testLastReadDate +{ + OCTChat *chat = [OCTChat new]; + + chat.lastReadDateInterval = 0; + XCTAssertNil([chat lastReadDate]); + + chat.lastReadDateInterval = 12345; + XCTAssertEqual([[chat lastReadDate] timeIntervalSince1970], 12345); + + chat.lastReadDateInterval = -12345; + XCTAssertNil([chat lastReadDate]); +} + +- (void)testLastActivityDate +{ + OCTChat *chat = [OCTChat new]; + + chat.lastActivityDateInterval = 0; + XCTAssertNil([chat lastActivityDate]); + + chat.lastActivityDateInterval = 12345; + XCTAssertEqual([[chat lastActivityDate] timeIntervalSince1970], 12345); + + chat.lastActivityDateInterval = -12345; + XCTAssertNil([chat lastActivityDate]); +} + +- (void)testHasUnreadMessages +{ + OCTChat *chat = [OCTChat new]; + chat.lastMessage = [OCTMessageAbstract new]; + + XCTAssertFalse([chat hasUnreadMessages]); + + chat.lastReadDateInterval = 10; + XCTAssertFalse([chat hasUnreadMessages]); + + chat.lastMessage.dateInterval = 20; + XCTAssertTrue([chat hasUnreadMessages]); + + chat.lastReadDateInterval = 20; + XCTAssertFalse([chat hasUnreadMessages]); + + chat.lastReadDateInterval = 30; + XCTAssertFalse([chat hasUnreadMessages]); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTDefaultFileStorageTests.m b/local_pod_repo/objcTox/Tests/OCTDefaultFileStorageTests.m new file mode 100644 index 0000000..4358286 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTDefaultFileStorageTests.m @@ -0,0 +1,49 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import + +#import "OCTDefaultFileStorage.h" + +@interface OCTDefaultFileStorageTests : XCTestCase + +@end + +@implementation OCTDefaultFileStorageTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testProperties +{ + OCTDefaultFileStorage *storage = [[OCTDefaultFileStorage alloc] initWithBaseDirectory:@"/base" + temporaryDirectory:@"/temp"]; + + XCTAssertEqualObjects(storage.pathForToxSaveFile, @"/base/save.tox"); + XCTAssertEqualObjects(storage.pathForDatabase, @"/base/database"); + XCTAssertEqualObjects(storage.pathForDatabaseEncryptionKey, @"/base/database.encryptionkey"); + XCTAssertEqualObjects(storage.pathForDownloadedFilesDirectory, @"/base/files"); + XCTAssertEqualObjects(storage.pathForTemporaryFilesDirectory, @"/temp"); +} + +- (void)testCustomToxSaveFileName +{ + OCTDefaultFileStorage *storage = [[OCTDefaultFileStorage alloc] initWithToxSaveFileName:@"filename" + baseDirectory:@"/base" + temporaryDirectory:@"/temp"]; + + XCTAssertEqualObjects(storage.pathForToxSaveFile, @"/base/filename.tox"); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTFileToolsTests.m b/local_pod_repo/objcTox/Tests/OCTFileToolsTests.m new file mode 100644 index 0000000..b912846 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTFileToolsTests.m @@ -0,0 +1,127 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTFileTools.h" + +@interface OCTFileToolsTests : XCTestCase + +@end + +@implementation OCTFileToolsTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testCreateNewFilePath +{ +#define COMPARE(originalName, resultName) \ + XCTAssertEqualObjects([directory stringByAppendingPathComponent:resultName], \ + [OCTFileTools createNewFilePathInDirectory:directory fileName:originalName]); + + NSString *directory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"testCreateNewFilePath"]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + [fileManager removeItemAtPath:directory error:nil]; + [fileManager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:nil]; + + COMPARE(@"file.txt", @"file.txt") + COMPARE(@"file", @"file") + + [self createFileInDirectory: directory name: @"file.txt"]; + [self createFileInDirectory:directory name:@"file"]; + COMPARE(@"file.txt", @"file 2.txt") + COMPARE(@"file", @"file 2") + + [self createFileInDirectory: directory name: @"file 2.txt"]; + [self createFileInDirectory:directory name:@"file 2"]; + COMPARE(@"file.txt", @"file 3.txt") + COMPARE(@"file", @"file 3") + + [self createFileInDirectory: directory name: @"file 3.txt"]; + [self createFileInDirectory:directory name:@"file 3"]; + COMPARE(@"file.txt", @"file 4.txt") + COMPARE(@"file", @"file 4") + + + + COMPARE(@"other 1.txt", @"other 1.txt") + COMPARE(@"other 1", @"other 1") + + [self createFileInDirectory: directory name: @"other 1.txt"]; + [self createFileInDirectory:directory name:@"other 1"]; + COMPARE(@"other 1.txt", @"other 2.txt") + COMPARE(@"other 1", @"other 2") + + [self createFileInDirectory: directory name: @"other 9.txt"]; + [self createFileInDirectory:directory name:@"other 9"]; + COMPARE(@"other 9.txt", @"other 10.txt") + COMPARE(@"other 9", @"other 10") + + [self createFileInDirectory: directory name: @"other 10.txt"]; + [self createFileInDirectory:directory name:@"other 10"]; + COMPARE(@"other 9.txt", @"other 11.txt") + COMPARE(@"other 9", @"other 11") + + + + COMPARE(@"qq 1q.txt", @"qq 1q.txt") + COMPARE(@"qq 1q", @"qq 1q") + + [self createFileInDirectory: directory name: @"qq 1q.txt"]; + [self createFileInDirectory:directory name:@"qq 1q"]; + COMPARE(@"qq 1q.txt", @"qq 1q 2.txt") + COMPARE(@"qq 1q", @"qq 1q 2") + + + + COMPARE(@"zz 0.txt", @"zz 0.txt") + COMPARE(@"zz 0", @"zz 0") + + [self createFileInDirectory: directory name: @"zz 0.txt"]; + [self createFileInDirectory:directory name:@"zz 0"]; + COMPARE(@"zz 0.txt", @"zz 1.txt") + COMPARE(@"zz 0", @"zz 1") + + [self createFileInDirectory: directory name: @"zz 1.txt"]; + [self createFileInDirectory:directory name:@"zz 1"]; + COMPARE(@"zz 0.txt", @"zz 2.txt") + COMPARE(@"zz 0", @"zz 2") + + [self createFileInDirectory: directory name: @"zz -3.txt"]; + [self createFileInDirectory:directory name:@"zz -3"]; + COMPARE(@"zz -3.txt", @"zz -3 2.txt") + COMPARE(@"zz -3", @"zz -3 2") + + + + COMPARE(@"1.txt", @"1.txt") + COMPARE(@"1", @"1") + + [self createFileInDirectory: directory name: @"1.txt"]; + [self createFileInDirectory:directory name:@"1"]; + + COMPARE(@"1.txt", @"1 2.txt") + COMPARE(@"1", @"1 2") + + [fileManager removeItemAtPath: directory error: nil]; +} + +- (void)createFileInDirectory:(NSString *)directory name:(NSString *)name +{ + [[NSFileManager defaultManager] createFileAtPath:[directory stringByAppendingPathComponent:name] + contents:[NSData data] + attributes:nil]; +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTFriendRequestTests.m b/local_pod_repo/objcTox/Tests/OCTFriendRequestTests.m new file mode 100644 index 0000000..2485f4c --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTFriendRequestTests.m @@ -0,0 +1,37 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTFriendRequest.h" + +@interface OCTFriendRequestTests : XCTestCase + +@end + +@implementation OCTFriendRequestTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testDate +{ + OCTFriendRequest *request = [OCTFriendRequest new]; + request.dateInterval = 12345; + + NSDate *date = [request date]; + + XCTAssertEqual([date timeIntervalSince1970], 12345); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTManagerConfigurationTests.m b/local_pod_repo/objcTox/Tests/OCTManagerConfigurationTests.m new file mode 100644 index 0000000..0ce633e --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTManagerConfigurationTests.m @@ -0,0 +1,84 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import + +#import "OCTManagerConfiguration.h" + +@interface OCTManagerConfigurationTests : XCTestCase + +@end + +@implementation OCTManagerConfigurationTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testDefaultConfiguration +{ + OCTManagerConfiguration *configuration = [OCTManagerConfiguration defaultConfiguration]; + + XCTAssertNotNil(configuration.fileStorage); + XCTAssertNotNil(configuration.options); + XCTAssertTrue(configuration.useFauxOfflineMessaging); +} + +- (void)testCopy +{ + OCTManagerConfiguration *configuration = [OCTManagerConfiguration defaultConfiguration]; + configuration.options.ipv6Enabled = YES; + configuration.options.udpEnabled = YES; + configuration.options.localDiscoveryEnabled = YES; + configuration.options.proxyType = OCTToxProxyTypeHTTP; + configuration.options.proxyHost = @"proxy.address"; + configuration.options.proxyPort = 999; + configuration.options.startPort = 123; + configuration.options.endPort = 321; + configuration.options.tcpPort = 777; + configuration.options.holePunchingEnabled = YES; + configuration.importToxSaveFromPath = @"save.tox"; + configuration.useFauxOfflineMessaging = NO; + + OCTManagerConfiguration *c2 = [configuration copy]; + + configuration.options.ipv6Enabled = NO; + configuration.options.udpEnabled = NO; + configuration.options.localDiscoveryEnabled = NO; + configuration.options.proxyType = OCTToxProxyTypeSocks5; + configuration.options.proxyHost = @"another.address"; + configuration.options.proxyPort = 10; + configuration.options.startPort = 11; + configuration.options.endPort = 12; + configuration.options.tcpPort = 13; + configuration.options.holePunchingEnabled = NO; + configuration.importToxSaveFromPath = @"another.tox"; + configuration.useFauxOfflineMessaging = YES; + + XCTAssertEqualObjects(configuration.fileStorage, c2.fileStorage); + + XCTAssertTrue(c2.options.ipv6Enabled); + XCTAssertTrue(c2.options.udpEnabled); + XCTAssertTrue(c2.options.localDiscoveryEnabled); + XCTAssertEqual(c2.options.proxyType, OCTToxProxyTypeHTTP); + XCTAssertEqualObjects(c2.options.proxyHost, @"proxy.address"); + XCTAssertEqual(c2.options.proxyPort, 999); + XCTAssertEqual(c2.options.startPort, 123); + XCTAssertEqual(c2.options.endPort, 321); + XCTAssertEqual(c2.options.tcpPort, 777); + XCTAssertTrue(c2.options.holePunchingEnabled); + XCTAssertEqualObjects(c2.importToxSaveFromPath, @"save.tox"); + XCTAssertFalse(c2.useFauxOfflineMessaging); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTManagerImplTests.m b/local_pod_repo/objcTox/Tests/OCTManagerImplTests.m new file mode 100644 index 0000000..ef4dda8 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTManagerImplTests.m @@ -0,0 +1,514 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import + +#import "OCTManagerImpl.h" +#import "OCTManagerFactory.h" +#import "OCTManagerConstants.h" +#import "OCTTox.h" +#import "OCTToxAV.h" +#import "OCTToxEncryptSave.h" +#import "OCTSubmanagerDataSource.h" +#import "OCTManagerConfiguration.h" +#import "OCTSubmanagerBootstrapImpl.h" +#import "OCTSubmanagerChatsImpl.h" +#import "OCTSubmanagerFriendsImpl.h" +#import "OCTSubmanagerFilesImpl.h" +#import "OCTSubmanagerUserImpl.h" +#import "OCTSubmanagerObjectsImpl.h" +#import "OCTSubmanagerCallsImpl.h" +#import "OCTRealmManager.h" +#import "OCTDefaultFileStorage.h" +#import "OCTMessageAbstract.h" +#import "OCTMessageText.h" +#import "OCTMessageFile.h" + +static NSString *const kTestDirectory = @"me.dvor.objcToxTests"; + +@interface OCTManagerImpl (Tests) + +@property (strong, nonatomic, readonly) OCTTox *tox; +@property (strong, nonatomic, readonly) OCTToxAV *toxAV; +@property (copy, nonatomic, readwrite) OCTManagerConfiguration *configuration; + +@property (strong, nonatomic, readwrite) OCTSubmanagerBootstrapImpl *bootstrap; +@property (strong, nonatomic, readwrite) OCTSubmanagerChatsImpl *chats; +@property (strong, nonatomic, readwrite) OCTSubmanagerFilesImpl *files; +@property (strong, nonatomic, readwrite) OCTSubmanagerFriendsImpl *friends; +@property (strong, nonatomic, readwrite) OCTSubmanagerObjectsImpl *objects; +@property (strong, nonatomic, readwrite) OCTSubmanagerUserImpl *user; + +@property (strong, nonatomic) OCTRealmManager *realmManager; +@property (strong, nonatomic) NSNotificationCenter *notificationCenter; + +- (id)forwardingTargetForSelector:(SEL)aSelector; + +@end + + +@interface FakeSubmanager : NSObject +@property (weak, nonatomic) id dataSource; +@end +@implementation FakeSubmanager +- (void)tox:(OCTTox *)tox connectionStatus:(OCTToxConnectionStatus)status +{} +@end + +@interface OCTManagerImplTests : XCTestCase + +@property (strong, nonatomic) OCTManagerImpl *manager; +@property (nonatomic, assign) id mockedCallManager; +@property (strong, nonatomic) id tox; +@property (strong, nonatomic) id toxAV; + +@property (strong, nonatomic) NSString *tempDirectoryPath; + +@end + +@implementation OCTManagerImplTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. + + self.tempDirectoryPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:kTestDirectory] stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]]; + + [[NSFileManager defaultManager] createDirectoryAtPath:self.tempDirectoryPath + withIntermediateDirectories:YES + attributes:nil + error:nil]; + + self.mockedCallManager = OCMClassMock([OCTSubmanagerCallsImpl class]); + + self.tox = OCMClassMock([OCTTox class]); + OCMStub([self.tox alloc]).andReturn(self.tox); + OCMStub([self.tox initWithOptions:[OCMArg any] savedData:[OCMArg any] error:[OCMArg anyObjectRef]]).andReturn(self.tox); + OCMStub([self.tox initWithOptions:[OCMArg any] savedData:[OCMArg any] error:[OCMArg anyObjectRef]]).andReturn(self.tox); + OCMStub([self.tox save]).andReturn([@"save file" dataUsingEncoding:NSUTF8StringEncoding]); + + self.toxAV = OCMClassMock([OCTToxAV class]); + OCMStub([self.toxAV alloc]).andReturn(self.toxAV); + OCMStub([self.toxAV initWithTox:[OCMArg any] error:[OCMArg anyObjectRef]]).andReturn(self.toxAV); + + id data = OCMClassMock([NSData class]); + + OCMStub([data writeToFile:[OCMArg any] options:NSDataWritingAtomic error:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.tox save]).andReturn(data); +} + +- (void)tearDown +{ + [self.tox stopMocking]; + self.tox = nil; + + [self.toxAV stopMocking]; + self.toxAV = nil; + + self.manager = nil; + [self.mockedCallManager stopMocking]; + self.mockedCallManager = nil; + + [[NSFileManager defaultManager] removeItemAtPath:self.tempDirectoryPath error:nil]; + + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testInit +{ + [self createManager]; + + XCTAssertNotNil(self.manager); + + XCTAssertNotNil(self.manager.bootstrap); + XCTAssertEqual(self.manager.bootstrap.dataSource, self.manager); + XCTAssertNotNil(self.manager.chats); + XCTAssertEqual(self.manager.chats.dataSource, self.manager); + XCTAssertNotNil(self.manager.files); + XCTAssertEqual(self.manager.files.dataSource, self.manager); + XCTAssertNotNil(self.manager.friends); + XCTAssertEqual(self.manager.friends.dataSource, self.manager); + XCTAssertNotNil(self.manager.objects); + XCTAssertEqual(self.manager.objects.dataSource, self.manager); + XCTAssertNotNil(self.manager.user); + XCTAssertEqual(self.manager.user.dataSource, self.manager); + + XCTAssertNotNil(self.manager.tox); + XCTAssertNotNil(self.manager.configuration); + XCTAssertNotNil(self.manager.realmManager); + XCTAssertEqualObjects(self.manager.realmManager.realmFileURL.path, self.manager.configuration.fileStorage.pathForDatabase); + + XCTAssertNotNil(self.manager.notificationCenter); +} + +- (void)testToxEncryption +{ + // We want use real data to make sure that encryption/decryption actually works. This is really crucial test. + [self.tox stopMocking]; + self.tox = nil; + + OCTManagerConfiguration *configuration = [OCTManagerConfiguration defaultConfiguration]; + configuration.fileStorage = [self temporaryFileStorage]; + __block NSString *userAddress; + + { + // Create non-encrypted Tox to test manager encryption of first start. + OCTTox *tox = [[OCTTox alloc] initWithOptions:configuration.options savedData:nil error:nil]; + XCTAssertNotNil(tox); + + userAddress = tox.userAddress; + [tox setNickname:@"nonEncrypted" error:nil]; + + NSData *data = [tox save]; + BOOL result = [data writeToFile:configuration.fileStorage.pathForToxSaveFile + options:NSDataWritingAtomic + error:nil]; + XCTAssertTrue(result); + } + + XCTestExpectation *encryptOnFirstRunExpectation = [self expectationWithDescription:@"encryptOnFirstRunExpectation"]; + + [OCTManagerFactory managerWithConfiguration:configuration encryptPassword:@"password123" successBlock:^(id < OCTManager > manager) { + XCTAssertNotNil(manager); + XCTAssertEqualObjects(userAddress, manager.user.userAddress); + XCTAssertEqualObjects(@"nonEncrypted", manager.user.userName); + + [encryptOnFirstRunExpectation fulfill]; + } failureBlock:nil]; + + [self waitForExpectationsWithTimeout:2.0 handler:nil]; + + + XCTestExpectation *encryptedExpectation = [self expectationWithDescription:@"encryptedExpectation"]; + + [OCTManagerFactory managerWithConfiguration:configuration encryptPassword:@"password123" successBlock:^(id < OCTManager > manager) { + XCTAssertNotNil(manager); + XCTAssertEqualObjects(userAddress, manager.user.userAddress); + XCTAssertEqualObjects(@"nonEncrypted", manager.user.userName); + + // Change passphrase + [manager.user setUserName:@"renamed" error:nil]; + [manager changeEncryptPassword:@"new password" oldPassword:@"password123"]; + + [encryptedExpectation fulfill]; + } failureBlock:nil]; + + [self waitForExpectationsWithTimeout:2.0 handler:nil]; + + + XCTestExpectation *renamedEncryptedExpectation = [self expectationWithDescription:@"renamedEncryptedExpectation"]; + + [OCTManagerFactory managerWithConfiguration:configuration encryptPassword:@"new password" successBlock:^(id < OCTManager > manager) { + XCTAssertNotNil(manager); + XCTAssertEqualObjects(userAddress, manager.user.userAddress); + XCTAssertEqualObjects(@"renamed", manager.user.userName); + + [renamedEncryptedExpectation fulfill]; + } failureBlock:nil]; + + [self waitForExpectationsWithTimeout:2.0 handler:nil]; + + + XCTestExpectation *wrongPasswordExpectation = [self expectationWithDescription:@"wrongPasswordExpectation"]; + + [OCTManagerFactory managerWithConfiguration:configuration encryptPassword:@"new password" successBlock:^(id < OCTManager > manager) { + XCTAssertNotNil(manager); + XCTAssertFalse([manager changeEncryptPassword:@"the password" oldPassword:@"wrong password"]); + + [wrongPasswordExpectation fulfill]; + } failureBlock:nil]; + + [self waitForExpectationsWithTimeout:2.0 handler:nil]; + + + XCTestExpectation *newPasswordExpectation = [self expectationWithDescription:@"newPasswordExpectation"]; + + [OCTManagerFactory managerWithConfiguration:configuration encryptPassword:@"new password" successBlock:^(id < OCTManager > manager) { + XCTAssertNotNil(manager); + XCTAssertTrue([manager changeEncryptPassword:@"another" oldPassword:@"new password"]); + + [newPasswordExpectation fulfill]; + } failureBlock:nil]; + + [self waitForExpectationsWithTimeout:2.0 handler:nil]; + + + XCTestExpectation *anotherPasswordExpectation = [self expectationWithDescription:@"anotherPasswordExpectation"]; + + [OCTManagerFactory managerWithConfiguration:configuration encryptPassword:@"another" successBlock:^(id < OCTManager > manager) { + XCTAssertNotNil(manager); + + [anotherPasswordExpectation fulfill]; + } failureBlock:nil]; + + [self waitForExpectationsWithTimeout:2.0 handler:nil]; +} + +- (void)testIsManagerEncryptedWithPassword +{ + // We want use real data to make sure that encryption/decryption actually works. This is really crucial test. + [self.tox stopMocking]; + self.tox = nil; + + OCTManagerConfiguration *configuration = [OCTManagerConfiguration defaultConfiguration]; + configuration.fileStorage = [self temporaryFileStorage]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expectation"]; + + [OCTManagerFactory managerWithConfiguration:configuration encryptPassword:@"password123" successBlock:^(id < OCTManager > manager) { + XCTAssertNotNil(manager); + XCTAssertTrue([manager isManagerEncryptedWithPassword:@"password123"]); + XCTAssertFalse([manager isManagerEncryptedWithPassword:@"wrong password"]); + + [expectation fulfill]; + } failureBlock:nil]; + + [self waitForExpectationsWithTimeout:2.0 handler:nil]; +} + +- (void)testDatabaseMigration +{ + NSMutableArray *friendsArray = [NSMutableArray new]; + for (int i = 0; i < 100; i++) { + [friendsArray addObject:@(i)]; + + NSString *publicKey = [NSString stringWithFormat:@"publicKey-%d", i]; + OCMStub([self.tox publicKeyFromFriendNumber:i error:[OCMArg anyObjectRef]]).andReturn(publicKey); + } + OCMStub([self.tox friendsArray]).andReturn(friendsArray); + + OCTManagerConfiguration *configuration = [OCTManagerConfiguration defaultConfiguration]; + configuration.fileStorage = [self temporaryFileStorage]; + + NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; + NSString *bundlePath = [testBundle pathForResource:@"unencrypted-database" ofType:@"realm"]; + + [[NSFileManager defaultManager] copyItemAtPath:bundlePath toPath:configuration.fileStorage.pathForDatabase + error:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expectation"]; + __block id manager; + + [OCTManagerFactory managerWithConfiguration:configuration encryptPassword:@"the password" successBlock:^(id < OCTManager > m) { + manager = m; + [expectation fulfill]; + } failureBlock:nil]; + + [self waitForExpectationsWithTimeout:20.0 handler:nil]; + + XCTAssertNotNil(manager); + + RLMResults *friends = [manager.objects objectsForType:OCTFetchRequestTypeFriend predicate:nil]; + XCTAssertEqual(friends.count, 100); + + for (OCTToxFriendNumber friendNumber = 0; friendNumber < 100; friendNumber++) { + OCTFriend *friend = friends[friendNumber]; + + XCTAssertEqual(friend.friendNumber, friendNumber); + NSString *nickname = [NSString stringWithFormat:@"friend-%d", friendNumber]; + XCTAssertEqualObjects(friend.nickname, nickname); + } + + RLMResults *messages = [manager.objects objectsForType:OCTFetchRequestTypeMessageAbstract predicate:nil]; + XCTAssertEqual(messages.count, 50000); + + OCTMessageAbstract *message0 = messages[0]; + XCTAssertNotNil(message0.messageText); + XCTAssertNil(message0.messageFile); + XCTAssertNil(message0.messageCall); + XCTAssertEqualObjects(message0.messageText.text, @"message-0"); + + OCTMessageAbstract *message1 = messages[1]; + XCTAssertNil(message1.messageText); + XCTAssertNotNil(message1.messageFile); + XCTAssertNil(message1.messageCall); + XCTAssertEqualObjects(message1.messageFile.fileName, @"file-1"); + + XCTAssertEqualObjects(message0.chatUniqueIdentifier, message1.chatUniqueIdentifier); +} + +- (void)testConfiguration +{ + OCTToxEncryptSave *encryptSave = OCMClassMock([OCTToxEncryptSave class]); + OCMStub([(id)encryptSave alloc]).andReturn(encryptSave); + OCMStub([encryptSave initWithPassphrase:[OCMArg any] + toxData:[OCMArg any] + error:[OCMArg anyObjectRef]]).andReturn(encryptSave); + OCMStub([encryptSave encryptData:[OCMArg any] error:[OCMArg anyObjectRef]]).andReturn([NSData new]); + OCMStub([encryptSave decryptData:[OCMArg any] error:[OCMArg anyObjectRef]]).andReturn([NSData new]); + + OCTManagerConfiguration *configuration = [OCTManagerConfiguration defaultConfiguration]; + configuration.options.ipv6Enabled = YES; + configuration.options.udpEnabled = YES; + configuration.options.localDiscoveryEnabled = YES; + configuration.options.proxyType = OCTToxProxyTypeHTTP; + configuration.options.proxyHost = @"proxy.address"; + configuration.options.proxyPort = 999; + configuration.options.startPort = 123; + configuration.options.endPort = 321; + configuration.options.tcpPort = 777; + configuration.options.holePunchingEnabled = YES; + configuration.importToxSaveFromPath = @"save.tox"; + configuration.fileStorage = [self temporaryFileStorage]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expectation"]; + __block id manager; + + [OCTManagerFactory managerWithConfiguration:configuration encryptPassword:@"p@s$" successBlock:^(id < OCTManager > m) { + manager = m; + [expectation fulfill]; + } failureBlock:nil]; + + [self waitForExpectationsWithTimeout:200.0 handler:nil]; + + OCTManagerConfiguration *c2 = [manager configuration]; + + XCTAssertTrue(c2.options.ipv6Enabled); + XCTAssertTrue(c2.options.udpEnabled); + XCTAssertTrue(c2.options.localDiscoveryEnabled); + XCTAssertEqual(c2.options.proxyType, OCTToxProxyTypeHTTP); + XCTAssertEqualObjects(c2.options.proxyHost, @"proxy.address"); + XCTAssertEqual(c2.options.proxyPort, 999); + XCTAssertEqual(c2.options.startPort, 123); + XCTAssertEqual(c2.options.endPort, 321); + XCTAssertEqual(c2.options.tcpPort, 777); + XCTAssertTrue(c2.options.holePunchingEnabled); + XCTAssertEqualObjects(c2.importToxSaveFromPath, @"save.tox"); + + [(id)encryptSave stopMocking]; +} + +- (void)testSubmanagerDataSource +{ + [self createManager]; + + XCTAssertEqual([self.manager managerGetTox], self.manager.tox); + XCTAssertEqual([self.manager managerGetRealmManager], self.manager.realmManager); + XCTAssertEqual([self.manager managerGetFileStorage], self.manager.configuration.fileStorage); + XCTAssertEqual([self.manager managerGetNotificationCenter], self.manager.notificationCenter); +} + +- (void)testForwardTargetForSelector +{ + [self createManager]; + + id submanager = [FakeSubmanager new]; + self.manager.bootstrap = submanager; + self.manager.chats = submanager; + self.manager.files = submanager; + self.manager.friends = submanager; + self.manager.objects = submanager; + self.manager.user = submanager; + + // test non protocol selector + XCTAssertNil([self.manager forwardingTargetForSelector:@selector(dataSource)]); + + // test for protocol non-implemented selector + XCTAssertNil([self.manager forwardingTargetForSelector:@selector(tox:friendRequestWithMessage:publicKey:)]); + + // test for protocol implemented selector + XCTAssertEqual([self.manager forwardingTargetForSelector:@selector(tox:connectionStatus:)], submanager); +} + +- (void)testForwardTargetForSelector2 +{ + [self createManager]; + + id submanager = [FakeSubmanager new]; + id dummy = [NSObject new]; + + self.manager.bootstrap = submanager; + self.manager.chats = dummy; + self.manager.files = dummy; + self.manager.friends = dummy; + self.manager.objects = dummy; + self.manager.user = dummy; + + XCTAssertEqual([self.manager forwardingTargetForSelector:@selector(tox:connectionStatus:)], submanager); + + self.manager.bootstrap = dummy; + self.manager.chats = submanager; + self.manager.files = dummy; + self.manager.friends = dummy; + self.manager.objects = dummy; + self.manager.user = dummy; + + XCTAssertEqual([self.manager forwardingTargetForSelector:@selector(tox:connectionStatus:)], submanager); + + self.manager.bootstrap = dummy; + self.manager.chats = dummy; + self.manager.files = submanager; + self.manager.friends = dummy; + self.manager.objects = dummy; + self.manager.user = dummy; + + XCTAssertEqual([self.manager forwardingTargetForSelector:@selector(tox:connectionStatus:)], submanager); + + self.manager.bootstrap = dummy; + self.manager.chats = dummy; + self.manager.files = dummy; + self.manager.friends = submanager; + self.manager.objects = dummy; + self.manager.user = dummy; + + XCTAssertEqual([self.manager forwardingTargetForSelector:@selector(tox:connectionStatus:)], submanager); + + self.manager.bootstrap = dummy; + self.manager.chats = dummy; + self.manager.files = dummy; + self.manager.friends = dummy; + self.manager.objects = submanager; + self.manager.user = dummy; + + XCTAssertEqual([self.manager forwardingTargetForSelector:@selector(tox:connectionStatus:)], submanager); + + self.manager.bootstrap = dummy; + self.manager.chats = dummy; + self.manager.files = dummy; + self.manager.friends = dummy; + self.manager.objects = dummy; + self.manager.user = submanager; + + XCTAssertEqual([self.manager forwardingTargetForSelector:@selector(tox:connectionStatus:)], submanager); +} + +- (void)testExportToxSaveFile +{ + [self createManager]; + + NSString *path = [self.manager exportToxSaveFileAndReturnError:nil]; + NSString *result = [[self.manager configuration].fileStorage.pathForTemporaryFilesDirectory stringByAppendingPathComponent:@"save.tox"]; + XCTAssertEqualObjects(path, result); +} + +#pragma mark - Helper methods + +- (void)createManager +{ + OCMStub([[self.mockedCallManager alloc] initWithTox:[OCMArg anyPointer]]).andReturn(nil); + OCTManagerConfiguration *configuration = [OCTManagerConfiguration defaultConfiguration]; + + configuration.fileStorage = [self temporaryFileStorage]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expectation"]; + __weak OCTManagerImplTests *weakSelf = self; + + [OCTManagerFactory managerWithConfiguration:configuration encryptPassword:@"123" successBlock:^(id < OCTManager > manager) { + weakSelf.manager = (OCTManagerImpl *)manager; + [expectation fulfill]; + } failureBlock:nil]; + + [self waitForExpectationsWithTimeout:2.0 handler:nil]; +} + +- (OCTDefaultFileStorage *)temporaryFileStorage +{ + return [[OCTDefaultFileStorage alloc] initWithBaseDirectory:self.tempDirectoryPath + temporaryDirectory:[self.tempDirectoryPath stringByAppendingPathComponent:@"tmp"]]; +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTMessageAbstractTests.m b/local_pod_repo/objcTox/Tests/OCTMessageAbstractTests.m new file mode 100644 index 0000000..37bff6a --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTMessageAbstractTests.m @@ -0,0 +1,52 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTMessageAbstract.h" +#import "OCTFriend.h" + +@interface OCTMessageAbstractTests : XCTestCase + +@end + +@implementation OCTMessageAbstractTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testDate +{ + OCTMessageAbstract *message = [OCTMessageAbstract new]; + + message.dateInterval = 0; + XCTAssertNil([message date]); + + message.dateInterval = 12345; + XCTAssertEqual([[message date] timeIntervalSince1970], 12345); + + message.dateInterval = -12345; + XCTAssertNil([message date]); +} + +- (void)testIsOutgoing +{ + OCTMessageAbstract *message = [OCTMessageAbstract new]; + + XCTAssertTrue([message isOutgoing]); + + message.senderUniqueIdentifier = @"Some unique id"; + XCTAssertFalse([message isOutgoing]); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTObjectTests.m b/local_pod_repo/objcTox/Tests/OCTObjectTests.m new file mode 100644 index 0000000..a31d6b7 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTObjectTests.m @@ -0,0 +1,40 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTObject.h" + +@interface OCTObjectTests : XCTestCase + +@end + +@implementation OCTObjectTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testIsEqual +{ + OCTObject *object = [OCTObject new]; + OCTObject *same = [OCTObject new]; + same.uniqueIdentifier = object.uniqueIdentifier; + + OCTObject *another = [OCTObject new]; + + XCTAssertTrue([object isEqual:object]); + XCTAssertTrue([object isEqual:same]); + XCTAssertFalse([object isEqual:another]); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTRealmTests.h b/local_pod_repo/objcTox/Tests/OCTRealmTests.h new file mode 100644 index 0000000..5e6c3c8 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTRealmTests.h @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import + +#import "OCTRealmManager.h" +#import "OCTFriend.h" +#import "OCTChat.h" + +@interface OCTRealmManager (Tests) + +@property (strong, nonatomic) dispatch_queue_t queue; +@property (strong, nonatomic) RLMRealm *realm; + +@end + +@interface OCTRealmTests : XCTestCase + +/** + * Partially mocked realm manager with in memory realm, which is reset after each test. + */ +@property (strong, nonatomic) OCTRealmManager *realmManager; + +- (OCTFriend *)createFriendWithFriendNumber:(OCTToxFriendNumber)friendNumber; + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTRealmTests.m b/local_pod_repo/objcTox/Tests/OCTRealmTests.m new file mode 100644 index 0000000..2236cc1 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTRealmTests.m @@ -0,0 +1,61 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTRealmTests.h" + +@interface OCTRealmTests () + +@property (strong, nonatomic) id realmMock; + +@end + +@implementation OCTRealmTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. + + RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; + configuration.inMemoryIdentifier = @"OCTRealmTests"; + + RLMRealm *realRealm = [RLMRealm realmWithConfiguration:configuration error:nil]; + + self.realmMock = OCMClassMock([RLMRealm class]); + OCMStub([self.realmMock realmWithConfiguration:[OCMArg any] error:[OCMArg anyObjectRef]]).andReturn(realRealm); + + NSURL *fileURL = [NSURL fileURLWithPath:@"/some/realm/path"]; + self.realmManager = [[OCTRealmManager alloc] initWithDatabaseFileURL:fileURL encryptionKey:nil]; + self.realmManager = OCMPartialMock(self.realmManager); +} + +- (void)tearDown +{ + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm deleteAllObjects]; + [self.realmManager.realm commitWriteTransaction]; + + [(id)self.realmManager stopMocking]; + self.realmManager = nil; + + [self.realmMock stopMocking]; + self.realmMock = nil; + + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (OCTFriend *)createFriendWithFriendNumber:(OCTToxFriendNumber)friendNumber +{ + OCTFriend *friend = [OCTFriend new]; + friend.nickname = @""; + friend.publicKey = [[NSUUID UUID] UUIDString]; + friend.friendNumber = friendNumber; + + return friend; +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTSubmanagerBootstrapImplTests.m b/local_pod_repo/objcTox/Tests/OCTSubmanagerBootstrapImplTests.m new file mode 100644 index 0000000..4b74711 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTSubmanagerBootstrapImplTests.m @@ -0,0 +1,211 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import + +#import "OCTSubmanagerBootstrapImpl.h" +#import "OCTSubmanagerDataSource.h" +#import "OCTTox.h" +#import "OCTRealmManager.h" +#import "OCTSettingsStorageObject.h" +#import "OCTNode.h" + +@interface OCTSubmanagerBootstrapImpl (Tests) + +@property (strong, nonatomic) NSMutableSet *addedNodes; + +@property (assign, nonatomic) NSTimeInterval didConnectDelay; +@property (assign, nonatomic) NSTimeInterval iterationTime; + +@end + +@interface OCTSubmanagerBootstrapImplTests : XCTestCase + +@property (strong, nonatomic) id dataSource; +@property (strong, nonatomic) id tox; +@property (strong, nonatomic) id realmManager; +@property (strong, nonatomic) id settingsStorage; +@property (strong, nonatomic) OCTSubmanagerBootstrapImpl *submanager; + +@end + +@implementation OCTSubmanagerBootstrapImplTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. + + self.dataSource = OCMProtocolMock(@protocol(OCTSubmanagerDataSource)); + // self.tox = OCMClassMock([OCTTox class]); + self.tox = OCMStrictClassMock([OCTTox class]); + self.realmManager = OCMClassMock([OCTRealmManager class]); + self.settingsStorage = OCMClassMock([OCTSettingsStorageObject class]); + + OCMStub([self.dataSource managerGetTox]).andReturn(self.tox); + OCMStub([self.dataSource managerGetRealmManager]).andReturn(self.realmManager); + OCMStub([self.realmManager settingsStorage]).andReturn(self.settingsStorage); + + self.submanager = [OCTSubmanagerBootstrapImpl new]; + self.submanager.dataSource = self.dataSource; +} + +- (void)tearDown +{ + self.dataSource = nil; + self.tox = nil; + self.realmManager = nil; + self.settingsStorage = nil; + self.submanager = nil; + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testAddPredefinedNodes +{ + [self.submanager addPredefinedNodes]; + + XCTAssertTrue(self.submanager.addedNodes.count > 0); + + for (OCTNode *node in self.submanager.addedNodes) { + XCTAssertTrue(node.ipv4Host.length > 0); + XCTAssertTrue(node.udpPort > 0); + XCTAssertNotNil(node.tcpPorts); + XCTAssertEqual(node.publicKey.length, kOCTToxPublicKeyLength); + } +} + +- (void)testBootstrapCustomNodes +{ + XCTestExpectation *expectation = [self expectationWithDescription:@"bootstrap"]; + + self.submanager.didConnectDelay = 0.0; + self.submanager.iterationTime = 0.05; + + [self.submanager addNodeWithIpv4Host:@"one" ipv6Host:@"one6" udpPort:1 tcpPorts:@[@1, @11] publicKey:@"1"]; + [self.submanager addNodeWithIpv4Host:@"two" ipv6Host:nil udpPort:2 tcpPorts:@[@2] publicKey:@"2"]; + [self.submanager addNodeWithIpv4Host:nil ipv6Host:@"three6" udpPort:3 tcpPorts:@[@3] publicKey:@"3"]; + [self.submanager addNodeWithIpv4Host:@"four" ipv6Host:@"four6" udpPort:4 tcpPorts:@[] publicKey:@"4"]; + + OCMExpect([self.tox bootstrapFromHost:@"one" port:1 publicKey:@"1" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox bootstrapFromHost:@"one6" port:1 publicKey:@"1" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox addTCPRelayWithHost:@"one" port:1 publicKey:@"1" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox addTCPRelayWithHost:@"one6" port:1 publicKey:@"1" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox addTCPRelayWithHost:@"one" port:11 publicKey:@"1" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox addTCPRelayWithHost:@"one6" port:11 publicKey:@"1" error:[OCMArg anyObjectRef]]); + + OCMExpect([self.tox bootstrapFromHost:@"two" port:2 publicKey:@"2" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox addTCPRelayWithHost:@"two" port:2 publicKey:@"2" error:[OCMArg anyObjectRef]]); + + OCMExpect([self.tox bootstrapFromHost:@"three6" port:3 publicKey:@"3" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox addTCPRelayWithHost:@"three6" port:3 publicKey:@"3" error:[OCMArg anyObjectRef]]); + + OCMExpect([self.tox bootstrapFromHost:@"four" port:4 publicKey:@"4" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox bootstrapFromHost:@"four6" port:4 publicKey:@"4" error:[OCMArg anyObjectRef]]); + + [self.submanager bootstrap]; + + [self performBlock:^{ + [expectation fulfill]; + } afterDelay:0.1]; + [self waitForExpectationsWithTimeout:0.5 handler:nil]; + + OCMVerifyAll(self.tox); +} + +- (void)testBootstrapSeveralPortions +{ + XCTestExpectation *expectation = [self expectationWithDescription:@"bootstrap"]; + + self.submanager.didConnectDelay = 0.0; + self.submanager.iterationTime = 0.1; + + [self.submanager addNodeWithIpv4Host:@"h1" ipv6Host:nil udpPort:1 tcpPorts:@[] publicKey:@"1"]; + [self.submanager addNodeWithIpv4Host:@"h2" ipv6Host:nil udpPort:2 tcpPorts:@[] publicKey:@"2"]; + [self.submanager addNodeWithIpv4Host:@"h3" ipv6Host:nil udpPort:3 tcpPorts:@[] publicKey:@"3"]; + [self.submanager addNodeWithIpv4Host:@"h4" ipv6Host:nil udpPort:4 tcpPorts:@[] publicKey:@"4"]; + [self.submanager addNodeWithIpv4Host:@"h5" ipv6Host:nil udpPort:5 tcpPorts:@[] publicKey:@"5"]; + [self.submanager addNodeWithIpv4Host:@"h6" ipv6Host:nil udpPort:6 tcpPorts:@[] publicKey:@"6"]; + [self.submanager addNodeWithIpv4Host:@"h7" ipv6Host:nil udpPort:7 tcpPorts:@[] publicKey:@"7"]; + [self.submanager addNodeWithIpv4Host:@"h8" ipv6Host:nil udpPort:8 tcpPorts:@[] publicKey:@"8"]; + [self.submanager addNodeWithIpv4Host:@"h9" ipv6Host:nil udpPort:9 tcpPorts:@[] publicKey:@"9"]; + [self.submanager addNodeWithIpv4Host:@"h10" ipv6Host:nil udpPort:10 tcpPorts:@[] publicKey:@"10"]; + + OCMExpect([self.tox bootstrapFromHost:@"h1" port:1 publicKey:@"1" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox bootstrapFromHost:@"h2" port:2 publicKey:@"2" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox bootstrapFromHost:@"h3" port:3 publicKey:@"3" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox bootstrapFromHost:@"h4" port:4 publicKey:@"4" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox bootstrapFromHost:@"h5" port:5 publicKey:@"5" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox bootstrapFromHost:@"h6" port:6 publicKey:@"6" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox bootstrapFromHost:@"h7" port:7 publicKey:@"7" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox bootstrapFromHost:@"h8" port:8 publicKey:@"8" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox bootstrapFromHost:@"h9" port:9 publicKey:@"9" error:[OCMArg anyObjectRef]]); + OCMExpect([self.tox bootstrapFromHost:@"h10" port:10 publicKey:@"10" error:[OCMArg anyObjectRef]]); + + [self.submanager bootstrap]; + + [self performBlock:^{ + [expectation fulfill]; + } afterDelay:0.6]; + [self waitForExpectationsWithTimeout:0.8 handler:nil]; + + OCMVerifyAll(self.tox); +} + +- (void)testBootstrapDidConnectVerify +{ + XCTestExpectation *expectation = [self expectationWithDescription:@"bootstrap"]; + + OCMStub([self.settingsStorage bootstrapDidConnect]).andReturn(YES); + + self.submanager.didConnectDelay = 0.1; + self.submanager.iterationTime = 0.05; + + [self.submanager addNodeWithIpv4Host:@"one" ipv6Host:nil udpPort:1 tcpPorts:@[] publicKey:@"1"]; + + OCMExpect([self.tox bootstrapFromHost:@"one" port:1 publicKey:@"1" error:[OCMArg anyObjectRef]]); + + [self.submanager bootstrap]; + + [self performBlock:^{ + [expectation fulfill]; + } afterDelay:0.3]; + [self waitForExpectationsWithTimeout:0.5 handler:nil]; + + OCMVerifyAll(self.tox); +} + +- (void)testBootstrapDidConnectReject +{ + XCTestExpectation *expectation = [self expectationWithDescription:@"bootstrap"]; + + OCMStub([self.settingsStorage bootstrapDidConnect]).andReturn(YES); + + self.submanager.didConnectDelay = 0.2; + self.submanager.iterationTime = 0.05; + + [self.submanager addNodeWithIpv4Host:@"one" ipv6Host:nil udpPort:1 tcpPorts:@[] publicKey:@"1"]; + + [self.submanager bootstrap]; + + [self performBlock:^{ + [expectation fulfill]; + } afterDelay:0.1]; + [self waitForExpectationsWithTimeout:0.15 handler:nil]; + + OCMVerifyAll(self.tox); +} + +- (void)performBlock:(void (^)())block afterDelay:(NSTimeInterval)delay +{ + [self performSelector:@selector(runBlock:) withObject:block afterDelay:delay]; +} + +- (void)runBlock:(void (^)())block +{ + block(); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTSubmanagerCallsImplTests.m b/local_pod_repo/objcTox/Tests/OCTSubmanagerCallsImplTests.m new file mode 100644 index 0000000..2b63193 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTSubmanagerCallsImplTests.m @@ -0,0 +1,527 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTRealmTests.h" + +#import "OCTSubmanagerCallsImpl.h" +#import "OCTRealmManager.h" +#import "OCTAudioEngine.h" +#import "OCTMessageCall.h" +#import "OCTMessageAbstract.h" +#import "OCTToxAV.h" +#import "OCTTox.h" +#import "OCTToxOptions.h" +#import + +@import AVFoundation; + +@interface OCTSubmanagerCallsImpl (Tests) + +@property (strong, nonatomic) OCTToxAV *toxAV; +@property (strong, nonatomic) OCTAudioEngine *audioEngine; +@property (strong, nonatomic) OCTVideoEngine *videoEngine; +@property (weak, nonatomic) id dataSource; +@property (strong, nonatomic) OCTCallTimer *timer; + +- (void)toxAV:(OCTToxAV *)toxAV receiveCallAudioEnabled:(BOOL)audio videoEnabled:(BOOL)video friendNumber:(OCTToxFriendNumber)friendNumber; +- (void)toxAV:(OCTToxAV *)toxAV callStateChanged:(OCTToxAVCallState)state friendNumber:(OCTToxFriendNumber)friendNumber; +- (void)toxAV:(OCTToxAV *)toxAV audioBitRateChanged:(OCTToxAVAudioBitRate)bitrate stable:(BOOL)stable friendNumber:(OCTToxFriendNumber)friendNumber; +- (void) toxAV:(OCTToxAV *)toxAV + receiveAudio:(OCTToxAVPCMData *)pcm + sampleCount:(OCTToxAVSampleCount)sampleCount + channels:(OCTToxAVChannels)channels + sampleRate:(OCTToxAVSampleRate)sampleRate + friendNumber:(OCTToxFriendNumber)friendNumber; + +- (void) toxAV:(OCTToxAV *)toxAV + receiveVideoFrameWithWidth:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height + yPlane:(OCTToxAVPlaneData *)yPlane uPlane:(OCTToxAVPlaneData *)uPlane + vPlane:(OCTToxAVPlaneData *)vPlane + yStride:(OCTToxAVStrideData)yStride uStride:(OCTToxAVStrideData)uStride + vStride:(OCTToxAVStrideData)vStride + friendNumber:(OCTToxFriendNumber)friendNumber; + +- (OCTCall *)createCallWithFriend:(OCTFriend *)friend status:(OCTCallStatus)status; +- (OCTCall *)getCurrentCallForFriendNumber:(OCTToxFriendNumber)friendNumber; + +@end + +@interface OCTSubmanagerCallsImplTests : OCTRealmTests + +@property (strong, nonatomic) id dataSource; +@property (strong, nonatomic) OCTSubmanagerCallsImpl *callManager; +@property (strong, nonatomic) OCTTox *tox; +@property (strong, nonatomic) id mockedAudioEngine; +@property (strong, nonatomic) id mockedVideoEngine; +@property (strong, nonatomic) id mockedToxAV; + +@end + +@implementation OCTSubmanagerCallsImplTests + +- (void)setUp +{ + [super setUp]; + self.tox = [[OCTTox alloc] initWithOptions:[OCTToxOptions new] savedData:nil error:nil]; + self.tox = OCMPartialMock(self.tox); + self.callManager = [[OCTSubmanagerCallsImpl alloc] initWithTox:self.tox]; + + self.dataSource = OCMProtocolMock(@protocol(OCTSubmanagerDataSource)); + OCMStub([self.dataSource managerGetRealmManager]).andReturn(self.realmManager); + OCMStub([self.dataSource managerGetTox]).andReturn(self.tox); + + OCTAudioEngine *audioEngine = [OCTAudioEngine new]; + self.mockedAudioEngine = OCMPartialMock(audioEngine); + self.callManager.audioEngine = self.mockedAudioEngine; + + OCTVideoEngine *videoEngine = [OCTVideoEngine new]; + self.mockedVideoEngine = OCMPartialMock(videoEngine); + self.callManager.videoEngine = self.mockedVideoEngine; + + self.mockedToxAV = OCMClassMock([OCTToxAV class]); + self.callManager.toxAV = self.mockedToxAV; + + self.callManager.dataSource = self.dataSource; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [self.dataSource stopMocking]; + [self.mockedAudioEngine stopMocking]; + [self.mockedVideoEngine stopMocking]; + [self.mockedToxAV stopMocking]; + [(id)self.tox stopMocking]; + self.dataSource = nil; + self.callManager = nil; + self.tox = nil; + self.mockedAudioEngine = nil; + self.mockedToxAV = nil; + self.mockedVideoEngine = nil; + [super tearDown]; +} + +- (void)testInit +{ + id tox = OCMClassMock([OCTTox class]); + OCMStub([self.mockedToxAV alloc]).andReturn(self.mockedToxAV); + OCMStub([self.mockedToxAV initWithTox:tox error:nil]).andReturn(self.mockedToxAV); + + OCTSubmanagerCallsImpl *manager = [[OCTSubmanagerCallsImpl alloc] initWithTox:tox]; + + XCTAssertNotNil(manager); + OCMVerify([(OCTToxAV *)self.mockedToxAV start]); + + [tox stopMocking]; +} + +- (void)testSetup +{ + OCMStub([self.mockedAudioEngine new]).andReturn(self.mockedAudioEngine); + + OCMStub([self.mockedVideoEngine new]).andReturn(self.mockedVideoEngine); + OCMStub([self.mockedVideoEngine setupAndReturnError:[OCMArg anyObjectRef]]).andReturn(YES); + + XCTAssertTrue([self.callManager setupAndReturnError:nil]); + + OCMVerify([self.mockedVideoEngine setupAndReturnError:[OCMArg anyObjectRef]]); +} + +- (void)testCallToChat +{ + [OCMStub([self.mockedToxAV callFriendNumber:1 + audioBitRate:0 + videoBitRate:0 + error:[OCMArg anyObjectRef]]).andReturn(YES) ignoringNonObjectArgs]; + + OCTFriend *friend = [self createFriendWithFriendNumber:1]; + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + OCTChat *chat = [self.realmManager getOrCreateChatWithFriend:friend]; + + OCTCall *call = [self.callManager callToChat:chat enableAudio:YES enableVideo:NO error:nil]; + + XCTAssertNotNil(call.chat); + + XCTAssertEqualObjects(call.chat, chat); + XCTAssertEqual(call.status, OCTCallStatusDialing); + XCTAssertNil(call.caller); + XCTAssertTrue([call isOutgoing]); +} + +- (void)testEnableVideoForCall +{ + id partialMockedVideoEngine = OCMPartialMock([OCTVideoEngine new]); + self.callManager.videoEngine = partialMockedVideoEngine; + [self.mockedVideoEngine setExpectationOrderMatters:YES]; + OCMExpect([partialMockedVideoEngine startSendingVideo]); + OCMExpect([partialMockedVideoEngine stopSendingVideo]); + + [OCMStub([self.mockedToxAV setVideoBitRate:123 force:YES forFriend:987 error:[OCMArg anyObjectRef]]).andReturn(YES) ignoringNonObjectArgs]; + OCTFriend *friend = [self createFriendWithFriendNumber:987]; + OCTCall *call = [self.callManager createCallWithFriend:friend status:OCTCallStatusActive]; + + XCTAssertTrue([self.callManager enableVideoSending:YES forCall:call error:nil]); + XCTAssertTrue(call.videoIsEnabled); + XCTAssertEqual(self.callManager.videoEngine.friendNumber, 987); + + XCTAssertTrue([self.callManager enableVideoSending:NO forCall:call error:nil]); + XCTAssertFalse(call.videoIsEnabled); + + OCMVerifyAll(partialMockedVideoEngine); +} + +- (void)testEndCall +{ + OCMStub([self.mockedToxAV callFriendNumber:12 audioBitRate:48 videoBitRate:0 error:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.mockedToxAV sendCallControl:OCTToxAVCallControlCancel toFriendNumber:12 error:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.mockedAudioEngine stopAudioFlow:[OCMArg anyObjectRef]]).andReturn(YES); + + OCTFriend *friend = [self createFriendWithFriendNumber:12]; + OCTChat *chat = [self.realmManager getOrCreateChatWithFriend:friend]; + + OCTCall *call = [self.callManager callToChat:chat enableAudio:YES enableVideo:NO error:nil]; + + NSError *error; + XCTAssertTrue([self.callManager sendCallControl:OCTToxAVCallControlCancel toCall:call error:&error]); + + OCMVerify([self.realmManager deleteObject:call]); + + XCTAssertNotNil(chat.lastMessage.messageCall); + XCTAssertEqual(chat.lastMessage.messageCall.callEvent, OCTMessageCallEventUnanswered); + XCTAssertTrue(chat.lastMessage.isOutgoing); +} + +- (void)testAnswerCallSuccess +{ + OCMStub([self.mockedAudioEngine startAudioFlow:[OCMArg anyObjectRef]]).andReturn(YES); + + OCTFriend *friend = [self createFriendWithFriendNumber:1234]; + + OCTCall *call = [self.callManager createCallWithFriend:friend status:OCTCallStatusRinging]; + + OCMStub([self.mockedToxAV answerIncomingCallFromFriend:1234 audioBitRate:0 videoBitRate:0 error:[OCMArg anyObjectRef]]).andReturn(YES); + + XCTAssertTrue([self.callManager answerCall:call enableAudio:NO enableVideo:NO error:nil]); + XCTAssertEqual(self.callManager.audioEngine.friendNumber, 1234); +} + +- (void)testCallStateReceiveFinished +{ + OCMStub([self.mockedAudioEngine isAudioRunning:nil]).andReturn(NO); + OCMStub([self.mockedAudioEngine friendNumber]).andReturn(89); + + OCTFriend *friend = [self createFriendWithFriendNumber:89]; + + OCTCall *call = [self.callManager createCallWithFriend:friend status:OCTCallStatusDialing]; + [self.realmManager updateObject:call withBlock:^(OCTCall *callToUpdate) { + callToUpdate.status = OCTCallStatusRinging; + }]; + + OCTToxAVCallState state = 0; + state |= OCTToxAVFriendCallStateFinished; + + [self.callManager toxAV:nil callStateChanged:state friendNumber:89]; + + OCTChat *chat = [self.realmManager getOrCreateChatWithFriend:friend]; + + XCTAssertNotNil(chat.lastMessage.messageCall); + XCTAssertEqual(chat.lastMessage.messageCall.callEvent, OCTMessageCallEventUnanswered); + + call = [self.callManager createCallWithFriend:friend status:OCTCallStatusRinging]; + [self.realmManager updateObject:call withBlock:^(OCTCall *callToUpdate) { + call.status = OCTCallStatusActive; + }]; + + [self.callManager toxAV:nil callStateChanged:state friendNumber:89]; + + XCTAssertEqual(chat.lastMessage.messageCall.callEvent, OCTMessageCallEventAnswered); +} + +- (void)testFriendAnsweredCall +{ + id timer = OCMClassMock([OCTCallTimer class]); + self.callManager.timer = timer; + + OCTFriend *friend = [self createFriendWithFriendNumber:92]; + + OCTCall *call = [self.callManager createCallWithFriend:friend status:OCTCallStatusRinging]; + [self.realmManager updateObject:call withBlock:^(OCTCall *callToUpdate) { + callToUpdate.status = OCTCallStatusDialing; + }]; + + OCTToxAVCallState state = 0; + state |= OCTToxAVFriendCallStateAcceptingVideo; + + OCMStub([self.mockedAudioEngine startAudioFlow:[OCMArg anyObjectRef]]).andReturn(YES); + [self.callManager toxAV:nil callStateChanged:state friendNumber:92]; + + OCMVerify([self.mockedAudioEngine startAudioFlow:[OCMArg anyObjectRef]]); + OCMVerify([self.mockedAudioEngine setFriendNumber:92]); + OCMVerify([timer startTimerForCall:[OCMArg isNotNil]]); + + XCTAssertEqual(call.status, OCTCallStatusActive); + XCTAssertTrue(call.friendAcceptingVideo); +} + +- (void)testEnableMicrophone +{ + [self.callManager setEnableMicrophone:NO]; + + OCMVerify([self.mockedAudioEngine setEnableMicrophone:NO]); + + XCTAssertFalse(self.callManager.enableMicrophone); +} + +- (void)testTogglePauseForCall +{ + OCMStub([self.mockedToxAV sendCallControl:OCTToxAVCallControlPause toFriendNumber:12345 error:nil]).andReturn(YES); + id partialMockedAudioEngine = OCMPartialMock([OCTAudioEngine new]); + self.callManager.audioEngine = partialMockedAudioEngine; + self.callManager.audioEngine.friendNumber = 12345; + + OCTFriend *friend = [self createFriendWithFriendNumber:12345]; + + OCTCall *call = [self.callManager createCallWithFriend:friend status:OCTCallStatusActive]; + [self.realmManager updateObject:call withBlock:^(OCTCall *callToUpdate) { + callToUpdate.videoIsEnabled = YES; + }]; + + OCMStub([self.mockedVideoEngine isSendingVideo]).andReturn(YES); + OCMStub([partialMockedAudioEngine isAudioRunning:nil]).andReturn(YES); + OCMStub([self.mockedVideoEngine stopSendingVideo]); + XCTAssertTrue([self.callManager sendCallControl:OCTToxAVCallControlPause toCall:call error:nil]); + + OCMStub([self.mockedVideoEngine startSendingVideo]); + OCMVerify([self.mockedVideoEngine stopSendingVideo]); + OCMVerify([partialMockedAudioEngine stopAudioFlow:nil]); + + OCMStub([partialMockedAudioEngine isAudioRunning:nil]).andReturn(NO); + OCMStub([self.mockedVideoEngine isSendingVideo]).andReturn(NO); + + OCMStub([self.mockedToxAV sendCallControl:OCTToxAVCallControlResume toFriendNumber:12345 error:nil]).andReturn(YES); + OCMStub([partialMockedAudioEngine startAudioFlow:[OCMArg anyObjectRef]]).andReturn(YES); + XCTAssertTrue([self.callManager sendCallControl:OCTToxAVCallControlResume toCall:call error:nil]); + + OCMVerify([self.mockedVideoEngine startSendingVideo]); + OCMVerify([partialMockedAudioEngine startAudioFlow:[OCMArg anyObjectRef]]); +} + +- (void)testSetAudioBitRate +{ + OCTFriend *friend = [self createFriendWithFriendNumber:123456]; + + OCTCall *call = [self.callManager createCallWithFriend:friend status:OCTCallStatusActive]; + + OCMStub([self.mockedToxAV setAudioBitRate:5555 force:NO forFriend:123456 error:nil]).andReturn(YES); + + XCTAssertTrue([self.callManager setAudioBitrate:5555 forCall:call error:nil]); + OCMVerify([self.mockedToxAV setAudioBitRate:5555 force:NO forFriend:123456 error:nil]); +} + +#pragma mark - Pause Scenarios + +- (void)testAnsweringAnotherCallWhileActive +{ + [OCMStub([self.mockedToxAV sendCallControl:123 toFriendNumber:123 error:[OCMArg anyObjectRef]]).andReturn(YES) ignoringNonObjectArgs]; + [OCMStub([self.mockedToxAV answerIncomingCallFromFriend:123 audioBitRate:48 videoBitRate:0 error:[OCMArg anyObjectRef]]).andReturn(YES) ignoringNonObjectArgs]; + [self.mockedToxAV setExpectationOrderMatters:YES]; + OCMExpect([self.mockedToxAV sendCallControl:OCTToxAVCallControlPause toFriendNumber:4 error:[OCMArg anyObjectRef]]); + OCMExpect([self.mockedToxAV answerIncomingCallFromFriend:5 audioBitRate:48 videoBitRate:0 error:[OCMArg anyObjectRef]]); + + OCMStub([self.callManager.audioEngine isAudioRunning:nil]).andReturn(YES); + OCMStub([self.callManager.audioEngine stopAudioFlow:[OCMArg anyObjectRef]]).andReturn(YES); + OCMStub([self.callManager.audioEngine startAudioFlow:[OCMArg anyObjectRef]]).andReturn(YES); + + OCTFriend *firstFriend = [self createFriendWithFriendNumber:4]; + OCTFriend *secondFriend = [self createFriendWithFriendNumber:5]; + OCTCall *firstCall = [self.callManager createCallWithFriend:firstFriend status:OCTCallStatusActive]; + self.callManager.audioEngine.friendNumber = 4; + + // create incoming call + OCTCall *secondCall = [self.callManager createCallWithFriend:secondFriend status:OCTCallStatusRinging]; + + // mock call timer + id mockedTimer = OCMClassMock([OCTCallTimer class]); + [mockedTimer setExpectationOrderMatters:YES]; + OCMExpect([mockedTimer stopTimer]); + OCMExpect([mockedTimer startTimerForCall:[OCMArg isNotNil]]); + + self.callManager.timer = mockedTimer; + + [self.callManager answerCall:secondCall enableAudio:YES enableVideo:NO error:nil]; + + XCTAssertEqual(secondCall.status, OCTCallStatusActive); + XCTAssertEqual(secondCall.pausedStatus, OCTCallPausedStatusNone); + XCTAssertEqual(self.callManager.audioEngine.friendNumber, 5); + + XCTAssertEqual(firstCall.status, OCTCallStatusActive); + XCTAssertEqual(firstCall.pausedStatus, OCTCallPausedStatusByUser); + + OCMVerifyAll(mockedTimer); + OCMVerifyAll(self.mockedAudioEngine); +} + +- (void)testPauseControlPermissions +{ + OCTFriend *friend = [self createFriendWithFriendNumber:11]; + + OCTCall *call = [self.callManager createCallWithFriend:friend status:OCTCallStatusActive]; + + id mockedTimer = OCMClassMock([OCTCallTimer class]); + [mockedTimer setExpectationOrderMatters:YES]; + self.callManager.timer = mockedTimer; + OCMExpect([mockedTimer stopTimer]); + OCMExpect([mockedTimer startTimerForCall:[OCMArg isNotNil]]); + + [self.callManager toxAV:nil callStateChanged:OCTToxAVFriendCallStatePaused friendNumber:11]; + + XCTAssertEqual(call.pausedStatus, OCTCallPausedStatusByFriend); + XCTAssertEqual(call.status, OCTCallStatusActive); + + id strictMockedTimer = OCMStrictClassMock([OCTCallTimer class]); + self.callManager.timer = strictMockedTimer; + + OCMStub([self.mockedToxAV sendCallControl:OCTToxAVCallControlResume toFriendNumber:11 error:[OCMArg anyObjectRef]]).andReturn(YES); + [self.callManager sendCallControl:OCTToxAVCallControlResume toCall:call error:nil]; + + XCTAssertEqual(call.pausedStatus, OCTCallPausedStatusByFriend); + + OCMStub([self.mockedToxAV sendCallControl:OCTToxAVCallControlPause toFriendNumber:11 error:[OCMArg anyObjectRef]]).andReturn(YES); + [self.callManager sendCallControl:OCTToxAVCallControlPause toCall:call error:nil]; + + XCTAssertEqual(call.pausedStatus, OCTCallPausedStatusByFriend | OCTCallPausedStatusByUser); + + [self.callManager toxAV:nil callStateChanged:OCTToxAVFriendCallStateAcceptingAudio friendNumber:11]; + XCTAssertEqual(call.pausedStatus, OCTCallPausedStatusByUser); + + self.callManager.timer = mockedTimer; + + OCMStub([self.mockedAudioEngine startAudioFlow:[OCMArg anyObjectRef]]).andReturn(YES); + [self.callManager sendCallControl:OCTToxAVCallControlResume toCall:call error:nil]; + XCTAssertEqual(call.pausedStatus, OCTCallPausedStatusNone); + + OCMVerifyAll(mockedTimer); +} + +#pragma mark - Private +- (void)testGetOrCreateCallWithFriend +{ + OCTFriend *friend = [self createFriendWithFriendNumber:222]; + + OCTChat *chat = [self.realmManager getOrCreateChatWithFriend:friend]; + + OCTCall *call = [self.callManager createCallWithFriend:friend status:OCTCallStatusActive]; + OCTCall *sameCall = [self.callManager getCurrentCallForFriendNumber:222]; + + XCTAssertNotNil(call.chat); + XCTAssertEqualObjects(call.chat, chat); + XCTAssertEqualObjects(sameCall, call); +} + + #pragma mark - Delegates + +- (void)testReceiveCalls +{ + id delegate = OCMProtocolMock(@protocol(OCTSubmanagerCallDelegate)); + OCMStub([delegate respondsToSelector:[OCMArg anySelector]]).andReturn(YES); + self.callManager.delegate = delegate; + + OCTFriend *friend = [self createFriendWithFriendNumber:221]; + + OCTCall *call = [self.callManager createCallWithFriend:friend status:OCTCallStatusActive]; + + [self.callManager toxAV:nil receiveCallAudioEnabled:YES videoEnabled:NO friendNumber:221]; + OCMVerify([delegate callSubmanager:self.callManager receiveCall:[OCMArg isNotNil] audioEnabled:YES videoEnabled:NO]); + XCTAssertEqualObjects(friend, call.caller); + XCTAssertFalse([call isOutgoing]); +} + +- (void)testCallStateChanged +{ + OCTFriend *friend = [self createFriendWithFriendNumber:111]; + + OCTCall *call = [self.callManager createCallWithFriend:friend status:OCTCallStatusActive]; + + OCTToxAVCallState state = 0; + + state |= OCTToxAVFriendCallStateAcceptingAudio; + state |= OCTToxAVFriendCallStateAcceptingVideo; + + [self.callManager toxAV:nil callStateChanged:state friendNumber:111]; + + call = [self.callManager getCurrentCallForFriendNumber:111]; + + XCTAssertTrue(call.friendAcceptingAudio); + XCTAssertTrue(call.friendAcceptingVideo); + XCTAssertFalse(call.friendSendingAudio); + XCTAssertFalse(call.friendSendingVideo); +} + +- (void)testReceiveAudio +{ + OCTToxAVPCMData pcm[] = { 1, 2, 3, 4}; + + [self.callManager toxAV:nil receiveAudio:pcm sampleCount:4 channels:2 sampleRate:55 friendNumber:123]; + + OCMVerify([self.mockedAudioEngine provideAudioFrames:pcm sampleCount:4 channels:2 sampleRate:55 fromFriend:123]); +} + +- (void)testReceiveVideo +{ + OCTToxAVVideoHeight height = 1920; + OCTToxAVVideoHeight width = 1080; + OCTToxAVPlaneData y[] = {5, 5, 6, 8}; + OCTToxAVPlaneData u[] = {5, 5, 6, 8}; + OCTToxAVPlaneData v[] = {5, 5, 6, 8}; + OCTToxAVStrideData yStride = 44; + OCTToxAVStrideData uStride = 45; + OCTToxAVStrideData vStride = 46; + + [self.callManager toxAV:nil + receiveVideoFrameWithWidth:width + height:height + yPlane:y + uPlane:u + vPlane:v + yStride:yStride + uStride:uStride + vStride:vStride + friendNumber:444]; + + OCMVerify([self.mockedVideoEngine receiveVideoFrameWithWidth:width + height:height + yPlane:y + uPlane:u + vPlane:v + yStride:yStride + uStride:uStride + vStride:vStride + friendNumber:444]); +} + +#pragma mark Test helper methods + +- (OCTFriend *)createFriendWithFriendNumber:(OCTToxFriendNumber)friendNumber +{ + OCTFriend *friend = [super createFriendWithFriendNumber:friendNumber]; + friend.friendNumber = friendNumber; + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:friendNumber error:nil]).andReturn(publicKey); + + return friend; +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTSubmanagerChatsImplTests.m b/local_pod_repo/objcTox/Tests/OCTSubmanagerChatsImplTests.m new file mode 100644 index 0000000..606df0d --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTSubmanagerChatsImplTests.m @@ -0,0 +1,486 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import + +#import "OCTRealmTests.h" + +#import "OCTSubmanagerChatsImpl.h" +#import "OCTRealmManager.h" +#import "OCTTox.h" +#import "OCTMessageAbstract.h" +#import "OCTMessageText.h" + +@interface OCTSubmanagerChatsImplTests : OCTRealmTests + +@property (strong, nonatomic) OCTSubmanagerChatsImpl *submanager; +@property (strong, nonatomic) NSNotificationCenter *notificationCenter; +@property (strong, nonatomic) id dataSource; +@property (strong, nonatomic) id tox; + +@end + +@implementation OCTSubmanagerChatsImplTests + +- (void)setUp +{ + [super setUp]; + self.notificationCenter = [[NSNotificationCenter alloc] init]; + + self.dataSource = OCMProtocolMock(@protocol(OCTSubmanagerDataSource)); + OCMStub([self.dataSource managerGetNotificationCenter]).andReturn(self.notificationCenter); + + OCMStub([self.dataSource managerGetRealmManager]).andReturn(self.realmManager); + + self.tox = OCMClassMock([OCTTox class]); + OCMStub([self.dataSource managerGetTox]).andReturn(self.tox); + + self.submanager = [OCTSubmanagerChatsImpl new]; + self.submanager.dataSource = self.dataSource; + [self.submanager configure]; +} + +- (void)tearDown +{ + self.dataSource = nil; + self.tox = nil; + self.submanager = nil; + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testGetOrCreateChatWithFriend +{ + OCTFriend *friend = [self createFriendWithFriendNumber:5]; + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + OCTChat *first = [self.realmManager getOrCreateChatWithFriend:friend]; + OCTChat *second = [self.realmManager getOrCreateChatWithFriend:friend]; + + XCTAssertEqualObjects(first, second); + XCTAssertEqualObjects([first.friends firstObject], friend); +} + +- (void)testRemoveMessages +{ + XCTestExpectation *expect = [self expectationWithDescription:@""]; + [self.notificationCenter addObserverForName:kOCTScheduleFileTransferCleanupNotification object:nil queue:nil usingBlock:^(NSNotification *note) { + [expect fulfill]; + }]; + + NSArray *messages = [NSArray new]; + OCMExpect([self.realmManager removeMessages:messages]); + + [self.submanager removeMessages:messages]; + + [self waitForExpectationsWithTimeout:0.0 handler:nil]; + OCMVerifyAll((id)self.realmManager); +} + +- (void)testRemoveMessagesWithChat +{ + XCTestExpectation *expect = [self expectationWithDescription:@""]; + [self.notificationCenter addObserverForName:kOCTScheduleFileTransferCleanupNotification object:nil queue:nil usingBlock:^(NSNotification *note) { + [expect fulfill]; + }]; + + OCTChat *chat = [OCTChat new]; + + OCMExpect([self.realmManager removeAllMessagesInChat:chat removeChat:YES]); + + [self.submanager removeAllMessagesInChat:chat removeChat:YES]; + + [self waitForExpectationsWithTimeout:0.0 handler:nil]; + OCMVerifyAll((id)self.realmManager); +} + +- (void)testSendMessageToChatSuccess +{ + id message = OCMClassMock([OCTMessageAbstract class]); + + id friend = OCMClassMock([OCTFriend class]); + OCMStub([friend friendNumber]).andReturn(5); + NSArray *friends = @[friend]; + + id chat = OCMClassMock([OCTChat class]); + OCMStub([chat friends]).andReturn(friends); + + OCMStub([self.tox sendMessageWithFriendNumber:5 + type:OCTToxMessageTypeAction + message:@"text" + error:[OCMArg anyObjectRef]]).andReturn(7); + OCMExpect([self.realmManager addMessageWithText:@"text" + type:OCTToxMessageTypeAction + chat:chat + sender:nil + messageId:7]).andReturn(message); + + XCTestExpectation *expectation = [self expectationWithDescription:@""]; + + [self.submanager sendMessageToChat:chat text:@"text" type:OCTToxMessageTypeAction successBlock:^(OCTMessageAbstract *theMessage) { + OCMVerifyAll((id)self.realmManager); + XCTAssertEqualObjects(message, theMessage); + [expectation fulfill]; + + } failureBlock:^(NSError *error) { + XCTAssertTrue(false, @"This block shouldn't be called"); + }]; + + [self waitForExpectationsWithTimeout:0.2 handler:nil]; +} + +- (void)testSendMessageToChatFailure +{ + id friend = OCMClassMock([OCTFriend class]); + OCMStub([friend friendNumber]).andReturn(5); + NSArray *friends = @[friend]; + + id chat = OCMClassMock([OCTChat class]); + OCMStub([chat friends]).andReturn(friends); + + NSError *error2 = OCMClassMock([NSError class]); + + OCMStub([self.tox sendMessageWithFriendNumber:5 + type:OCTToxMessageTypeAction + message:@"text" + error:[OCMArg setTo:error2]]).andReturn(0); + + + XCTestExpectation *expectation = [self expectationWithDescription:@""]; + + [self.submanager sendMessageToChat:chat text:@"text" type:OCTToxMessageTypeAction successBlock:^(OCTMessageAbstract *theMessage) { + XCTAssertTrue(false, @"This block shouldn't be called"); + + } failureBlock:^(NSError *error) { + OCMVerifyAll((id)self.realmManager); + XCTAssertEqualObjects(error, error2); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:0.2 handler:nil]; +} + +- (void)testSendMessageToChatFauxEnabled +{ + OCMStub([self.dataSource managerUseFauxOfflineMessaging]).andReturn(YES); + + id message = OCMClassMock([OCTMessageAbstract class]); + + id friend = OCMClassMock([OCTFriend class]); + OCMStub([friend friendNumber]).andReturn(5); + NSArray *friends = @[friend]; + + id chat = OCMClassMock([OCTChat class]); + OCMStub([chat friends]).andReturn(friends); + + NSError *error2 = OCMClassMock([NSError class]); + OCMStub(error2.code).andReturn(OCTToxErrorFriendSendMessageFriendNotConnected); + + OCMStub([self.tox sendMessageWithFriendNumber:5 + type:OCTToxMessageTypeAction + message:@"text" + error:[OCMArg setTo:error2]]).andReturn(0); + OCMExpect([self.realmManager addMessageWithText:@"text" + type:OCTToxMessageTypeAction + chat:chat + sender:nil + messageId:-1]).andReturn(message); + + XCTestExpectation *expectation = [self expectationWithDescription:@""]; + + [self.submanager sendMessageToChat:chat text:@"text" type:OCTToxMessageTypeAction successBlock:^(OCTMessageAbstract *theMessage) { + OCMVerifyAll((id)self.realmManager); + XCTAssertEqualObjects(message, theMessage); + [expectation fulfill]; + + } failureBlock:^(NSError *error) { + XCTAssertTrue(false, @"This block shouldn't be called"); + }]; + + [self waitForExpectationsWithTimeout:0.2 handler:nil]; +} + +- (void)testSendMessageToChatFauxDisabled +{ + OCMStub([self.dataSource managerUseFauxOfflineMessaging]).andReturn(NO); + + id friend = OCMClassMock([OCTFriend class]); + OCMStub([friend friendNumber]).andReturn(5); + NSArray *friends = @[friend]; + + id chat = OCMClassMock([OCTChat class]); + OCMStub([chat friends]).andReturn(friends); + + NSError *error2 = OCMClassMock([NSError class]); + OCMStub(error2.code).andReturn(OCTToxErrorFriendSendMessageFriendNotConnected); + + OCMStub([self.tox sendMessageWithFriendNumber:5 + type:OCTToxMessageTypeAction + message:@"text" + error:[OCMArg setTo:error2]]).andReturn(0); + + XCTestExpectation *expectation = [self expectationWithDescription:@""]; + + [self.submanager sendMessageToChat:chat text:@"text" type:OCTToxMessageTypeAction successBlock:^(OCTMessageAbstract *theMessage) { + XCTAssertTrue(false, @"This block shouldn't be called"); + + } failureBlock:^(NSError *error) { + OCMVerifyAll((id)self.realmManager); + XCTAssertEqualObjects(error, error2); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:0.2 handler:nil]; +} + +- (void)testSetIsTyping +{ + id friend = OCMClassMock([OCTFriend class]); + OCMStub([friend friendNumber]).andReturn(5); + NSArray *friends = @[friend]; + + id chat = OCMClassMock([OCTChat class]); + OCMStub([chat friends]).andReturn(friends); + + NSError *error; + NSError *error2 = OCMClassMock([NSError class]); + + OCMExpect([self.tox setUserIsTyping:YES forFriendNumber:5 error:[OCMArg setTo:error2]]).andReturn(NO); + + XCTAssertFalse([self.submanager setIsTyping:YES inChat:chat error:&error]); + + XCTAssertEqual(error, error2); + OCMVerifyAll(self.tox); +} + +- (void)testResendUndeliveredMessages +{ + NSArray * (^createMessages)(OCTChat *, OCTToxMessageId) = ^(OCTChat *chat, OCTToxMessageId number) { + NSMutableArray *array = [NSMutableArray new]; + + for (OCTToxMessageId index = 0; index < number; index++) { + BOOL outgoing = index % 2; + OCTMessageAbstract *message = [self createTextMessageInChat:chat outgoing:outgoing messageId:index]; + [array addObject:message]; + } + + return [array copy]; + }; + + OCTToxFriendNumber friendNumber = 1; + + OCTFriend *friend1 = [self createFriendWithFriendNumber:friendNumber++]; + OCTFriend *friend2 = [self createFriendWithFriendNumber:friendNumber++]; + + NSString *publicKey1 = friend1.publicKey; + NSString *publicKey2 = friend2.publicKey; + + OCMStub([self.tox publicKeyFromFriendNumber:friend1.friendNumber error:nil]).andReturn(publicKey1); + OCMStub([self.tox publicKeyFromFriendNumber:friend2.friendNumber error:nil]).andReturn(publicKey2); + + OCTChat *chat1 = [self createChatWithFriend:friend1]; + OCTChat *chat2 = [self createChatWithFriend:friend2]; + + NSArray *messages1 = createMessages(chat1, 10); + NSArray *messages2 = createMessages(chat2, 2); + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:chat1]; + [self.realmManager.realm addObject:chat2]; + [self.realmManager.realm addObjects:messages1]; + [self.realmManager.realm addObjects:messages2]; + [self.realmManager.realm commitWriteTransaction]; + + OCMStub([self.tox sendMessageWithFriendNumber:1 type:OCTToxMessageTypeNormal message:@"1" error:[OCMArg anyObjectRef]]).andReturn(101); + OCMStub([self.tox sendMessageWithFriendNumber:1 type:OCTToxMessageTypeNormal message:@"3" error:[OCMArg anyObjectRef]]).andReturn(103); + OCMStub([self.tox sendMessageWithFriendNumber:1 type:OCTToxMessageTypeNormal message:@"5" error:[OCMArg anyObjectRef]]).andReturn(105); + OCMStub([self.tox sendMessageWithFriendNumber:1 type:OCTToxMessageTypeNormal message:@"7" error:[OCMArg anyObjectRef]]).andReturn(107); + OCMStub([self.tox sendMessageWithFriendNumber:1 type:OCTToxMessageTypeNormal message:@"9" error:[OCMArg anyObjectRef]]).andReturn(109); + + [self.realmManager.realm beginWriteTransaction]; + friend1.connectionStatus = OCTToxConnectionStatusUDP; + friend1.isConnected = YES; + [self.realmManager.realm commitWriteTransaction]; + + [self.notificationCenter postNotificationName:kOCTFriendConnectionStatusChangeNotification object:friend1]; + +#define VERIFY_MESSAGE(__array, __index, __messageId, __delivered) \ + { \ + NSString *identifier = [__array[__index] uniqueIdentifier]; \ + OCTMessageAbstract *message = [self.realmManager objectWithUniqueIdentifier:identifier class:[OCTMessageAbstract class]]; \ + XCTAssertEqual(message.messageText.messageId, __messageId); \ + XCTAssertEqual(message.messageText.isDelivered, __delivered); \ + } + + XCTestExpectation *expectation = [self expectationWithDescription:@""]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + VERIFY_MESSAGE(messages1, 0, 0, NO); + VERIFY_MESSAGE(messages1, 1, 101, NO); + VERIFY_MESSAGE(messages1, 2, 2, NO); + VERIFY_MESSAGE(messages1, 3, 103, NO); + VERIFY_MESSAGE(messages1, 4, 4, NO); + VERIFY_MESSAGE(messages1, 5, 105, NO); + VERIFY_MESSAGE(messages1, 6, 6, NO); + VERIFY_MESSAGE(messages1, 7, 107, NO); + VERIFY_MESSAGE(messages1, 8, 8, NO); + VERIFY_MESSAGE(messages1, 9, 109, NO); + + VERIFY_MESSAGE(messages2, 0, 0, NO); + VERIFY_MESSAGE(messages2, 1, 1, NO); + + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:0.3 handler:nil]; + + + // Deliver some messages, then resend all left again. + + [self.submanager tox:self.tox messageDelivered:101 friendNumber:1]; + [self.submanager tox:self.tox messageDelivered:103 friendNumber:1]; + [self.submanager tox:self.tox messageDelivered:105 friendNumber:1]; + + { + OCTMessageAbstract *message; + + [self.realmManager.realm beginWriteTransaction]; + + message = messages1[7]; + message.messageText.text = @"107"; + message = messages1[9]; + message.messageText.text = @"109"; + + [self.realmManager.realm commitWriteTransaction]; + } + + OCMStub([self.tox sendMessageWithFriendNumber:1 type:OCTToxMessageTypeNormal message:@"107" error:[OCMArg anyObjectRef]]).andReturn(207); + OCMStub([self.tox sendMessageWithFriendNumber:1 type:OCTToxMessageTypeNormal message:@"109" error:[OCMArg anyObjectRef]]).andReturn(209); + + [self.notificationCenter postNotificationName:kOCTFriendConnectionStatusChangeNotification object:friend1]; + + XCTestExpectation *expectation2 = [self expectationWithDescription:@""]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + VERIFY_MESSAGE(messages1, 0, 0, NO); + VERIFY_MESSAGE(messages1, 1, 101, YES); + VERIFY_MESSAGE(messages1, 2, 2, NO); + VERIFY_MESSAGE(messages1, 3, 103, YES); + VERIFY_MESSAGE(messages1, 4, 4, NO); + VERIFY_MESSAGE(messages1, 5, 105, YES); + VERIFY_MESSAGE(messages1, 6, 6, NO); + VERIFY_MESSAGE(messages1, 7, 207, NO); + VERIFY_MESSAGE(messages1, 8, 8, NO); + VERIFY_MESSAGE(messages1, 9, 209, NO); + + VERIFY_MESSAGE(messages2, 0, 0, NO); + VERIFY_MESSAGE(messages2, 1, 1, NO); + + [expectation2 fulfill]; + }); + + [self waitForExpectationsWithTimeout:0.3 handler:nil]; +} + +#pragma mark - OCTToxDelegate + +- (void)testFriendMessage +{ + OCTFriend *friend = [self createFriendWithFriendNumber:5]; + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:friend.friendNumber error:nil]).andReturn(publicKey); + + OCTChat *chat = [OCTChat new]; + [chat.friends addObject:friend]; + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:chat]; + [self.realmManager.realm commitWriteTransaction]; + + [self.submanager tox:nil friendMessage:@"message" type:OCTToxMessageTypeAction friendNumber:5]; + + RLMResults *results = [OCTMessageAbstract allObjectsInRealm:self.realmManager.realm]; + XCTAssertEqual(results.count, 1); + + OCTMessageAbstract *message = [results firstObject]; + XCTAssertEqualObjects(message.senderUniqueIdentifier, friend.uniqueIdentifier); + XCTAssertEqualObjects(message.chatUniqueIdentifier, chat.uniqueIdentifier); + XCTAssertNotNil(message.messageText); + XCTAssertEqualObjects(message.messageText.text, @"message"); + XCTAssertEqual(message.messageText.type, OCTToxMessageTypeAction); +} + +- (void)testMessageDelivered +{ + OCTFriend *friend = [self createFriendWithFriendNumber:5]; + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:friend.friendNumber error:nil]).andReturn(publicKey); + + OCTChat *chat = [OCTChat new]; + [chat.friends addObject:friend]; + + OCTMessageAbstract *message = [OCTMessageAbstract new]; + message.chatUniqueIdentifier = chat.uniqueIdentifier; + message.dateInterval = [[NSDate date] timeIntervalSince1970]; + message.messageText = [OCTMessageText new]; + message.messageText.text = @""; + message.messageText.messageId = 10; + message.messageText.isDelivered = NO; + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm addObject:chat]; + [self.realmManager.realm addObject:message]; + [self.realmManager.realm commitWriteTransaction]; + + [self.submanager tox:self.tox messageDelivered:10 friendNumber:5]; + + XCTAssertTrue(message.messageText.isDelivered); + + OCTMessageAbstract *anotherMessageSameId = [OCTMessageAbstract new]; + anotherMessageSameId.chatUniqueIdentifier = chat.uniqueIdentifier; + anotherMessageSameId.dateInterval = [[NSDate date] timeIntervalSince1970]; + anotherMessageSameId.messageText = [OCTMessageText new]; + anotherMessageSameId.messageText.text = @""; + anotherMessageSameId.messageText.messageId = 10; + anotherMessageSameId.messageText.isDelivered = NO; + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:anotherMessageSameId]; + [self.realmManager.realm commitWriteTransaction]; + + [self.submanager tox:self.tox messageDelivered:10 friendNumber:5]; + + XCTAssertTrue(anotherMessageSameId.messageText.isDelivered); +} + +- (OCTChat *)createChatWithFriend:(OCTFriend *)friend +{ + OCTChat *chat = [OCTChat new]; + [chat.friends addObject:friend]; + + return chat; +} + +- (OCTMessageAbstract *)createTextMessageInChat:(OCTChat *)chat outgoing:(BOOL)outgoing messageId:(OCTToxMessageId)messageId +{ + OCTMessageAbstract *message = [OCTMessageAbstract new]; + message.chatUniqueIdentifier = chat.uniqueIdentifier; + message.messageText = [OCTMessageText new]; + message.messageText.text = [NSString stringWithFormat:@"%d", messageId]; + message.messageText.messageId = messageId; + + if (! outgoing) { + OCTFriend *friend = chat.friends.lastObject; + message.senderUniqueIdentifier = friend.uniqueIdentifier; + } + + return message; +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTSubmanagerFilesImplTests.m b/local_pod_repo/objcTox/Tests/OCTSubmanagerFilesImplTests.m new file mode 100644 index 0000000..da725ad --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTSubmanagerFilesImplTests.m @@ -0,0 +1,26 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import + +@interface OCTSubmanagerFilesImplTests : XCTestCase + +@end + +@implementation OCTSubmanagerFilesImplTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTSubmanagerFriendsImplTests.m b/local_pod_repo/objcTox/Tests/OCTSubmanagerFriendsImplTests.m new file mode 100644 index 0000000..e748e3a --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTSubmanagerFriendsImplTests.m @@ -0,0 +1,481 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import + +#import "OCTRealmTests.h" +#import "OCTSubmanagerFriendsImpl.h" +#import "OCTSubmanagerDataSource.h" +#import "OCTTox.h" +#import "OCTFriendRequest.h" + +static const OCTToxFriendNumber kFriendNumber = 5; +static NSString *const kPublicKey = @"kPublicKey"; +static NSString *const kName = @"kName"; +static NSString *const kStatusMessage = @"kStatusMessage"; +static const OCTToxUserStatus kStatus = OCTToxUserStatusAway; +static const OCTToxConnectionStatus kConnectionStatus = OCTToxConnectionStatusUDP; +static const BOOL kIsTyping = YES; +static NSDate *sLastSeenOnline; +static NSString *const kMessage = @"kMessage"; + +@interface OCTSubmanagerFriendsImplTests : OCTRealmTests + +@property (strong, nonatomic) OCTSubmanagerFriendsImpl *submanager; +@property (strong, nonatomic) id dataSource; +@property (strong, nonatomic) id tox; + +@end + +@implementation OCTSubmanagerFriendsImplTests + +- (void)setUp +{ + [super setUp]; + + sLastSeenOnline = [NSDate date]; + + self.dataSource = OCMProtocolMock(@protocol(OCTSubmanagerDataSource)); + + OCMStub([self.dataSource managerGetRealmManager]).andReturn(self.realmManager); + + self.tox = OCMClassMock([OCTTox class]); + OCMStub([self.dataSource managerGetTox]).andReturn(self.tox); + + self.submanager = [OCTSubmanagerFriendsImpl new]; + self.submanager.dataSource = self.dataSource; +} + +- (void)tearDown +{ + self.dataSource = nil; + self.tox = nil; + self.submanager = nil; + + [super tearDown]; +} + +- (void)testConfigure +{ + OCTFriend *friend = [self createFriendWithFriendNumber:5]; + friend.status = OCTToxUserStatusBusy; + friend.isConnected = YES; + friend.connectionStatus = OCTToxConnectionStatusUDP; + friend.isTyping = YES; + + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:5 error:[OCMArg anyObjectRef]]).andReturn(publicKey); + + NSArray *array = @[@(5)]; + OCMStub([self.tox friendsArray]).andReturn(array); + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + [self.submanager configure]; + + XCTAssertEqual(friend.status, OCTToxUserStatusNone); + XCTAssertFalse(friend.isConnected); + XCTAssertEqual(friend.connectionStatus, OCTToxConnectionStatusNone); + XCTAssertFalse(friend.isTyping); +} + +- (void)testConfigure2 +{ + OCTFriend *friend = [self createFriendWithFriendNumber:99]; + // This one should be removed. + OCTFriend *unboundedFriend = [self createFriendWithFriendNumber:33]; + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm addObject:unboundedFriend]; + [self.realmManager.realm commitWriteTransaction]; + + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:99 error:[OCMArg anyObjectRef]]).andReturn(publicKey); + + NSArray *array = @[@(99), @(kFriendNumber)]; + OCMStub([self.tox friendsArray]).andReturn(array); + [self stubFriendMethodsInTox]; + + [self.submanager configure]; + + RLMResults *objects = [OCTFriend allObjectsInRealm:self.realmManager.realm]; + XCTAssertEqual(objects.count, 2); + + OCTFriend *first = objects[0]; + OCTFriend *second = objects[1]; + + XCTAssertEqualObjects(friend, first); + [self verifyFriend:second]; +} + +#pragma mark - Public + +- (void)testSendFriendRequestSuccess +{ + [self stubFriendMethodsInTox]; + + OCMExpect([self.tox addFriendWithAddress:kPublicKey message:@"message" error:nil]).andReturn(kFriendNumber); + + BOOL result = [self.submanager sendFriendRequestToAddress:kPublicKey message:@"message" error:nil]; + + XCTAssertTrue(result); + OCMVerify([self.dataSource managerSaveTox]); + OCMVerifyAll(self.tox); + + RLMResults *objects = [OCTFriend allObjectsInRealm:self.realmManager.realm]; + XCTAssertEqual(objects.count, 1); + + OCTFriend *friend = [objects firstObject]; + + [self verifyFriend:friend]; +} + +- (void)testSendFriendRequestFailure +{ + NSError *error; + NSError *error2 = [NSError new]; + + [[self.dataSource reject] managerSaveTox]; + OCMExpect([self.tox addFriendWithAddress:kPublicKey message:@"message" error:[OCMArg setTo:error2]]).andReturn(kOCTToxFriendNumberFailure); + + BOOL result = [self.submanager sendFriendRequestToAddress:kPublicKey message:@"message" error:&error]; + + XCTAssertFalse(result); + XCTAssertEqualObjects(error, error2); +} + +- (void)testApproveFriendRequestSuccess +{ + [self stubFriendMethodsInTox]; + + OCTFriendRequest *friendRequest = [OCTFriendRequest new]; + friendRequest.publicKey = kPublicKey; + friendRequest.message = @"message"; + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friendRequest]; + [self.realmManager.realm commitWriteTransaction]; + + OCMExpect([self.tox addFriendWithNoRequestWithPublicKey:kPublicKey error:nil]).andReturn(kFriendNumber); + + BOOL result = [self.submanager approveFriendRequest:friendRequest error:nil]; + + XCTAssertTrue(result); + OCMVerify([self.dataSource managerSaveTox]); + OCMVerifyAll(self.tox); + + RLMResults *objects = [OCTFriendRequest allObjectsInRealm:self.realmManager.realm]; + XCTAssertEqual(objects.count, 0); + + objects = [OCTFriend allObjectsInRealm:self.realmManager.realm]; + XCTAssertEqual(objects.count, 1); + + OCTFriend *friend = [objects firstObject]; + + [self verifyFriend:friend]; +} + +- (void)testApproveFriendRequestFailure +{ + OCTFriendRequest *friendRequest = [OCTFriendRequest new]; + friendRequest.publicKey = kPublicKey; + friendRequest.message = @"message"; + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friendRequest]; + [self.realmManager.realm commitWriteTransaction]; + + NSError *error; + NSError *error2 = [NSError new]; + + [[self.dataSource reject] managerSaveTox]; + OCMExpect([self.tox addFriendWithNoRequestWithPublicKey:kPublicKey error:[OCMArg setTo:error2]]).andReturn(kOCTToxFriendNumberFailure); + + BOOL result = [self.submanager approveFriendRequest:friendRequest error:&error]; + + XCTAssertFalse(result); + XCTAssertEqualObjects(error, error2); + + RLMResults *objects = [OCTFriendRequest allObjectsInRealm:self.realmManager.realm]; + XCTAssertEqual(objects.count, 1); + + XCTAssertEqualObjects(friendRequest, [objects firstObject]); +} + +- (void)testRemoveFriendRequest +{ + OCTFriendRequest *friendRequest = [OCTFriendRequest new]; + friendRequest.publicKey = kPublicKey; + friendRequest.message = @"message"; + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friendRequest]; + [self.realmManager.realm commitWriteTransaction]; + + [self.submanager removeFriendRequest:friendRequest]; + + RLMResults *objects = [OCTFriendRequest allObjectsInRealm:self.realmManager.realm]; + XCTAssertEqual(objects.count, 0); +} + +- (void)testRemoveFriendSuccess +{ + OCTFriend *friend = [self createFriendWithFriendNumber:kFriendNumber]; + + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(publicKey); + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + OCMExpect([self.tox deleteFriendWithFriendNumber:kFriendNumber error:nil]).andReturn(YES); + + BOOL result = [self.submanager removeFriend:friend error:nil]; + + XCTAssertTrue(result); + OCMVerify([self.dataSource managerSaveTox]); + + RLMResults *objects = [OCTFriend allObjectsInRealm:self.realmManager.realm]; + XCTAssertEqual(objects.count, 0); +} + +- (void)testRemoveFriendFailure +{ + OCTFriend *friend = [self createFriendWithFriendNumber:kFriendNumber]; + + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(publicKey); + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + NSError *error; + NSError *error2 = [NSError new]; + + [[self.dataSource reject] managerSaveTox]; + OCMExpect([self.tox deleteFriendWithFriendNumber:kFriendNumber error:[OCMArg setTo:error2]]).andReturn(NO); + + BOOL result = [self.submanager removeFriend:friend error:&error]; + + XCTAssertFalse(result); + + RLMResults *objects = [OCTFriend allObjectsInRealm:self.realmManager.realm]; + XCTAssertEqual(objects.count, 1); + XCTAssertEqualObjects([objects firstObject], friend); +} + +#pragma mark - OCTToxDelegate + +- (void)testFriendRequestWithMessage +{ + [self.submanager tox:self.tox friendRequestWithMessage:kMessage publicKey:kPublicKey]; + + RLMResults *objects = [OCTFriendRequest allObjectsInRealm:self.realmManager.realm]; + XCTAssertEqual(objects.count, 1); + + OCTFriendRequest *request = [objects firstObject]; + XCTAssertEqualObjects(request.publicKey, kPublicKey); + XCTAssertEqualObjects(request.message, kMessage); + XCTAssertTrue(([[NSDate date] timeIntervalSince1970] - request.dateInterval) < 0.1); +} + +- (void)testFriendRequestWithMessageDuplicate +{ + // trying to add same friend request twice + [self.submanager tox:self.tox friendRequestWithMessage:kMessage publicKey:kPublicKey]; + [self.submanager tox:self.tox friendRequestWithMessage:kMessage publicKey:kPublicKey]; + + RLMResults *objects = [OCTFriendRequest allObjectsInRealm:self.realmManager.realm]; + XCTAssertEqual(objects.count, 1); + + OCTFriendRequest *request = [objects firstObject]; + XCTAssertEqualObjects(request.publicKey, kPublicKey); + XCTAssertEqualObjects(request.message, kMessage); + XCTAssertTrue(([[NSDate date] timeIntervalSince1970] - request.dateInterval) < 0.1); +} + +- (void)testFriendRequestWithMessageFriendExists +{ + OCTFriend *friend = [self createFriendWithFriendNumber:kFriendNumber]; + friend.publicKey = kPublicKey; + friend.status = OCTToxUserStatusBusy; + friend.isConnected = YES; + friend.connectionStatus = OCTToxConnectionStatusUDP; + friend.isTyping = YES; + + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(publicKey); + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + [self.submanager tox:self.tox friendRequestWithMessage:kMessage publicKey:kPublicKey]; + + RLMResults *objects = [OCTFriendRequest allObjectsInRealm:self.realmManager.realm]; + XCTAssertEqual(objects.count, 0); +} + +- (void)testFriendNameUpdate +{ + OCTFriend *friend = [self createFriendWithFriendNumber:kFriendNumber]; + friend.publicKey = kPublicKey; + friend.nickname = kPublicKey; + + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(publicKey); + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + [self.submanager tox:self.tox friendNameUpdate:@"" friendNumber:kFriendNumber]; + XCTAssertEqualObjects(friend.name, @""); + XCTAssertEqualObjects(friend.nickname, kPublicKey); + + [self.submanager tox:self.tox friendNameUpdate:kName friendNumber:kFriendNumber]; + XCTAssertEqualObjects(friend.name, kName); + XCTAssertEqualObjects(friend.nickname, kName); +} + +- (void)testStatusMessageUpdate +{ + OCTFriend *friend = [self createFriendWithFriendNumber:kFriendNumber]; + + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(publicKey); + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + [self.submanager tox:self.tox friendStatusMessageUpdate:kStatusMessage friendNumber:kFriendNumber]; + XCTAssertEqualObjects(friend.statusMessage, kStatusMessage); +} + +- (void)testStatusUpdate +{ + OCTFriend *friend = [self createFriendWithFriendNumber:kFriendNumber]; + + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(publicKey); + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + [self.submanager tox:self.tox friendStatusUpdate:kStatus friendNumber:kFriendNumber]; + XCTAssertEqual(friend.status, kStatus); +} + +- (void)testLastSeenDate +{ + OCTFriend *friend = [self createFriendWithFriendNumber:kFriendNumber]; + friend.lastSeenOnlineInterval = 0; + + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(publicKey); + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + XCTAssertNil([friend lastSeenOnline]); + + [self.submanager tox:self.tox friendConnectionStatusChanged:OCTToxConnectionStatusTCP friendNumber:kFriendNumber]; + + XCTAssertNil([friend lastSeenOnline]); + XCTAssertEqual(friend.lastSeenOnlineInterval, 0); + + [self stubFriendMethodsInTox]; + [self.submanager tox:self.tox friendConnectionStatusChanged:OCTToxConnectionStatusNone friendNumber:kFriendNumber]; + XCTAssertEqual(friend.lastSeenOnlineInterval, [sLastSeenOnline timeIntervalSince1970]); +} + +- (void)testIsTypingUpdate +{ + OCTFriend *friend = [self createFriendWithFriendNumber:kFriendNumber]; + + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(publicKey); + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + [self.submanager tox:self.tox friendIsTypingUpdate:kIsTyping friendNumber:kFriendNumber]; + XCTAssertEqual(friend.isTyping, kIsTyping); +} + +- (void)testFriendConnectionStatusChanged +{ + OCTFriend *friend = [self createFriendWithFriendNumber:kFriendNumber]; + + NSString *publicKey = friend.publicKey; + OCMStub([self.tox publicKeyFromFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(publicKey); + + [self.realmManager.realm beginWriteTransaction]; + [self.realmManager.realm addObject:friend]; + [self.realmManager.realm commitWriteTransaction]; + + [self.submanager tox:self.tox friendConnectionStatusChanged:OCTToxConnectionStatusUDP friendNumber:kFriendNumber]; + XCTAssertEqual(friend.connectionStatus, OCTToxConnectionStatusUDP); + XCTAssertTrue(friend.isConnected); + + [self.submanager tox:self.tox friendConnectionStatusChanged:OCTToxConnectionStatusNone friendNumber:kFriendNumber]; + XCTAssertEqual(friend.connectionStatus, OCTToxConnectionStatusNone); + XCTAssertFalse(friend.isConnected); + + [self.submanager tox:self.tox friendConnectionStatusChanged:OCTToxConnectionStatusTCP friendNumber:kFriendNumber]; + XCTAssertEqual(friend.connectionStatus, OCTToxConnectionStatusTCP); + XCTAssertTrue(friend.isConnected); + + NSNotificationCenter *center = [[NSNotificationCenter alloc] init]; + OCMStub([self.dataSource managerGetNotificationCenter]).andReturn(center); + + XCTestExpectation *expect = [self expectationWithDescription:@""]; + [center addObserverForName:kOCTFriendConnectionStatusChangeNotification object:nil queue:nil usingBlock:^(NSNotification *note) { + if ([note.object friendNumber] == kFriendNumber) { + [expect fulfill]; + } + }]; + + [self.submanager tox:self.tox friendConnectionStatusChanged:OCTToxConnectionStatusUDP friendNumber:kFriendNumber]; + [self waitForExpectationsWithTimeout:0.0 handler:nil]; +} + +#pragma mark - Helper methods + +- (void)stubFriendMethodsInTox +{ + OCMStub([self.tox publicKeyFromFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(kPublicKey); + OCMStub([self.tox friendNameWithFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(kName); + OCMStub([self.tox friendStatusMessageWithFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(kStatusMessage); + OCMStub([self.tox friendStatusWithFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(kStatus); + OCMStub([self.tox friendConnectionStatusWithFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(kConnectionStatus); + OCMStub([self.tox friendGetLastOnlineWithFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(sLastSeenOnline); + OCMStub([self.tox isFriendTypingWithFriendNumber:kFriendNumber error:[OCMArg anyObjectRef]]).andReturn(kIsTyping); +} + +- (void)verifyFriend:(OCTFriend *)friend +{ + XCTAssertEqual(friend.friendNumber, kFriendNumber); + XCTAssertEqualObjects(friend.nickname, kName); + XCTAssertEqualObjects(friend.publicKey, kPublicKey); + XCTAssertEqualObjects(friend.name, kName); + XCTAssertEqualObjects(friend.statusMessage, kStatusMessage); + XCTAssertEqual(friend.status, kStatus); + XCTAssertEqual(friend.isConnected, YES); + XCTAssertEqual(friend.connectionStatus, kConnectionStatus); + XCTAssertEqual(friend.lastSeenOnlineInterval, [sLastSeenOnline timeIntervalSince1970]); + XCTAssertEqual(friend.isTyping, kIsTyping); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTSubmanagerObjectsImplTests.m b/local_pod_repo/objcTox/Tests/OCTSubmanagerObjectsImplTests.m new file mode 100644 index 0000000..cb2b474 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTSubmanagerObjectsImplTests.m @@ -0,0 +1,195 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import + +#import "OCTSubmanagerObjectsImpl.h" +#import "OCTRealmManager.h" +#import "OCTFriend.h" +#import "OCTFriendRequest.h" +#import "OCTChat.h" +#import "OCTMessageAbstract.h" +#import "OCTSettingsStorageObject.h" + +@interface OCTSubmanagerObjectsImplTests : XCTestCase + +@property (strong, nonatomic) OCTSubmanagerObjectsImpl *submanager; +@property (strong, nonatomic) id dataSource; +@property (strong, nonatomic) id realmManager; + +@end + +@implementation OCTSubmanagerObjectsImplTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. + + self.dataSource = OCMProtocolMock(@protocol(OCTSubmanagerDataSource)); + + self.realmManager = OCMClassMock([OCTRealmManager class]); + OCMStub([self.dataSource managerGetRealmManager]).andReturn(self.realmManager); + + self.submanager = [OCTSubmanagerObjectsImpl new]; + self.submanager.dataSource = self.dataSource; +} + +- (void)tearDown +{ + self.dataSource = nil; + self.realmManager = nil; + self.submanager = nil; + + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testFetchRequestByType +{ + id predicate = OCMClassMock([NSPredicate class]); + + id results1 = OCMClassMock([RLMResults class]); + id results2 = OCMClassMock([RLMResults class]); + id results3 = OCMClassMock([RLMResults class]); + id results4 = OCMClassMock([RLMResults class]); + + OCMStub([self.realmManager objectsWithClass:[OCTFriend class] + predicate:predicate]).andReturn(results1); + OCMStub([self.realmManager objectsWithClass:[OCTFriendRequest class] + predicate:predicate]).andReturn(results2); + OCMStub([self.realmManager objectsWithClass:[OCTChat class] + predicate:predicate]).andReturn(results3); + OCMStub([self.realmManager objectsWithClass:[OCTMessageAbstract class] + predicate:predicate]).andReturn(results4); + + XCTAssertEqual(results1, [self.submanager objectsForType:OCTFetchRequestTypeFriend + predicate:predicate]); + XCTAssertEqual(results2, [self.submanager objectsForType:OCTFetchRequestTypeFriendRequest + predicate:predicate]); + XCTAssertEqual(results3, [self.submanager objectsForType:OCTFetchRequestTypeChat + predicate:predicate]); + XCTAssertEqual(results4, [self.submanager objectsForType:OCTFetchRequestTypeMessageAbstract + predicate:predicate]); +} + +- (void)testGenericSettingsData +{ + NSData *data = [@"some string" dataUsingEncoding:NSUTF8StringEncoding]; + + id settingsStorage = OCMClassMock([OCTSettingsStorageObject class]); + OCMStub([settingsStorage genericSettingsData]).andReturn(data); + OCMExpect([settingsStorage setGenericSettingsData:data]); + + OCMStub([self.realmManager settingsStorage]).andReturn(settingsStorage); + + XCTAssertEqual(self.submanager.genericSettingsData, data); + self.submanager.genericSettingsData = data; + + OCMVerify(settingsStorage); +} + +- (void)testObjectWithUniqueIdentifier +{ + NSString *identifier = @"identifier"; + + id object1 = OCMClassMock([OCTObject class]); + id object2 = OCMClassMock([OCTObject class]); + id object3 = OCMClassMock([OCTObject class]); + id object4 = OCMClassMock([OCTObject class]); + + OCMStub([self.realmManager objectWithUniqueIdentifier:identifier class:[OCTFriend class]]).andReturn(object1); + OCMStub([self.realmManager objectWithUniqueIdentifier:identifier class:[OCTFriendRequest class]]).andReturn(object2); + OCMStub([self.realmManager objectWithUniqueIdentifier:identifier class:[OCTChat class]]).andReturn(object3); + OCMStub([self.realmManager objectWithUniqueIdentifier:identifier class:[OCTMessageAbstract class]]).andReturn(object4); + + XCTAssertEqual(object1, [self.submanager objectWithUniqueIdentifier:identifier forType:OCTFetchRequestTypeFriend]); + XCTAssertEqual(object2, [self.submanager objectWithUniqueIdentifier:identifier forType:OCTFetchRequestTypeFriendRequest]); + XCTAssertEqual(object3, [self.submanager objectWithUniqueIdentifier:identifier forType:OCTFetchRequestTypeChat]); + XCTAssertEqual(object4, [self.submanager objectWithUniqueIdentifier:identifier forType:OCTFetchRequestTypeMessageAbstract]); +} + +- (void)testChangeFriendNickname +{ + OCTFriend *friend = [OCTFriend new]; + + OCMStub([self.realmManager updateObject:friend withBlock:[OCMArg checkWithBlock:^BOOL (id obj) { + void (^block)(id) = obj; + block(friend); + return YES; + }]]); + + [self.submanager changeFriend:friend nickname:@"new"]; + + XCTAssertEqualObjects(friend.nickname, @"new"); +} + +- (void)testChangeFriendNicknameEmpty1 +{ + OCTFriend *friend = [OCTFriend new]; + friend.nickname = @"nickname"; + friend.name = @"name"; + friend.publicKey = @"public"; + + OCMStub([self.realmManager updateObject:friend withBlock:[OCMArg checkWithBlock:^BOOL (id obj) { + void (^block)(id) = obj; + block(friend); + return YES; + }]]); + + [self.submanager changeFriend:friend nickname:@""]; + + XCTAssertEqualObjects(friend.nickname, @"name"); +} + +- (void)testChangeFriendNicknameEmpty2 +{ + OCTFriend *friend = [OCTFriend new]; + friend.nickname = @"nickname"; + friend.name = @""; + friend.publicKey = @"public"; + + OCMStub([self.realmManager updateObject:friend withBlock:[OCMArg checkWithBlock:^BOOL (id obj) { + void (^block)(id) = obj; + block(friend); + return YES; + }]]); + + [self.submanager changeFriend:friend nickname:@""]; + + XCTAssertEqualObjects(friend.nickname, @"public"); +} + +- (void)testChangeChatEnteredText +{ + OCTChat *chat = [OCTChat new]; + + OCMStub([self.realmManager updateObject:chat withBlock:[OCMArg checkWithBlock:^BOOL (id obj) { + void (^block)(id) = obj; + block(chat); + return YES; + }]]); + + [self.submanager changeChat:chat enteredText:@"text"]; + + XCTAssertEqualObjects(chat.enteredText, @"text"); +} + +- (void)testChangeChatEnteredLastReadDateInterval +{ + OCTChat *chat = [OCTChat new]; + + OCMStub([self.realmManager updateObject:chat withBlock:[OCMArg checkWithBlock:^BOOL (id obj) { + void (^block)(id) = obj; + block(chat); + return YES; + }]]); + + [self.submanager changeChat:chat lastReadDateInterval:17]; + + XCTAssertEqual(chat.lastReadDateInterval, 17); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTSubmanagerUserImplTests.m b/local_pod_repo/objcTox/Tests/OCTSubmanagerUserImplTests.m new file mode 100644 index 0000000..ee3bc94 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTSubmanagerUserImplTests.m @@ -0,0 +1,130 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import + +#import "OCTRealmTests.h" +#import "OCTSubmanagerUserImpl.h" +#import "OCTSubmanagerDataSource.h" +#import "OCTTox.h" + +@interface OCTSubmanagerUserImplTests : OCTRealmTests + +@property (strong, nonatomic) OCTSubmanagerUserImpl *submanager; +@property (strong, nonatomic) id dataSource; +@property (strong, nonatomic) id tox; + +@end + +@implementation OCTSubmanagerUserImplTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. + self.tox = OCMClassMock([OCTTox class]); + + self.dataSource = OCMProtocolMock(@protocol(OCTSubmanagerDataSource)); + OCMStub([self.dataSource managerGetTox]).andReturn(self.tox); + OCMStub([self.dataSource managerGetRealmManager]).andReturn(self.realmManager); + + self.submanager = [OCTSubmanagerUserImpl new]; + self.submanager.dataSource = self.dataSource; +} + +- (void)tearDown +{ + self.tox = nil; + self.dataSource = nil; + self.submanager = nil; + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testConnectionStatus +{ + OCMStub([self.tox connectionStatus]).andReturn(OCTToxConnectionStatusTCP); + XCTAssertEqual([self.submanager connectionStatus], OCTToxConnectionStatusTCP); +} + +- (void)testUserAddress +{ + OCMStub([self.tox userAddress]).andReturn(@"address"); + XCTAssertEqualObjects([self.submanager userAddress], @"address"); +} + +- (void)testPublicKey +{ + OCMStub([self.tox publicKey]).andReturn(@"publicKey"); + XCTAssertEqualObjects([self.submanager publicKey], @"publicKey"); +} + +- (void)testNospam +{ + OCMStub([self.tox nospam]).andReturn(5); + XCTAssertEqual([self.submanager nospam], 5); + + self.submanager.nospam = 7; + OCMVerify([self.tox setNospam:7]); + OCMVerify([self.dataSource managerSaveTox]); +} + +- (void)testUserStatus +{ + OCMStub([self.tox userStatus]).andReturn(5); + XCTAssertEqual([self.submanager userStatus], 5); + + self.submanager.userStatus = 7; + OCMVerify([self.tox setUserStatus:7]); + OCMVerify([self.dataSource managerSaveTox]); +} + +- (void)testUserName +{ + OCMStub([self.tox userName]).andReturn(@"userName"); + XCTAssertEqualObjects([self.submanager userName], @"userName"); + + NSError *error, *error2; + OCMExpect([self.tox setNickname:@"name" error:[OCMArg setTo:error2]]).andReturn(YES); + + [self.submanager setUserName:@"name" error:&error]; + + XCTAssertEqual(error, error2); + OCMVerifyAll(self.tox); + OCMVerify([self.dataSource managerSaveTox]); +} + +- (void)testUserStatusMessage +{ + OCMStub([self.tox userStatusMessage]).andReturn(@"userStatusMessage"); + XCTAssertEqualObjects([self.submanager userStatusMessage], @"userStatusMessage"); + + NSError *error, *error2; + OCMExpect([self.tox setUserStatusMessage:@"message" error:[OCMArg setTo:error2]]).andReturn(YES); + + [self.submanager setUserStatusMessage:@"message" error:&error]; + + XCTAssertEqual(error, error2); + OCMVerifyAll(self.tox); + OCMVerify([self.dataSource managerSaveTox]); +} + +- (void)testSetUserAvatar +{ + char databytes[65536]; + NSData *data = [[NSData alloc] initWithBytes:databytes length:65536]; + XCTAssertTrue([self.submanager setUserAvatar:data error:nil]); + + XCTAssertEqualObjects([self.submanager userAvatar], data); + + char toomanybytes[85536]; + data = [[NSData alloc] initWithBytes:toomanybytes length:85536]; + XCTAssertFalse([self.submanager setUserAvatar:data error:nil]); + + XCTAssertTrue([self.submanager setUserAvatar:nil error:nil]); + XCTAssertNil([self.submanager userAvatar]); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTToxAVTests.m b/local_pod_repo/objcTox/Tests/OCTToxAVTests.m new file mode 100644 index 0000000..8c5402c --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTToxAVTests.m @@ -0,0 +1,583 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import +#import "OCTToxAV+Private.h" +#import "OCTTox+Private.h" +#import "OCTToxOptions.h" +#import "OCTCAsserts.h" + +static void *refToSelf; + +void mocked_toxav_iterate(ToxAV *toxAV); +uint32_t mocked_toxav_iteration_interval(const ToxAV *toxAV); +void mocked_toxav_kill(ToxAV *toxAV); + +bool mocked_tox_av_call_success(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_CALL *error); +bool mocked_tox_av_answer_success(ToxAV *cToxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER *error); +bool mocked_tox_av_answer_fail(ToxAV *cToxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER *error); +bool mocked_tox_av_call_fail(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_CALL *error); + +bool mocked_toxav_call_control_resume(ToxAV *toxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error); +bool mocked_toxav_call_control_cancel(ToxAV *toxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error); + +bool mocked_toxav_audio_set_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error); +bool mocked_toxav_video_set_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error); + +bool mocked_toxav_audio_send_frame(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error); +bool mocked_toxav_video_send_frame(ToxAV *toxAV, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error); + +OCTToxAVPCMData pcmTestData [] = { 5, 6, 7, 8}; +OCTToxAVPCMData *pcmPointer = pcmTestData; + +OCTToxAVPlaneData yPlaneTestData [] = {2, 3, 4, 5}; +OCTToxAVPlaneData *yPlanePointer = yPlaneTestData; +OCTToxAVPlaneData uPlaneTestData [] = {6, 7, 8, 9}; +OCTToxAVPlaneData *uPlanePointer = uPlaneTestData; +OCTToxAVPlaneData vPlaneTestData [] = {10, 11, 12, 13}; +OCTToxAVPlaneData *vPlanePointer = vPlaneTestData; +OCTToxAVPlaneData aPlaneTestData [] = {14, 15, 16, 17}; +OCTToxAVPlaneData *aPlanePointer = aPlaneTestData; + +@interface OCTToxAVTests : XCTestCase + +@property (strong, nonatomic) OCTToxAV *toxAV; +@property (strong, nonatomic) OCTTox *tox; + +@end + +@implementation OCTToxAVTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. + + refToSelf = (__bridge void *)(self); + + self.tox = [[OCTTox alloc] initWithOptions:[OCTToxOptions new] savedData:nil error:nil]; + self.toxAV = [[OCTToxAV alloc] initWithTox:self.tox error:nil]; +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + + self.tox = nil; + self.toxAV = nil; + + refToSelf = NULL; + + [super tearDown]; +} + +- (void)testInit +{ + XCTAssertNotNil(self.toxAV); +} + +- (void)testCallFriend +{ + _toxav_call = mocked_tox_av_call_success; + XCTAssertTrue([self.toxAV callFriendNumber:1234 audioBitRate:5678 videoBitRate:9101112 error:nil]); + + _toxav_call = mocked_tox_av_call_fail; + XCTAssertFalse([self.toxAV callFriendNumber:1234 audioBitRate:5678 videoBitRate:9101112 error:nil]); +} + +- (void)testAnswerCall +{ + _toxav_answer = mocked_tox_av_answer_success; + XCTAssertTrue([self.toxAV answerIncomingCallFromFriend:9876 audioBitRate:555 videoBitRate:666 error:nil]); + + _toxav_answer = mocked_tox_av_answer_fail; + XCTAssertFalse([self.toxAV answerIncomingCallFromFriend:999 audioBitRate:888 videoBitRate:777 error:nil]); +} + +- (void)testSendCallControl +{ + _toxav_call_control = mocked_toxav_call_control_resume; + XCTAssertTrue([self.toxAV sendCallControl:OCTToxAVCallControlResume toFriendNumber:12345 error:nil]); + + _toxav_call_control = mocked_toxav_call_control_cancel; + XCTAssertTrue([self.toxAV sendCallControl:OCTToxAVCallControlCancel toFriendNumber:12345 error:nil]); +} + +- (void)testStartandStop +{ + _toxav_iterate = mocked_toxav_iterate; + _toxav_iteration_interval = mocked_toxav_iteration_interval; + + [self.toxAV start]; + [self.toxAV stop]; + + _toxav_iterate = nil; + _toxav_iteration_interval = nil; +} + +- (void)testSendAudioFrame +{ + _toxav_audio_send_frame = mocked_toxav_audio_send_frame; + XCTAssertTrue([self.toxAV sendAudioFrame:pcmPointer sampleCount:6 channels:7 sampleRate:8 toFriend:5 error:nil]); +} + +- (void)testSendVideoFrame +{ + _toxav_video_send_frame = mocked_toxav_video_send_frame; + XCTAssertFalse([self.toxAV sendVideoFrametoFriend:7 width:50 height:70 + yPlane:yPlanePointer + uPlane:uPlanePointer + vPlane:vPlanePointer + error:nil]); +} + +#pragma mark Private methods + +- (void)testFillErrorInit +{ + [self.toxAV fillError:nil withCErrorInit:TOXAV_ERR_NEW_NULL]; + + NSError *error; + [self.toxAV fillError:&error withCErrorInit:TOXAV_ERR_NEW_NULL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorInitNULL); + + error = nil; + [self.toxAV fillError:&error withCErrorInit:TOXAV_ERR_NEW_MALLOC]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorInitCodeMemoryError); + + error = nil; + [self.toxAV fillError:&error withCErrorInit:TOXAV_ERR_NEW_MULTIPLE]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorInitMultiple); +} + +- (void)testFillErrorCall +{ + [self.toxAV fillError:nil withCErrorCall:TOXAV_ERR_CALL_MALLOC]; + + NSError *error; + [self.toxAV fillError:&error withCErrorCall:TOXAV_ERR_CALL_MALLOC]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorCallMalloc); + + error = nil; + [self.toxAV fillError:&error withCErrorCall:TOXAV_ERR_CALL_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorCallFriendNotFound); + + error = nil; + [self.toxAV fillError:&error withCErrorCall:TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorCallFriendNotConnected); + + error = nil; + [self.toxAV fillError:&error withCErrorCall:TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorCallAlreadyInCall); + + error = nil; + [self.toxAV fillError:&error withCErrorCall:TOXAV_ERR_CALL_INVALID_BIT_RATE]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorCallInvalidBitRate); +} + +- (void)testFillErrorAnswer +{ + [self.toxAV fillError:nil withCErrorAnswer:TOXAV_ERR_ANSWER_OK]; + + NSError *error; + [self.toxAV fillError:&error withCErrorAnswer:TOXAV_ERR_ANSWER_CODEC_INITIALIZATION]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorAnswerCodecInitialization); + + error = nil; + [self.toxAV fillError:&error withCErrorAnswer:TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorAnswerFriendNotCalling); + + error = nil; + [self.toxAV fillError:&error withCErrorAnswer:TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorAnswerFriendNotFound); + + error = nil; + [self.toxAV fillError:&error withCErrorAnswer:TOXAV_ERR_ANSWER_INVALID_BIT_RATE]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorAnswerInvalidBitRate); +} + +- (void)testFillErrorControl +{ + [self.toxAV fillError:nil withCErrorControl:TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION]; + + NSError *error; + [self.toxAV fillError:&error withCErrorControl:TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorControlFriendNotFound); + + error = nil; + [self.toxAV fillError:&error withCErrorControl:TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorControlFriendNotInCall); + + error = nil; + [self.toxAV fillError:&error withCErrorControl:TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorControlInvaldTransition); +} +- (void)testFillErrorSetBitRate +{ + [self.toxAV fillError:nil withCErrorSetBitRate:TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL]; + + NSError *error; + [self.toxAV fillError:&error withCErrorSetBitRate:TOXAV_ERR_BIT_RATE_SET_INVALID_BIT_RATE]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorSetBitRateInvalidBitRate); + + error = nil; + [self.toxAV fillError:&error withCErrorSetBitRate:TOXAV_ERR_BIT_RATE_SET_SYNC]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorSetBitRateSync); + + error = nil; + [self.toxAV fillError:&error withCErrorSetBitRate:TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorSetBitRateFriendNotFound); + + error = nil; + [self.toxAV fillError:&error withCErrorSetBitRate:TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorSetBitRateFriendNotInCall); +} + +- (void)testFillErrorSendFrame +{ + [self.toxAV fillError:nil withCErrorSendFrame:TOXAV_ERR_SEND_FRAME_NULL]; + + NSError *error; + [self.toxAV fillError:&error withCErrorSendFrame:TOXAV_ERR_SEND_FRAME_NULL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorSendFrameNull); + + error = nil; + [self.toxAV fillError:&error withCErrorSendFrame:TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorSendFrameFriendNotFound); + + error = nil; + [self.toxAV fillError:&error withCErrorSendFrame:TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorSendFrameFriendNotInCall); + + error = nil; + [self.toxAV fillError:&error withCErrorSendFrame:TOXAV_ERR_SEND_FRAME_INVALID]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorSendFrameInvalid); + + error = nil; + [self.toxAV fillError:&error withCErrorSendFrame:TOXAV_ERR_SEND_FRAME_RTP_FAILED]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxAVErrorSendFrameRTPFailed); +} + +#pragma mark Callbacks + +- (void)testReceiveCallback +{ + [self makeTestCallbackWithCallBlock:^{ + callIncomingCallback(NULL, 1, true, false, (__bridge void *)self.toxAV); + } expectBlock:^(id delegate) { + OCMExpect([self.toxAV.delegate toxAV:self.toxAV + receiveCallAudioEnabled:YES + videoEnabled:NO + friendNumber:1]); + }]; +} + +- (void)testCallStateCallback +{ + [self makeTestCallbackWithCallBlock:^{ + callStateCallback(NULL, 1, TOXAV_FRIEND_CALL_STATE_ACCEPTING_A | TOXAV_FRIEND_CALL_STATE_SENDING_A, (__bridge void *)self.toxAV); + } expectBlock:^(id delegate) { + OCTToxFriendNumber friendNumber = 1; + OCMExpect([self.toxAV.delegate toxAV:self.toxAV + callStateChanged:OCTToxAVFriendCallStateAcceptingAudio | OCTToxAVFriendCallStateSendingAudio + friendNumber:friendNumber]); + }]; + + [self makeTestCallbackWithCallBlock:^{ + callStateCallback(NULL, 1, TOXAV_FRIEND_CALL_STATE_SENDING_A, (__bridge void *)self.toxAV); + } expectBlock:^(id delegate) { + OCTToxFriendNumber friendNumber = 1; + OCMExpect([self.toxAV.delegate toxAV:self.toxAV + callStateChanged:OCTToxAVFriendCallStateSendingAudio + friendNumber:friendNumber]); + }]; + + [self makeTestCallbackWithCallBlock:^{ + callStateCallback(NULL, 1, TOXAV_FRIEND_CALL_STATE_ERROR, (__bridge void *)self.toxAV); + } expectBlock:^(id delegate) { + OCTToxFriendNumber friendNumber = 1; + OCMExpect([self.toxAV.delegate toxAV:self.toxAV + callStateChanged:OCTToxAVFriendCallStateError + friendNumber:friendNumber]); + }]; + + [self makeTestCallbackWithCallBlock:^{ + callStateCallback(NULL, 1, TOXAV_FRIEND_CALL_STATE_ACCEPTING_A | TOXAV_FRIEND_CALL_STATE_SENDING_A | TOXAV_FRIEND_CALL_STATE_SENDING_V, (__bridge void *)self.toxAV); + } expectBlock:^(id delegate) { + OCTToxFriendNumber friendNumber = 1; + OCMExpect([self.toxAV.delegate toxAV:self.toxAV + callStateChanged:OCTToxAVFriendCallStateAcceptingAudio | OCTToxAVFriendCallStateSendingAudio | OCTToxAVFriendCallStateSendingVideo + friendNumber:friendNumber]); + }]; +} + +- (void)testAudioBitRateCallback +{ + [self makeTestCallbackWithCallBlock:^{ + audioBitRateStatusCallback(NULL, 1234, 567, (__bridge void *)self.toxAV); + } expectBlock:^(id delegate) { + OCMExpect([self.toxAV.delegate toxAV:self.toxAV audioBitRateStatus:567 forFriendNumber:1234]); + }]; +} + +- (void)testVideoBitRateCallback +{ + [self makeTestCallbackWithCallBlock:^{ + videoBitRateStatusCallback(NULL, 1234, 890, (__bridge void *)self.toxAV); + } expectBlock:^(id delegate) { + OCMExpect([self.toxAV.delegate toxAV:self.toxAV videoBitRateStatus:890 forFriendNumber:1234]); + }]; +} + +- (void)testReceiveAudioCallback +{ + const int16_t pcm[] = {5, 9, 5}; + const int16_t *pointerToData = pcm; + + [self makeTestCallbackWithCallBlock:^{ + receiveAudioFrameCallback(NULL, 20, pointerToData, 4, 0, 6, (__bridge void *)self.toxAV); + } expectBlock:^(id delegate) { + OCMExpect([self.toxAV.delegate toxAV:self.toxAV + receiveAudio:pointerToData + sampleCount:4 + channels:0 + sampleRate:6 + friendNumber:20]); + }]; +} + +- (void)testReceiveVideoFrameCallback +{ + const OCTToxAVPlaneData yPlane[] = {1, 2, 3, 4, 5}; + const OCTToxAVPlaneData *yPointer = yPlane; + const OCTToxAVPlaneData uPlane[] = {4, 3, 3, 4, 5}; + const OCTToxAVPlaneData *uPointer = uPlane; + const OCTToxAVPlaneData vPlane[] = {1, 2, 5, 4, 5}; + const OCTToxAVPlaneData *vPointer = vPlane; + + [self makeTestCallbackWithCallBlock:^{ + receiveVideoFrameCallback(NULL, 123, + 999, 888, + yPointer, uPointer, vPointer, + 1, 2, 3, + (__bridge void *)self.toxAV); + } expectBlock:^(id delegate) { + OCMExpect([self.toxAV.delegate toxAV:self.toxAV + receiveVideoFrameWithWidth:999 height:888 + yPlane:yPointer uPlane:uPointer vPlane:vPointer + yStride:1 uStride:2 vStride:3 friendNumber:123]); + }]; +} + +- (void)makeTestCallbackWithCallBlock:(void (^)())callBlock expectBlock:(void (^)(id delegate))expectBlock +{ + NSParameterAssert(callBlock); + NSParameterAssert(expectBlock); + + self.toxAV.delegate = OCMProtocolMock(@protocol(OCTToxAVDelegate)); + expectBlock(self.toxAV.delegate); + + callBlock(); + + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + OCMVerifyAll((id)self.toxAV.delegate); + +} + +@end + +#pragma mark - Mocked toxav methods + +void mocked_toxav_iterate(ToxAV *cToxAV) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + + CCCAssertTrue(toxAV.toxAV == cToxAV); +} + +uint32_t mocked_toxav_iteration_interval(const ToxAV *cToxAV) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + + CCCAssertTrue(toxAV.toxAV == cToxAV); + + return 200; +} + +void mocked_toxav_kill(ToxAV *cToxAV) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + + CCCAssertTrue(toxAV.toxAV == cToxAV); +} + +bool mocked_tox_av_call_success(ToxAV *cToxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_CALL *error) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + + CCCAssertTrue(toxAV.toxAV == cToxAV); + + CCCAssertEqual(1234, friend_number); + CCCAssertEqual(5678, audio_bit_rate); + CCCAssertEqual(9101112, video_bit_rate); + + return true; +} + +bool mocked_tox_av_call_fail(ToxAV *cToxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_CALL *error) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + + CCCAssertTrue(toxAV.toxAV == cToxAV); + + CCCAssertEqual(1234, friend_number); + CCCAssertEqual(5678, audio_bit_rate); + CCCAssertEqual(9101112, video_bit_rate); + + return false; +} + +bool mocked_tox_av_answer_success(ToxAV *cToxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER *error) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + CCCAssertTrue(toxAV.toxAV == cToxAV); + + CCCAssertEqual(9876, friend_number); + CCCAssertEqual(555, audio_bit_rate); + CCCAssertEqual(666, video_bit_rate); + + return true; +} + +bool mocked_tox_av_answer_fail(ToxAV *cToxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER *error) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + CCCAssertTrue(toxAV.toxAV == cToxAV); + + CCCAssertEqual(999, friend_number); + CCCAssertEqual(888, audio_bit_rate); + CCCAssertEqual(777, video_bit_rate); + + return false; +} + + +bool mocked_toxav_call_control_resume(ToxAV *cToxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + + CCCAssertTrue(toxAV.toxAV == cToxAV); + + CCCAssertEqual(friend_number, 12345); + CCCAssertEqual(control, TOXAV_CALL_CONTROL_RESUME); + + return true; +} + +bool mocked_toxav_call_control_cancel(ToxAV *cToxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + + CCCAssertTrue(toxAV.toxAV == cToxAV); + + CCCAssertEqual(friend_number, 12345); + CCCAssertEqual(control, TOXAV_CALL_CONTROL_CANCEL); + + return true; +} + +bool mocked_toxav_audio_set_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + + CCCAssertTrue(toxAV.toxAV == av); + + CCCAssertEqual(5, friend_number); + CCCAssertEqual(10, bit_rate); + + return false; +} + +bool mocked_toxav_video_set_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + + CCCAssertTrue(toxAV.toxAV == av); + + CCCAssertEqual(5, friend_number); + CCCAssertEqual(11, bit_rate); + + return false; +} + +bool mocked_toxav_audio_send_frame(ToxAV *cToxAV, uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + + CCCAssertTrue(toxAV.toxAV == cToxAV); + + CCCAssertEqual(pcmPointer, pcm); + CCCAssertEqual(5, friend_number); + CCCAssertEqual(6, sample_count); + CCCAssertEqual(7, channels); + CCCAssertEqual(8, sampling_rate); + + return true; +} + +bool mocked_toxav_video_send_frame(ToxAV *cToxAV, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error) +{ + OCTToxAV *toxAV = [(__bridge OCTToxAVTests *)refToSelf toxAV]; + + CCCAssertTrue(toxAV.toxAV == cToxAV); + + CCCAssertEqual(50, width); + CCCAssertEqual(70, height); + CCCAssertEqual(yPlanePointer, y); + CCCAssertEqual(uPlanePointer, u); + CCCAssertEqual(vPlanePointer, v); + CCCAssertEqual(7, friend_number); + + return false; +} + +bool mocked_toxav_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch) +{ + CCCAssertEqual(999, major); + CCCAssertEqual(888, minor); + CCCAssertEqual(777, patch); + return false; +} diff --git a/local_pod_repo/objcTox/Tests/OCTToxEncryptSaveTests.m b/local_pod_repo/objcTox/Tests/OCTToxEncryptSaveTests.m new file mode 100644 index 0000000..542435d --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTToxEncryptSaveTests.m @@ -0,0 +1,77 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTToxEncryptSave.h" + +@interface OCTToxEncryptSaveTests : XCTestCase + +@end + +@implementation OCTToxEncryptSaveTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testClassMethods +{ + NSData *data = [@"data" dataUsingEncoding:NSUTF8StringEncoding]; + + XCTAssertFalse([OCTToxEncryptSave isDataEncrypted:data]); + + NSData *encrypted = [OCTToxEncryptSave encryptData:data withPassphrase:@"password123" error:nil]; + + XCTAssertTrue([OCTToxEncryptSave isDataEncrypted:encrypted]); + XCTAssertFalse([data isEqualToData:encrypted]); + + NSData *decrypted = [OCTToxEncryptSave decryptData:encrypted withPassphrase:@"password123" error:nil]; + + XCTAssertFalse([OCTToxEncryptSave isDataEncrypted:decrypted]); + XCTAssertTrue([data isEqualToData:decrypted]); + XCTAssertFalse([encrypted isEqualToData:decrypted]); +} + +- (void)testInstanceMethods +{ + OCTToxEncryptSave *save = [[OCTToxEncryptSave alloc] initWithPassphrase:@"p@s$" toxData:nil error:nil]; + + XCTAssertNotNil(save); + + NSData *data = [@"data" dataUsingEncoding:NSUTF8StringEncoding]; + + XCTAssertFalse([OCTToxEncryptSave isDataEncrypted:data]); + + NSData *encrypted = [save encryptData:data error:nil]; + + XCTAssertTrue([OCTToxEncryptSave isDataEncrypted:encrypted]); + XCTAssertFalse([data isEqualToData:encrypted]); + + NSData *decrypted = [save decryptData:encrypted error:nil]; + + XCTAssertFalse([OCTToxEncryptSave isDataEncrypted:decrypted]); + XCTAssertTrue([data isEqualToData:decrypted]); + XCTAssertFalse([encrypted isEqualToData:decrypted]); + + save = nil; + + OCTToxEncryptSave *anotherSave = [[OCTToxEncryptSave alloc] initWithPassphrase:@"p@s$" toxData:encrypted error:nil]; + + NSData *anotherDecrypted = [anotherSave decryptData:encrypted error:nil]; + + XCTAssertFalse([OCTToxEncryptSave isDataEncrypted:anotherDecrypted]); + XCTAssertTrue([data isEqualToData:anotherDecrypted]); + XCTAssertFalse([encrypted isEqualToData:anotherDecrypted]); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTToxTests.m b/local_pod_repo/objcTox/Tests/OCTToxTests.m new file mode 100644 index 0000000..4a6029c --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTToxTests.m @@ -0,0 +1,784 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import + +#import "OCTTox+Private.h" +#import "OCTToxOptions.h" +#import "OCTCAsserts.h" + +static void *refToSelf; + +void mocked_tox_self_get_public_key(const Tox *tox, uint8_t *public_key); + +@interface OCTToxTests : XCTestCase + +@property (strong, nonatomic) OCTTox *tox; + +@end + +@implementation OCTToxTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. + + refToSelf = (__bridge void *)(self); + + self.tox = [[OCTTox alloc] initWithOptions:[OCTToxOptions new] savedData:nil error:nil]; +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + refToSelf = NULL; + + self.tox = nil; + + [super tearDown]; +} + +- (void)testInit +{ + XCTAssertNotNil(self.tox); +} + +- (void)testSavingAndLoading +{ + NSData *data = [self.tox save]; + XCTAssertNotNil(data); + + OCTTox *tox = [[OCTTox alloc] initWithOptions:[OCTToxOptions new] savedData:data error:nil]; + XCTAssertNotNil(tox); + + NSData *data2 = [tox save]; + + XCTAssertTrue(data.length == data2.length); +} + +- (void)testPublicKey +{ + _tox_self_get_public_key = mocked_tox_self_get_public_key; + + NSString *publicKey = [self.tox publicKey]; + + XCTAssertEqualObjects(publicKey, @"000102030405060708090A0B0C0D0E0F" @"000102030405060708090A0B0C0D0E0F"); +} + +#pragma mark - Private methods + +- (void)testUserStatusFromCUserStatus +{ + XCTAssertTrue(OCTToxUserStatusNone == [self.tox userStatusFromCUserStatus:TOX_USER_STATUS_NONE]); + XCTAssertTrue(OCTToxUserStatusAway == [self.tox userStatusFromCUserStatus:TOX_USER_STATUS_AWAY]); + XCTAssertTrue(OCTToxUserStatusBusy == [self.tox userStatusFromCUserStatus:TOX_USER_STATUS_BUSY]); +} + +- (void)testUserConnectionStatusFromCUserStatus +{ + XCTAssertTrue(OCTToxConnectionStatusNone == [self.tox userConnectionStatusFromCUserStatus:TOX_CONNECTION_NONE]); + XCTAssertTrue(OCTToxConnectionStatusTCP == [self.tox userConnectionStatusFromCUserStatus:TOX_CONNECTION_TCP]); + XCTAssertTrue(OCTToxConnectionStatusUDP == [self.tox userConnectionStatusFromCUserStatus:TOX_CONNECTION_UDP]); +} + +- (void)testMessageTypeFromCMessageType +{ + XCTAssertTrue(OCTToxMessageTypeNormal == [self.tox messageTypeFromCMessageType:TOX_MESSAGE_TYPE_NORMAL]); + XCTAssertTrue(OCTToxMessageTypeAction == [self.tox messageTypeFromCMessageType:TOX_MESSAGE_TYPE_ACTION]); +} + +- (void)testFileControlFromCFileControl +{ + XCTAssertTrue(OCTToxFileControlResume == [self.tox fileControlFromCFileControl:TOX_FILE_CONTROL_RESUME]); + XCTAssertTrue(OCTToxFileControlPause == [self.tox fileControlFromCFileControl:TOX_FILE_CONTROL_PAUSE]); + XCTAssertTrue(OCTToxFileControlCancel == [self.tox fileControlFromCFileControl:TOX_FILE_CONTROL_CANCEL]); +} + +- (void)testFillErrorInit +{ + // test nil error + [self.tox fillError:nil withCErrorInit:TOX_ERR_NEW_NULL]; + + NSError *error; + [self.tox fillError:&error withCErrorInit:TOX_ERR_NEW_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorInit:TOX_ERR_NEW_NULL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorInitCodeUnknown); + + error = nil; + [self.tox fillError:&error withCErrorInit:TOX_ERR_NEW_MALLOC]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorInitCodeMemoryError); + + error = nil; + [self.tox fillError:&error withCErrorInit:TOX_ERR_NEW_PORT_ALLOC]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorInitCodePortAlloc); + + error = nil; + [self.tox fillError:&error withCErrorInit:TOX_ERR_NEW_PROXY_BAD_TYPE]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorInitCodeProxyBadType); + + error = nil; + [self.tox fillError:&error withCErrorInit:TOX_ERR_NEW_PROXY_BAD_HOST]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorInitCodeProxyBadHost); + + error = nil; + [self.tox fillError:&error withCErrorInit:TOX_ERR_NEW_PROXY_BAD_PORT]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorInitCodeProxyBadPort); + + error = nil; + [self.tox fillError:&error withCErrorInit:TOX_ERR_NEW_PROXY_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorInitCodeProxyNotFound); + + error = nil; + [self.tox fillError:&error withCErrorInit:TOX_ERR_NEW_LOAD_ENCRYPTED]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorInitCodeEncrypted); + + error = nil; + [self.tox fillError:&error withCErrorInit:TOX_ERR_NEW_LOAD_BAD_FORMAT]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorInitCodeLoadBadFormat); +} + +- (void)testFillErrorBootstrap +{ + // test nil error + [self.tox fillError:nil withCErrorBootstrap:TOX_ERR_BOOTSTRAP_NULL]; + + NSError *error; + [self.tox fillError:&error withCErrorBootstrap:TOX_ERR_BOOTSTRAP_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorBootstrap:TOX_ERR_BOOTSTRAP_NULL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorBootstrapCodeUnknown); + + error = nil; + [self.tox fillError:&error withCErrorBootstrap:TOX_ERR_BOOTSTRAP_BAD_HOST]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorBootstrapCodeBadHost); + + error = nil; + [self.tox fillError:&error withCErrorBootstrap:TOX_ERR_BOOTSTRAP_BAD_PORT]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorBootstrapCodeBadPort); +} + +- (void)testFillErrorFriendAdd +{ + // test nil error + [self.tox fillError:nil withCErrorFriendAdd:TOX_ERR_FRIEND_ADD_NULL]; + + NSError *error; + [self.tox fillError:&error withCErrorFriendAdd:TOX_ERR_FRIEND_ADD_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorFriendAdd:TOX_ERR_FRIEND_ADD_NULL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendAddUnknown); + + error = nil; + [self.tox fillError:&error withCErrorFriendAdd:TOX_ERR_FRIEND_ADD_TOO_LONG]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendAddTooLong); + + error = nil; + [self.tox fillError:&error withCErrorFriendAdd:TOX_ERR_FRIEND_ADD_NO_MESSAGE]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendAddNoMessage); + + error = nil; + [self.tox fillError:&error withCErrorFriendAdd:TOX_ERR_FRIEND_ADD_OWN_KEY]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendAddOwnKey); + + error = nil; + [self.tox fillError:&error withCErrorFriendAdd:TOX_ERR_FRIEND_ADD_ALREADY_SENT]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendAddAlreadySent); + + error = nil; + [self.tox fillError:&error withCErrorFriendAdd:TOX_ERR_FRIEND_ADD_BAD_CHECKSUM]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendAddBadChecksum); + + error = nil; + [self.tox fillError:&error withCErrorFriendAdd:TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendAddSetNewNospam); + + error = nil; + [self.tox fillError:&error withCErrorFriendAdd:TOX_ERR_FRIEND_ADD_MALLOC]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendAddMalloc); +} + +- (void)testFillErrorFriendDelete +{ + // test nil error + [self.tox fillError:nil withCErrorFriendDelete:TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND]; + + NSError *error; + [self.tox fillError:&error withCErrorFriendDelete:TOX_ERR_FRIEND_DELETE_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorFriendDelete:TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendDeleteNotFound); +} + +- (void)testFillErrorFriendByPublicKey +{ + // test nil error + [self.tox fillError:nil withCErrorFriendByPublicKey:TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL]; + + NSError *error; + [self.tox fillError:&error withCErrorFriendByPublicKey:TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorFriendByPublicKey:TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendByPublicKeyUnknown); + + error = nil; + [self.tox fillError:&error withCErrorFriendByPublicKey:TOX_ERR_FRIEND_BY_PUBLIC_KEY_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendByPublicKeyNotFound); +} + +- (void)testFillErrorFriendGetPublicKey +{ + // test nil error + [self.tox fillError:nil withCErrorFriendGetPublicKey:TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND]; + + NSError *error; + [self.tox fillError:&error withCErrorFriendGetPublicKey:TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorFriendGetPublicKey:TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendGetPublicKeyFriendNotFound); +} + +- (void)testFillErrorSetInfo +{ + // test nil error + [self.tox fillError:nil withCErrorSetInfo:TOX_ERR_SET_INFO_NULL]; + + NSError *error; + [self.tox fillError:&error withCErrorSetInfo:TOX_ERR_SET_INFO_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorSetInfo:TOX_ERR_SET_INFO_NULL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorSetInfoCodeUnknow); + + error = nil; + [self.tox fillError:&error withCErrorSetInfo:TOX_ERR_SET_INFO_TOO_LONG]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorSetInfoCodeTooLong); +} + +- (void)testFillErrorFriendGetLastOnline +{ + // test nil error + [self.tox fillError:nil withCErrorFriendGetLastOnline:TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND]; + + NSError *error; + [self.tox fillError:&error withCErrorFriendGetLastOnline:TOX_ERR_FRIEND_GET_LAST_ONLINE_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorFriendGetLastOnline:TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendGetLastOnlineFriendNotFound); +} + +- (void)testFillErrorFriendQuery +{ + // test nil error + [self.tox fillError:nil withCErrorFriendQuery:TOX_ERR_FRIEND_QUERY_NULL]; + + NSError *error; + [self.tox fillError:&error withCErrorFriendQuery:TOX_ERR_FRIEND_QUERY_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorFriendQuery:TOX_ERR_FRIEND_QUERY_NULL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendQueryUnknown); + + error = nil; + [self.tox fillError:&error withCErrorFriendQuery:TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendQueryFriendNotFound); +} + +- (void)testFillErrorSetTyping +{ + // test nil error + [self.tox fillError:nil withCErrorSetTyping:TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND]; + + NSError *error; + [self.tox fillError:&error withCErrorSetTyping:TOX_ERR_SET_TYPING_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorSetTyping:TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorSetTypingFriendNotFound); +} + +- (void)testFillErrorFriendSendMessage +{ + // test nil error + [self.tox fillError:nil withCErrorFriendSendMessage:TOX_ERR_FRIEND_SEND_MESSAGE_NULL]; + + NSError *error; + [self.tox fillError:&error withCErrorFriendSendMessage:TOX_ERR_FRIEND_SEND_MESSAGE_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorFriendSendMessage:TOX_ERR_FRIEND_SEND_MESSAGE_NULL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendSendMessageUnknown); + + error = nil; + [self.tox fillError:&error withCErrorFriendSendMessage:TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendSendMessageFriendNotFound); + + error = nil; + [self.tox fillError:&error withCErrorFriendSendMessage:TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendSendMessageFriendNotConnected); + + error = nil; + [self.tox fillError:&error withCErrorFriendSendMessage:TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendSendMessageAlloc); + + error = nil; + [self.tox fillError:&error withCErrorFriendSendMessage:TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendSendMessageTooLong); + + error = nil; + [self.tox fillError:&error withCErrorFriendSendMessage:TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFriendSendMessageEmpty); +} + +- (void)testFillErrorFileControl +{ + // test nil error + [self.tox fillError:nil withCErrorFileControl:TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND]; + + NSError *error; + [self.tox fillError:&error withCErrorFileControl:TOX_ERR_FILE_CONTROL_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorFileControl:TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileControlFriendNotFound); + + error = nil; + [self.tox fillError:&error withCErrorFileControl:TOX_ERR_FILE_CONTROL_FRIEND_NOT_CONNECTED]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileControlFriendNotConnected); + + error = nil; + [self.tox fillError:&error withCErrorFileControl:TOX_ERR_FILE_CONTROL_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileControlNotFound); + + error = nil; + [self.tox fillError:&error withCErrorFileControl:TOX_ERR_FILE_CONTROL_NOT_PAUSED]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileControlNotPaused); + + error = nil; + [self.tox fillError:&error withCErrorFileControl:TOX_ERR_FILE_CONTROL_DENIED]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileControlDenied); + + error = nil; + [self.tox fillError:&error withCErrorFileControl:TOX_ERR_FILE_CONTROL_ALREADY_PAUSED]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileControlAlreadyPaused); + + error = nil; + [self.tox fillError:&error withCErrorFileControl:TOX_ERR_FILE_CONTROL_SENDQ]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileControlSendq); +} + +- (void)testFillErrorFileSeek +{ + // test nil error + [self.tox fillError:nil withCErrorFileSeek:TOX_ERR_FILE_SEEK_FRIEND_NOT_FOUND]; + + NSError *error; + [self.tox fillError:&error withCErrorFileSeek:TOX_ERR_FILE_SEEK_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorFileSeek:TOX_ERR_FILE_SEEK_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSeekFriendNotFound); + + error = nil; + [self.tox fillError:&error withCErrorFileSeek:TOX_ERR_FILE_SEEK_FRIEND_NOT_CONNECTED]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSeekFriendNotConnected); + + error = nil; + [self.tox fillError:&error withCErrorFileSeek:TOX_ERR_FILE_SEEK_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSeekNotFound); + + error = nil; + [self.tox fillError:&error withCErrorFileSeek:TOX_ERR_FILE_SEEK_DENIED]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSeekDenied); + + error = nil; + [self.tox fillError:&error withCErrorFileSeek:TOX_ERR_FILE_SEEK_INVALID_POSITION]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSeekInvalidPosition); + + error = nil; + [self.tox fillError:&error withCErrorFileSeek:TOX_ERR_FILE_SEEK_SENDQ]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSeekSendq); +} + +- (void)testFillErrorFileGet +{ + // test nil error + [self.tox fillError:nil withCErrorFileGet:TOX_ERR_FILE_GET_FRIEND_NOT_FOUND]; + + NSError *error; + [self.tox fillError:&error withCErrorFileGet:TOX_ERR_FILE_GET_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorFileGet:TOX_ERR_FILE_GET_NULL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileGetInternal); + + error = nil; + [self.tox fillError:&error withCErrorFileGet:TOX_ERR_FILE_GET_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileGetFriendNotFound); + + error = nil; + [self.tox fillError:&error withCErrorFileGet:TOX_ERR_FILE_GET_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileGetNotFound); +} + +- (void)testFillErrorFileSend +{ + // test nil error + [self.tox fillError:nil withCErrorFileSend:TOX_ERR_FILE_SEND_NULL]; + + NSError *error; + [self.tox fillError:&error withCErrorFileSend:TOX_ERR_FILE_SEND_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorFileSend:TOX_ERR_FILE_SEND_NULL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendUnknown); + + error = nil; + [self.tox fillError:&error withCErrorFileSend:TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendFriendNotFound); + + error = nil; + [self.tox fillError:&error withCErrorFileSend:TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendFriendNotConnected); + + error = nil; + [self.tox fillError:&error withCErrorFileSend:TOX_ERR_FILE_SEND_NAME_TOO_LONG]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendNameTooLong); + + error = nil; + [self.tox fillError:&error withCErrorFileSend:TOX_ERR_FILE_SEND_TOO_MANY]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendTooMany); +} + +- (void)testFillErrorFileSendChunk +{ + // test nil error + [self.tox fillError:nil withCErrorFileSendChunk:TOX_ERR_FILE_SEND_CHUNK_NULL]; + + NSError *error; + [self.tox fillError:&error withCErrorFileSendChunk:TOX_ERR_FILE_SEND_CHUNK_OK]; + XCTAssertNil(error); + + error = nil; + [self.tox fillError:&error withCErrorFileSendChunk:TOX_ERR_FILE_SEND_CHUNK_NULL]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendChunkUnknown); + + error = nil; + [self.tox fillError:&error withCErrorFileSendChunk:TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendChunkFriendNotFound); + + error = nil; + [self.tox fillError:&error withCErrorFileSendChunk:TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_CONNECTED]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendChunkFriendNotConnected); + + error = nil; + [self.tox fillError:&error withCErrorFileSendChunk:TOX_ERR_FILE_SEND_CHUNK_NOT_FOUND]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendChunkNotFound); + + error = nil; + [self.tox fillError:&error withCErrorFileSendChunk:TOX_ERR_FILE_SEND_CHUNK_NOT_TRANSFERRING]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendChunkNotTransferring); + + error = nil; + [self.tox fillError:&error withCErrorFileSendChunk:TOX_ERR_FILE_SEND_CHUNK_INVALID_LENGTH]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendChunkInvalidLength); + + error = nil; + [self.tox fillError:&error withCErrorFileSendChunk:TOX_ERR_FILE_SEND_CHUNK_SENDQ]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendChunkSendq); + + error = nil; + [self.tox fillError:&error withCErrorFileSendChunk:TOX_ERR_FILE_SEND_CHUNK_WRONG_POSITION]; + XCTAssertNotNil(error); + XCTAssertTrue(error.code == OCTToxErrorFileSendChunkWrongPosition); +} + +- (void)testBinToHexString +{ + uint8_t bin[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + NSString *string = [OCTTox binToHexString:bin length:16]; + + XCTAssertTrue([@"000102030405060708090A0B0C0D0E0F" isEqualToString:string]); +} + +- (void)testHexStringToBin +{ + uint8_t *bin = [OCTTox hexStringToBin:@"000102030405060708090A0B0C0D0E0F"]; + + for (NSUInteger i = 0; i < 16; i++) { + XCTAssertTrue(bin[i] == i); + } +} + +#pragma mark - Callbacks + +- (void)testConnectionStatusCallback +{ + [self makeTestCallbackWithCallBlock:^{ + connectionStatusCallback(NULL, TOX_CONNECTION_UDP, (__bridge void *)self.tox); + } expectBlock:^(id delegate) { + OCMExpect([self.tox.delegate tox:self.tox connectionStatus:OCTToxConnectionStatusUDP]); + }]; +} + +- (void)testFriendNameCallback +{ + [self makeTestCallbackWithCallBlock:^{ + friendNameCallback(NULL, 5, (const uint8_t *)"name", 4, (__bridge void *)self.tox); + } expectBlock:^(id delegate) { + OCMExpect([self.tox.delegate tox:self.tox friendNameUpdate:[OCMArg isEqual:@"name"] friendNumber:5]); + }]; +} + +- (void)testFriendStatusMessageCallback +{ + [self makeTestCallbackWithCallBlock:^{ + friendStatusMessageCallback(NULL, 5, (const uint8_t *)"message", 7, (__bridge void *)self.tox); + } expectBlock:^(id delegate) { + OCMExpect([self.tox.delegate tox:self.tox friendStatusMessageUpdate:[OCMArg isEqual:@"message"] friendNumber:5]); + }]; +} + +- (void)testFriendStatusCallback +{ + [self makeTestCallbackWithCallBlock:^{ + friendStatusCallback(NULL, 5, TOX_USER_STATUS_BUSY, (__bridge void *)self.tox); + } expectBlock:^(id delegate) { + OCMExpect([self.tox.delegate tox:self.tox friendStatusUpdate:OCTToxUserStatusBusy friendNumber:5]); + }]; +} + +- (void)testFriendConnectionStatusCallback +{ + [self makeTestCallbackWithCallBlock:^{ + friendConnectionStatusCallback(NULL, 5, TOX_CONNECTION_UDP, (__bridge void *)self.tox); + } expectBlock:^(id delegate) { + OCMExpect([self.tox.delegate tox:self.tox friendConnectionStatusChanged:OCTToxConnectionStatusUDP friendNumber:5]); + }]; +} + +- (void)testFriendTypingCallback +{ + [self makeTestCallbackWithCallBlock:^{ + friendTypingCallback(NULL, 5, true, (__bridge void *)self.tox); + } expectBlock:^(id delegate) { + OCMExpect([self.tox.delegate tox:self.tox friendIsTypingUpdate:YES friendNumber:5]); + }]; +} + +- (void)testFriendReadReceiptCallback +{ + [self makeTestCallbackWithCallBlock:^{ + friendReadReceiptCallback(NULL, 5, 7, (__bridge void *)self.tox); + } expectBlock:^(id delegate) { + OCMExpect([self.tox.delegate tox:self.tox messageDelivered:7 friendNumber:5]); + }]; +} + +- (void)testFriendRequestCallback +{ + [self makeTestCallbackWithCallBlock:^{ + uint8_t bin[32] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + }; + + friendRequestCallback(NULL, bin, (const uint8_t *)"message", 7, (__bridge void *)self.tox); + + } expectBlock:^(id delegate) { + NSString *publicKey = + @"000102030405060708090A0B0C0D0E0F" + @"000102030405060708090A0B0C0D0E0F"; + + OCMExpect([self.tox.delegate tox:self.tox friendRequestWithMessage:[OCMArg isEqual:@"message"] publicKey:publicKey]); + }]; +} + +- (void)testFriendMessageCallback +{ + [self makeTestCallbackWithCallBlock:^{ + friendMessageCallback(NULL, 5, TOX_MESSAGE_TYPE_ACTION, (const uint8_t *)"message", 7, (__bridge void *)self.tox); + + } expectBlock:^(id delegate) { + OCMExpect([self.tox.delegate tox:self.tox + friendMessage:[OCMArg isEqual:@"message"] + type:OCTToxMessageTypeAction + friendNumber:5]); + }]; +} + +- (void)testFileReceiveControlCallback +{ + [self makeTestCallbackWithCallBlock:^{ + fileReceiveControlCallback(NULL, 5, 4, TOX_FILE_CONTROL_PAUSE, (__bridge void *)self.tox); + + } expectBlock:^(id delegate) { + OCMExpect([self.tox.delegate tox:self.tox fileReceiveControl:OCTToxFileControlPause friendNumber:5 fileNumber:4]); + }]; +} + +- (void)testFileChunkRequestCallback +{ + [self makeTestCallbackWithCallBlock:^{ + fileChunkRequestCallback(NULL, 5, 4, 300, 150, (__bridge void *)self.tox); + + } expectBlock:^(id delegate) { + OCMExpect([self.tox.delegate tox:self.tox fileChunkRequestForFileNumber:4 friendNumber:5 position:300 length:150]); + }]; +} + +- (void)testFileReceiveCallback +{ + [self makeTestCallbackWithCallBlock:^{ + fileReceiveCallback(NULL, 5, 4, TOX_FILE_KIND_DATA, 500, (const uint8_t *)"filename", 8, (__bridge void *)self.tox); + + } expectBlock:^(id delegate) { + OCMExpect([self.tox.delegate tox:self.tox + fileReceiveForFileNumber:4 + friendNumber:5 + kind:OCTToxFileKindData + fileSize:500 + fileName:[OCMArg isEqual:@"filename"]]); + }]; +} + +- (void)testFileReceiveChunkCallback +{ + [self makeTestCallbackWithCallBlock:^{ + fileReceiveChunkCallback(NULL, 5, 4, 250, (const uint8_t *)"data", 4, (__bridge void *)self.tox); + + } expectBlock:^(id delegate) { + NSData *data = [NSData dataWithBytes:"data" length:4]; + + OCMExpect([self.tox.delegate tox:self.tox + fileReceiveChunk:[OCMArg isEqual:data] + fileNumber:4 + friendNumber:5 + position:250]); + }]; +} + +- (void)makeTestCallbackWithCallBlock:(void (^)())callBlock expectBlock:(void (^)(id delegate))expectBlock +{ + NSParameterAssert(callBlock); + NSParameterAssert(expectBlock); + + self.tox.delegate = OCMProtocolMock(@protocol(OCTToxDelegate)); + expectBlock(self.tox.delegate); + + callBlock(); + + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + OCMVerifyAll((id)self.tox.delegate); +} + +void mocked_tox_self_get_public_key(const Tox *cTox, uint8_t *public_key) +{ + OCTTox *tox = [(__bridge OCTToxTests *)refToSelf tox]; + + CCCAssertTrue(cTox == tox.tox); + + uint8_t bin[32] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + }; + + memcpy(public_key, bin, TOX_PUBLIC_KEY_SIZE); +} + +@end diff --git a/local_pod_repo/objcTox/Tests/OCTVideoEngineTests.m b/local_pod_repo/objcTox/Tests/OCTVideoEngineTests.m new file mode 100644 index 0000000..958c0d1 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/OCTVideoEngineTests.m @@ -0,0 +1,235 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import +#import "OCTVideoEngine.h" +#import "OCTPixelBufferPool.h" +#import "OCTVideoView.h" +#import "OCTToxAV.h" + +#include "test_straight3p.h" +#include "test_stride13p.h" +#include "test_backwards_3p.h" +#include "test_backwards_stride13p.h" +#include "test_good.h" + +@import AVFoundation; + +@interface OCTVideoEngine (Testing) + +@property (nonatomic, strong) AVCaptureSession *captureSession; +@property (nonatomic, strong) dispatch_queue_t processingQueue; +@property (nonatomic, weak) OCTVideoView *videoView; +@property (strong, nonatomic) OCTPixelBufferPool *pixelPool; + +- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection; + +@end + +@interface OCTVideoEngineTests : XCTestCase + +@property (strong, nonatomic) OCTVideoEngine *videoEngine; +@property (strong, nonatomic) id mockedToxAV; +@property (strong, nonatomic) id mockedCaptureSession; + +@end + +@implementation OCTVideoEngineTests + +- (void)setUp +{ + [super setUp]; + self.videoEngine = [OCTVideoEngine new]; + self.mockedToxAV = OCMClassMock([OCTToxAV class]); + self.videoEngine.toxav = self.mockedToxAV; + self.mockedCaptureSession = OCMClassMock([AVCaptureSession class]); + self.videoEngine.captureSession = self.mockedCaptureSession; + + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [self.mockedToxAV stopMocking]; + self.mockedToxAV = nil; + [self.mockedCaptureSession stopMocking]; + self.mockedCaptureSession = nil; + self.videoEngine = nil; + [super tearDown]; +} + +- (void)testInit +{ + XCTAssertNotNil(self.videoEngine); +} + +- (void)testStartSendingVideo +{ + OCMStub([self.mockedCaptureSession isRunning]).andReturn(NO); + + [self.videoEngine startSendingVideo]; + + dispatch_sync(self.videoEngine.processingQueue, ^{ + OCMVerify([self.mockedCaptureSession startRunning]); + }); +} + +- (void)testStopSendingVideo +{ + OCMStub([self.mockedCaptureSession isRunning]).andReturn(YES); + + [self.videoEngine stopSendingVideo]; + + dispatch_sync(self.videoEngine.processingQueue, ^{ + OCMVerify([self.mockedCaptureSession stopRunning]); + }); +} + +- (void)testIsSendingVideo +{ + OCMStub([self.mockedCaptureSession isRunning]).andReturn(YES); + + XCTAssertTrue([self.videoEngine isSendingVideo]); +} + +- (void)testGetVideoCallPreview +{ + id mockedCapturePreviewLayer = OCMClassMock([AVCaptureVideoPreviewLayer class]); + OCMStub([mockedCapturePreviewLayer layerWithSession:self.mockedCaptureSession]).andReturn(mockedCapturePreviewLayer); + + dispatch_sync(self.videoEngine.processingQueue, ^{ + [self.videoEngine getVideoCallPreview:^(CALayer *layer) { + XCTAssertEqualObjects(layer, mockedCapturePreviewLayer); + }]; + }); +} + +- (void)testVideoFeed +{ + id mockedVideoView = OCMClassMock([OCTVideoView class]); + OCMStub([mockedVideoView view]).andReturn(mockedVideoView); + + OCTView *view = [self.videoEngine videoFeed]; + + XCTAssertEqualObjects(view, mockedVideoView); +} + +- (void)testReceiveVideoFrame +{ + OCTToxAVVideoWidth width = 10; + OCTToxAVVideoHeight height = 10; + + id mockedPixelPool = OCMClassMock([OCTPixelBufferPool class]); + self.videoEngine.pixelPool = mockedPixelPool; + + OCMStub([mockedPixelPool createPixelBuffer:[OCMArg anyPointer] width:10 height:10]).andReturn(NO); + + self.videoEngine.videoView = OCMClassMock([OCTVideoView class]); + + [self.videoEngine receiveVideoFrameWithWidth:width + height:height + yPlane:nil + uPlane:nil + vPlane:nil + yStride:10 + uStride:10 + vStride:10 + friendNumber:10]; + + dispatch_sync(self.videoEngine.processingQueue, ^{ + OCMVerify([mockedPixelPool createPixelBuffer:[OCMArg anyPointer] + width:width + height:height]); + }); +} + +- (void)performYUVCheckWithPlanes:(uint8_t * [3])planes strides:(OCTToxAVStrideData[3])strides +{ + OCTToxAVVideoWidth width = 30; + OCTToxAVVideoHeight height = 30; + self.videoEngine.videoView = OCMClassMock([OCTVideoView class]); + id ciMock = OCMClassMock([CIImage class]); + + OCMStub([ciMock imageWithCVPixelBuffer:[OCMArg anyPointer]]).andDo(^(NSInvocation *invocation) { + CVPixelBufferRef pb = NULL; + [invocation getArgument:&pb atIndex:2]; + + XCTAssertNotNil((__bridge id)pb); + XCTAssertEqual(CVPixelBufferGetPixelFormatType(pb), kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange); + + // now we compare the buffer contents with the known good result + CVPixelBufferLockBaseAddress(pb, kCVPixelBufferLock_ReadOnly); + uint8_t *y = CVPixelBufferGetBaseAddressOfPlane(pb, 0); + uint8_t *uv = CVPixelBufferGetBaseAddressOfPlane(pb, 1); + + uint8_t *testyrow = y; + uint8_t *goodyrow = test_good_y; + for (int i = 0; i < 30; ++i) { + XCTAssertEqual(memcmp(testyrow, goodyrow, 30), 0); + testyrow += CVPixelBufferGetBytesPerRowOfPlane(pb, 0); + goodyrow += 30; + } + + uint8_t *testuvrow = uv; + uint8_t *gooduvrow = test_good_uv; + for (int i = 0; i < 15; ++i) { + XCTAssertEqual(memcmp(testuvrow, gooduvrow, 30), 0); + testuvrow += CVPixelBufferGetBytesPerRowOfPlane(pb, 1); + gooduvrow += 30; + } + + void *ret = nil; + [invocation setReturnValue:&ret]; + }); + + [self.videoEngine receiveVideoFrameWithWidth:width + height:height + yPlane:planes[0] + uPlane:planes[1] + vPlane:planes[2] + yStride:strides[0] + uStride:strides[1] + vStride:strides[2] + friendNumber:10]; + + dispatch_sync(self.videoEngine.processingQueue, ^{ + OCMVerify([ciMock imageWithCVPixelBuffer:[OCMArg anyPointer]]); + }); +} + +- (void)testReceivePackedVideoFrame +{ + uint8_t *planes[3] = {test_str83p_y, test_str83p_u, test_str83p_v}; + OCTToxAVStrideData strides[3] = {30, 15, 15}; + + [self performYUVCheckWithPlanes:planes strides:strides]; +} + +- (void)testReceiveStridedVideoFrame +{ + uint8_t *planes[3] = {test_stride13p_y, test_stride13p_u, test_stride13p_v}; + OCTToxAVStrideData strides[3] = {31, 16, 16}; + + [self performYUVCheckWithPlanes:planes strides:strides]; +} + +- (void)testReceiveUpsideDownStridedVideoFrame +{ + uint8_t *planes[3] = {test_backwards_stride13p_y, test_backwards_stride13p_u, test_backwards_stride13p_v}; + OCTToxAVStrideData strides[3] = {-31, -16, -16}; + + [self performYUVCheckWithPlanes:planes strides:strides]; +} + +- (void)testReceiveUpsideDownVideoFrame +{ + uint8_t *planes[3] = {test_backwards_3p_y, test_backwards_3p_u, test_backwards_3p_v}; + OCTToxAVStrideData strides[3] = {-30, -15, -15}; + + [self performYUVCheckWithPlanes:planes strides:strides]; +} + +@end diff --git a/local_pod_repo/objcTox/Tests/YUVPlanes/test_backwards_3p.h b/local_pod_repo/objcTox/Tests/YUVPlanes/test_backwards_3p.h new file mode 100644 index 0000000..e25eb6e --- /dev/null +++ b/local_pod_repo/objcTox/Tests/YUVPlanes/test_backwards_3p.h @@ -0,0 +1,77 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/* + * Tri-planar 30x30 YUV stream but backwards + * (negative strides) + */ + +uint8_t test_backwards_3p_y[] = { + 186, 173, 174, 174, 203, 211, 196, 188, 188, 182, 168, 203, 171, 180, 210, 194, 190, 173, 179, 194, 191, 160, 125, 129, 171, 146, 76, 61, 128, 205, + 214, 188, 184, 204, 211, 182, 208, 208, 181, 180, 176, 200, 168, 181, 188, 211, 194, 181, 168, 145, 154, 181, 195, 208, 207, 206, 198, 148, 86, 112, + 213, 211, 211, 209, 184, 181, 195, 196, 194, 179, 176, 196, 172, 155, 187, 211, 201, 167, 163, 176, 204, 206, 206, 206, 206, 205, 204, 203, 203, 147, + 202, 209, 208, 203, 190, 190, 180, 182, 199, 174, 176, 197, 160, 146, 193, 200, 167, 185, 204, 204, 204, 204, 205, 205, 205, 203, 203, 202, 201, 201, + 207, 208, 207, 206, 194, 182, 185, 186, 194, 171, 175, 203, 161, 148, 208, 171, 166, 203, 203, 203, 203, 197, 184, 186, 189, 189, 186, 188, 197, 201, + 209, 201, 200, 205, 203, 189, 187, 193, 201, 174, 171, 202, 157, 160, 208, 176, 199, 202, 202, 202, 202, 195, 183, 194, 198, 196, 189, 182, 178, 192, + 195, 191, 193, 195, 204, 203, 196, 197, 201, 183, 171, 200, 153, 165, 207, 183, 196, 196, 195, 201, 202, 202, 202, 202, 201, 201, 200, 199, 198, 199, + 194, 195, 196, 196, 198, 203, 202, 200, 201, 180, 168, 200, 153, 177, 198, 175, 170, 167, 168, 179, 200, 201, 202, 202, 201, 200, 199, 198, 181, 176, + 202, 201, 201, 202, 201, 200, 201, 201, 201, 178, 165, 200, 160, 168, 199, 188, 167, 173, 173, 186, 198, 201, 198, 183, 197, 200, 199, 198, 172, 157, + 207, 204, 59, 203, 58, 201, 201, 201, 200, 187, 173, 200, 146, 153, 207, 160, 172, 199, 179, 198, 200, 201, 197, 184, 198, 200, 199, 198, 198, 188, + 208, 205, 204, 203, 203, 202, 201, 200, 200, 189, 188, 174, 125, 138, 187, 156, 170, 192, 166, 191, 200, 201, 201, 201, 200, 199, 199, 198, 198, 196, + 208, 205, 59, 203, 57, 202, 201, 201, 195, 161, 178, 181, 150, 131, 190, 171, 168, 178, 145, 179, 195, 199, 200, 199, 199, 199, 197, 196, 193, 169, + 207, 204, 58, 203, 56, 201, 200, 200, 185, 146, 182, 193, 132, 185, 206, 146, 174, 178, 85, 75, 147, 203, 204, 204, 203, 201, 201, 202, 186, 130, + 209, 206, 60, 204, 58, 203, 202, 201, 176, 157, 200, 181, 137, 198, 200, 154, 160, 106, 61, 60, 136, 206, 206, 207, 207, 204, 205, 198, 199, 94, + 216, 212, 213, 212, 210, 211, 210, 208, 182, 168, 208, 179, 158, 206, 181, 148, 179, 66, 69, 129, 200, 208, 207, 209, 208, 202, 167, 170, 148, 62, + 209, 205, 60, 203, 203, 57, 201, 200, 174, 166, 200, 177, 177, 202, 196, 113, 180, 120, 56, 109, 201, 201, 201, 202, 193, 166, 133, 184, 160, 65, + 207, 204, 58, 202, 57, 200, 195, 191, 182, 189, 198, 188, 194, 201, 207, 137, 117, 159, 122, 91, 165, 200, 198, 165, 171, 182, 158, 162, 215, 116, + 208, 204, 58, 57, 202, 201, 188, 177, 179, 198, 199, 198, 200, 200, 196, 196, 165, 126, 147, 140, 171, 200, 169, 139, 197, 200, 151, 164, 151, 132, + 208, 204, 58, 203, 57, 201, 194, 184, 169, 185, 199, 192, 197, 202, 207, 200, 185, 190, 187, 200, 200, 200, 147, 168, 200, 187, 154, 180, 162, 107, + 208, 204, 59, 203, 202, 56, 196, 179, 172, 186, 199, 187, 191, 201, 207, 206, 178, 180, 200, 200, 200, 182, 148, 190, 200, 173, 154, 194, 168, 159, + 208, 204, 203, 203, 202, 201, 197, 184, 177, 190, 199, 192, 192, 201, 207, 199, 189, 174, 196, 200, 194, 161, 177, 201, 199, 178, 181, 192, 158, 180, + 207, 204, 203, 57, 56, 201, 200, 192, 178, 169, 199, 198, 201, 203, 207, 201, 184, 165, 172, 194, 179, 170, 196, 201, 198, 192, 200, 171, 178, 199, + 207, 204, 58, 203, 202, 56, 200, 185, 193, 176, 185, 198, 200, 200, 206, 205, 198, 181, 163, 184, 175, 193, 200, 201, 198, 196, 198, 159, 184, 183, + 208, 204, 59, 203, 202, 56, 201, 194, 185, 199, 190, 199, 201, 202, 207, 206, 197, 200, 179, 174, 180, 201, 201, 201, 200, 201, 193, 167, 188, 171, + 208, 205, 58, 203, 202, 56, 200, 200, 181, 186, 199, 199, 202, 203, 208, 207, 197, 200, 189, 155, 187, 201, 201, 202, 201, 201, 187, 171, 199, 188, + 208, 204, 59, 202, 202, 56, 200, 200, 195, 169, 193, 199, 200, 199, 206, 206, 198, 200, 201, 154, 174, 201, 202, 202, 202, 201, 192, 184, 200, 196, + 207, 203, 202, 56, 56, 200, 199, 199, 199, 181, 184, 197, 199, 202, 206, 205, 196, 199, 199, 183, 182, 200, 201, 202, 201, 200, 195, 195, 199, 200, + 205, 201, 200, 199, 199, 198, 198, 197, 196, 192, 178, 195, 198, 201, 205, 204, 191, 195, 198, 198, 199, 199, 199, 200, 200, 200, 190, 191, 198, 200, + 200, 199, 199, 198, 198, 196, 196, 195, 195, 195, 180, 179, 196, 198, 204, 201, 209, 210, 194, 197, 197, 198, 199, 199, 199, 199, 181, 187, 198, 197, + 204, 201, 199, 198, 197, 197, 196, 196, 195, 195, 194, 176, 189, 191, 206, 206, 204, 206, 195, 197, 198, 199, 199, 200, 200, 200, 187, 193, 199, 199, +}; + +uint8_t test_backwards_3p_u[] = { + 99, 99, 99, 101, 105, 110, 117, 124, 130, 137, 144, 151, 153, 154, 155, + 100, 101, 101, 102, 107, 112, 118, 125, 131, 139, 146, 152, 155, 156, 156, + 101, 101, 102, 103, 107, 112, 120, 127, 134, 140, 147, 154, 156, 157, 157, + 101, 102, 102, 103, 108, 113, 120, 127, 134, 141, 148, 155, 157, 158, 158, + 101, 102, 102, 103, 108, 114, 120, 127, 134, 141, 148, 155, 158, 158, 158, + 101, 101, 102, 103, 108, 113, 120, 127, 134, 142, 148, 156, 158, 159, 159, + 101, 102, 102, 103, 107, 114, 120, 127, 134, 140, 146, 153, 155, 155, 157, + 103, 104, 104, 105, 109, 114, 121, 127, 134, 141, 147, 153, 155, 156, 151, + 101, 100, 101, 103, 107, 113, 120, 127, 134, 141, 148, 155, 158, 158, 156, + 101, 101, 102, 102, 107, 114, 120, 128, 135, 141, 148, 155, 157, 158, 159, + 101, 101, 101, 103, 108, 114, 121, 127, 134, 141, 148, 155, 157, 158, 158, + 101, 101, 101, 103, 108, 114, 121, 127, 134, 141, 148, 155, 157, 158, 158, + 102, 102, 102, 103, 108, 113, 121, 127, 134, 141, 148, 155, 157, 157, 158, + 103, 103, 103, 104, 109, 115, 122, 128, 136, 142, 149, 156, 157, 158, 158, + 104, 105, 105, 106, 111, 117, 124, 130, 135, 143, 150, 156, 158, 158, 158, +}; + +uint8_t test_backwards_3p_v[] = { + 90, 91, 91, 92, 92, 93, 92, 97, 93, 94, 95, 95, 96, 97, 98, + 92, 92, 94, 94, 94, 95, 94, 99, 94, 95, 96, 96, 97, 98, 99, + 95, 95, 96, 97, 98, 97, 97, 102, 98, 98, 99, 99, 100, 101, 103, + 100, 101, 101, 103, 103, 103, 103, 106, 103, 104, 103, 104, 105, 105, 107, + 105, 106, 108, 108, 109, 109, 109, 111, 109, 109, 110, 110, 110, 112, 112, + 112, 113, 114, 115, 116, 116, 115, 117, 115, 116, 116, 116, 117, 118, 118, + 119, 120, 121, 122, 122, 122, 123, 123, 123, 123, 123, 122, 124, 124, 125, + 126, 127, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 131, 131, + 132, 133, 135, 135, 136, 136, 136, 135, 136, 136, 136, 136, 137, 137, 137, + 139, 140, 141, 142, 143, 143, 142, 141, 143, 143, 143, 142, 143, 144, 145, + 145, 147, 148, 149, 150, 150, 149, 147, 150, 150, 150, 149, 150, 151, 151, + 152, 154, 155, 156, 157, 157, 156, 153, 157, 156, 156, 155, 157, 157, 158, + 159, 161, 162, 163, 164, 164, 162, 159, 165, 163, 163, 163, 163, 164, 164, + 163, 164, 165, 167, 167, 167, 166, 162, 169, 167, 166, 165, 166, 166, 166, + 164, 166, 167, 168, 168, 169, 169, 162, 160, 168, 167, 166, 166, 166, 167, +}; diff --git a/local_pod_repo/objcTox/Tests/YUVPlanes/test_backwards_stride13p.h b/local_pod_repo/objcTox/Tests/YUVPlanes/test_backwards_stride13p.h new file mode 100644 index 0000000..363bdc4 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/YUVPlanes/test_backwards_stride13p.h @@ -0,0 +1,77 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/* + * Tri-planar 30x30 YUV stream with 1 extra padding per row, but backwards + * (negative strides) + */ + +uint8_t test_backwards_stride13p_y[] = { + 186, 173, 174, 174, 203, 211, 196, 188, 188, 182, 168, 203, 171, 180, 210, 194, 190, 173, 179, 194, 191, 160, 125, 129, 171, 146, 76, 61, 128, 205, 126, + 214, 188, 184, 204, 211, 182, 208, 208, 181, 180, 176, 200, 168, 181, 188, 211, 194, 181, 168, 145, 154, 181, 195, 208, 207, 206, 198, 148, 86, 112, 117, + 213, 211, 211, 209, 184, 181, 195, 196, 194, 179, 176, 196, 172, 155, 187, 211, 201, 167, 163, 176, 204, 206, 206, 206, 206, 205, 204, 203, 203, 147, 102, + 202, 209, 208, 203, 190, 190, 180, 182, 199, 174, 176, 197, 160, 146, 193, 200, 167, 185, 204, 204, 204, 204, 205, 205, 205, 203, 203, 202, 201, 201, 97, + 207, 208, 207, 206, 194, 182, 185, 186, 194, 171, 175, 203, 161, 148, 208, 171, 166, 203, 203, 203, 203, 197, 184, 186, 189, 189, 186, 188, 197, 201, 119, + 209, 201, 200, 205, 203, 189, 187, 193, 201, 174, 171, 202, 157, 160, 208, 176, 199, 202, 202, 202, 202, 195, 183, 194, 198, 196, 189, 182, 178, 192, 126, + 195, 191, 193, 195, 204, 203, 196, 197, 201, 183, 171, 200, 153, 165, 207, 183, 196, 196, 195, 201, 202, 202, 202, 202, 201, 201, 200, 199, 198, 199, 117, + 194, 195, 196, 196, 198, 203, 202, 200, 201, 180, 168, 200, 153, 177, 198, 175, 170, 167, 168, 179, 200, 201, 202, 202, 201, 200, 199, 198, 181, 176, 102, + 202, 201, 201, 202, 201, 200, 201, 201, 201, 178, 165, 200, 160, 168, 199, 188, 167, 173, 173, 186, 198, 201, 198, 183, 197, 200, 199, 198, 172, 157, 97, + 207, 204, 59, 203, 58, 201, 201, 201, 200, 187, 173, 200, 146, 153, 207, 160, 172, 199, 179, 198, 200, 201, 197, 184, 198, 200, 199, 198, 198, 188, 119, + 208, 205, 204, 203, 203, 202, 201, 200, 200, 189, 188, 174, 125, 138, 187, 156, 170, 192, 166, 191, 200, 201, 201, 201, 200, 199, 199, 198, 198, 196, 126, + 208, 205, 59, 203, 57, 202, 201, 201, 195, 161, 178, 181, 150, 131, 190, 171, 168, 178, 145, 179, 195, 199, 200, 199, 199, 199, 197, 196, 193, 169, 117, + 207, 204, 58, 203, 56, 201, 200, 200, 185, 146, 182, 193, 132, 185, 206, 146, 174, 178, 85, 75, 147, 203, 204, 204, 203, 201, 201, 202, 186, 130, 102, + 209, 206, 60, 204, 58, 203, 202, 201, 176, 157, 200, 181, 137, 198, 200, 154, 160, 106, 61, 60, 136, 206, 206, 207, 207, 204, 205, 198, 199, 94, 97, + 216, 212, 213, 212, 210, 211, 210, 208, 182, 168, 208, 179, 158, 206, 181, 148, 179, 66, 69, 129, 200, 208, 207, 209, 208, 202, 167, 170, 148, 62, 119, + 209, 205, 60, 203, 203, 57, 201, 200, 174, 166, 200, 177, 177, 202, 196, 113, 180, 120, 56, 109, 201, 201, 201, 202, 193, 166, 133, 184, 160, 65, 126, + 207, 204, 58, 202, 57, 200, 195, 191, 182, 189, 198, 188, 194, 201, 207, 137, 117, 159, 122, 91, 165, 200, 198, 165, 171, 182, 158, 162, 215, 116, 117, + 208, 204, 58, 57, 202, 201, 188, 177, 179, 198, 199, 198, 200, 200, 196, 196, 165, 126, 147, 140, 171, 200, 169, 139, 197, 200, 151, 164, 151, 132, 102, + 208, 204, 58, 203, 57, 201, 194, 184, 169, 185, 199, 192, 197, 202, 207, 200, 185, 190, 187, 200, 200, 200, 147, 168, 200, 187, 154, 180, 162, 107, 97, + 208, 204, 59, 203, 202, 56, 196, 179, 172, 186, 199, 187, 191, 201, 207, 206, 178, 180, 200, 200, 200, 182, 148, 190, 200, 173, 154, 194, 168, 159, 119, + 208, 204, 203, 203, 202, 201, 197, 184, 177, 190, 199, 192, 192, 201, 207, 199, 189, 174, 196, 200, 194, 161, 177, 201, 199, 178, 181, 192, 158, 180, 126, + 207, 204, 203, 57, 56, 201, 200, 192, 178, 169, 199, 198, 201, 203, 207, 201, 184, 165, 172, 194, 179, 170, 196, 201, 198, 192, 200, 171, 178, 199, 117, + 207, 204, 58, 203, 202, 56, 200, 185, 193, 176, 185, 198, 200, 200, 206, 205, 198, 181, 163, 184, 175, 193, 200, 201, 198, 196, 198, 159, 184, 183, 102, + 208, 204, 59, 203, 202, 56, 201, 194, 185, 199, 190, 199, 201, 202, 207, 206, 197, 200, 179, 174, 180, 201, 201, 201, 200, 201, 193, 167, 188, 171, 97, + 208, 205, 58, 203, 202, 56, 200, 200, 181, 186, 199, 199, 202, 203, 208, 207, 197, 200, 189, 155, 187, 201, 201, 202, 201, 201, 187, 171, 199, 188, 119, + 208, 204, 59, 202, 202, 56, 200, 200, 195, 169, 193, 199, 200, 199, 206, 206, 198, 200, 201, 154, 174, 201, 202, 202, 202, 201, 192, 184, 200, 196, 126, + 207, 203, 202, 56, 56, 200, 199, 199, 199, 181, 184, 197, 199, 202, 206, 205, 196, 199, 199, 183, 182, 200, 201, 202, 201, 200, 195, 195, 199, 200, 117, + 205, 201, 200, 199, 199, 198, 198, 197, 196, 192, 178, 195, 198, 201, 205, 204, 191, 195, 198, 198, 199, 199, 199, 200, 200, 200, 190, 191, 198, 200, 102, + 200, 199, 199, 198, 198, 196, 196, 195, 195, 195, 180, 179, 196, 198, 204, 201, 209, 210, 194, 197, 197, 198, 199, 199, 199, 199, 181, 187, 198, 197, 97, + 204, 201, 199, 198, 197, 197, 196, 196, 195, 195, 194, 176, 189, 191, 206, 206, 204, 206, 195, 197, 198, 199, 199, 200, 200, 200, 187, 193, 199, 199, 119, +}; + +uint8_t test_backwards_stride13p_u[] = { + 99, 99, 99, 101, 105, 110, 117, 124, 130, 137, 144, 151, 153, 154, 155, 126, + 100, 101, 101, 102, 107, 112, 118, 125, 131, 139, 146, 152, 155, 156, 156, 117, + 101, 101, 102, 103, 107, 112, 120, 127, 134, 140, 147, 154, 156, 157, 157, 102, + 101, 102, 102, 103, 108, 113, 120, 127, 134, 141, 148, 155, 157, 158, 158, 97, + 101, 102, 102, 103, 108, 114, 120, 127, 134, 141, 148, 155, 158, 158, 158, 119, + 101, 101, 102, 103, 108, 113, 120, 127, 134, 142, 148, 156, 158, 159, 159, 126, + 101, 102, 102, 103, 107, 114, 120, 127, 134, 140, 146, 153, 155, 155, 157, 117, + 103, 104, 104, 105, 109, 114, 121, 127, 134, 141, 147, 153, 155, 156, 151, 102, + 101, 100, 101, 103, 107, 113, 120, 127, 134, 141, 148, 155, 158, 158, 156, 97, + 101, 101, 102, 102, 107, 114, 120, 128, 135, 141, 148, 155, 157, 158, 159, 119, + 101, 101, 101, 103, 108, 114, 121, 127, 134, 141, 148, 155, 157, 158, 158, 126, + 101, 101, 101, 103, 108, 114, 121, 127, 134, 141, 148, 155, 157, 158, 158, 117, + 102, 102, 102, 103, 108, 113, 121, 127, 134, 141, 148, 155, 157, 157, 158, 102, + 103, 103, 103, 104, 109, 115, 122, 128, 136, 142, 149, 156, 157, 158, 158, 97, + 104, 105, 105, 106, 111, 117, 124, 130, 135, 143, 150, 156, 158, 158, 158, 119, +}; + +uint8_t test_backwards_stride13p_v[] = { + 90, 91, 91, 92, 92, 93, 92, 97, 93, 94, 95, 95, 96, 97, 98, 126, + 92, 92, 94, 94, 94, 95, 94, 99, 94, 95, 96, 96, 97, 98, 99, 117, + 95, 95, 96, 97, 98, 97, 97, 102, 98, 98, 99, 99, 100, 101, 103, 102, + 100, 101, 101, 103, 103, 103, 103, 106, 103, 104, 103, 104, 105, 105, 107, 97, + 105, 106, 108, 108, 109, 109, 109, 111, 109, 109, 110, 110, 110, 112, 112, 119, + 112, 113, 114, 115, 116, 116, 115, 117, 115, 116, 116, 116, 117, 118, 118, 126, + 119, 120, 121, 122, 122, 122, 123, 123, 123, 123, 123, 122, 124, 124, 125, 117, + 126, 127, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 131, 131, 102, + 132, 133, 135, 135, 136, 136, 136, 135, 136, 136, 136, 136, 137, 137, 137, 97, + 139, 140, 141, 142, 143, 143, 142, 141, 143, 143, 143, 142, 143, 144, 145, 119, + 145, 147, 148, 149, 150, 150, 149, 147, 150, 150, 150, 149, 150, 151, 151, 126, + 152, 154, 155, 156, 157, 157, 156, 153, 157, 156, 156, 155, 157, 157, 158, 117, + 159, 161, 162, 163, 164, 164, 162, 159, 165, 163, 163, 163, 163, 164, 164, 102, + 163, 164, 165, 167, 167, 167, 166, 162, 169, 167, 166, 165, 166, 166, 166, 97, + 164, 166, 167, 168, 168, 169, 169, 162, 160, 168, 167, 166, 166, 166, 167, 119, +}; diff --git a/local_pod_repo/objcTox/Tests/YUVPlanes/test_good.h b/local_pod_repo/objcTox/Tests/YUVPlanes/test_good.h new file mode 100644 index 0000000..026a85c --- /dev/null +++ b/local_pod_repo/objcTox/Tests/YUVPlanes/test_good.h @@ -0,0 +1,54 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +uint8_t test_good_y[] = { + 204, 201, 199, 198, 197, 197, 196, 196, 195, 195, 194, 176, 189, 191, 206, 206, 204, 206, 195, 197, 198, 199, 199, 200, 200, 200, 187, 193, 199, 199, + 200, 199, 199, 198, 198, 196, 196, 195, 195, 195, 180, 179, 196, 198, 204, 201, 209, 210, 194, 197, 197, 198, 199, 199, 199, 199, 181, 187, 198, 197, + 205, 201, 200, 199, 199, 198, 198, 197, 196, 192, 178, 195, 198, 201, 205, 204, 191, 195, 198, 198, 199, 199, 199, 200, 200, 200, 190, 191, 198, 200, + 207, 203, 202, 56, 56, 200, 199, 199, 199, 181, 184, 197, 199, 202, 206, 205, 196, 199, 199, 183, 182, 200, 201, 202, 201, 200, 195, 195, 199, 200, + 208, 204, 59, 202, 202, 56, 200, 200, 195, 169, 193, 199, 200, 199, 206, 206, 198, 200, 201, 154, 174, 201, 202, 202, 202, 201, 192, 184, 200, 196, + 208, 205, 58, 203, 202, 56, 200, 200, 181, 186, 199, 199, 202, 203, 208, 207, 197, 200, 189, 155, 187, 201, 201, 202, 201, 201, 187, 171, 199, 188, + 208, 204, 59, 203, 202, 56, 201, 194, 185, 199, 190, 199, 201, 202, 207, 206, 197, 200, 179, 174, 180, 201, 201, 201, 200, 201, 193, 167, 188, 171, + 207, 204, 58, 203, 202, 56, 200, 185, 193, 176, 185, 198, 200, 200, 206, 205, 198, 181, 163, 184, 175, 193, 200, 201, 198, 196, 198, 159, 184, 183, + 207, 204, 203, 57, 56, 201, 200, 192, 178, 169, 199, 198, 201, 203, 207, 201, 184, 165, 172, 194, 179, 170, 196, 201, 198, 192, 200, 171, 178, 199, + 208, 204, 203, 203, 202, 201, 197, 184, 177, 190, 199, 192, 192, 201, 207, 199, 189, 174, 196, 200, 194, 161, 177, 201, 199, 178, 181, 192, 158, 180, + 208, 204, 59, 203, 202, 56, 196, 179, 172, 186, 199, 187, 191, 201, 207, 206, 178, 180, 200, 200, 200, 182, 148, 190, 200, 173, 154, 194, 168, 159, + 208, 204, 58, 203, 57, 201, 194, 184, 169, 185, 199, 192, 197, 202, 207, 200, 185, 190, 187, 200, 200, 200, 147, 168, 200, 187, 154, 180, 162, 107, + 208, 204, 58, 57, 202, 201, 188, 177, 179, 198, 199, 198, 200, 200, 196, 196, 165, 126, 147, 140, 171, 200, 169, 139, 197, 200, 151, 164, 151, 132, + 207, 204, 58, 202, 57, 200, 195, 191, 182, 189, 198, 188, 194, 201, 207, 137, 117, 159, 122, 91, 165, 200, 198, 165, 171, 182, 158, 162, 215, 116, + 209, 205, 60, 203, 203, 57, 201, 200, 174, 166, 200, 177, 177, 202, 196, 113, 180, 120, 56, 109, 201, 201, 201, 202, 193, 166, 133, 184, 160, 65, + 216, 212, 213, 212, 210, 211, 210, 208, 182, 168, 208, 179, 158, 206, 181, 148, 179, 66, 69, 129, 200, 208, 207, 209, 208, 202, 167, 170, 148, 62, + 209, 206, 60, 204, 58, 203, 202, 201, 176, 157, 200, 181, 137, 198, 200, 154, 160, 106, 61, 60, 136, 206, 206, 207, 207, 204, 205, 198, 199, 94, + 207, 204, 58, 203, 56, 201, 200, 200, 185, 146, 182, 193, 132, 185, 206, 146, 174, 178, 85, 75, 147, 203, 204, 204, 203, 201, 201, 202, 186, 130, + 208, 205, 59, 203, 57, 202, 201, 201, 195, 161, 178, 181, 150, 131, 190, 171, 168, 178, 145, 179, 195, 199, 200, 199, 199, 199, 197, 196, 193, 169, + 208, 205, 204, 203, 203, 202, 201, 200, 200, 189, 188, 174, 125, 138, 187, 156, 170, 192, 166, 191, 200, 201, 201, 201, 200, 199, 199, 198, 198, 196, + 207, 204, 59, 203, 58, 201, 201, 201, 200, 187, 173, 200, 146, 153, 207, 160, 172, 199, 179, 198, 200, 201, 197, 184, 198, 200, 199, 198, 198, 188, + 202, 201, 201, 202, 201, 200, 201, 201, 201, 178, 165, 200, 160, 168, 199, 188, 167, 173, 173, 186, 198, 201, 198, 183, 197, 200, 199, 198, 172, 157, + 194, 195, 196, 196, 198, 203, 202, 200, 201, 180, 168, 200, 153, 177, 198, 175, 170, 167, 168, 179, 200, 201, 202, 202, 201, 200, 199, 198, 181, 176, + 195, 191, 193, 195, 204, 203, 196, 197, 201, 183, 171, 200, 153, 165, 207, 183, 196, 196, 195, 201, 202, 202, 202, 202, 201, 201, 200, 199, 198, 199, + 209, 201, 200, 205, 203, 189, 187, 193, 201, 174, 171, 202, 157, 160, 208, 176, 199, 202, 202, 202, 202, 195, 183, 194, 198, 196, 189, 182, 178, 192, + 207, 208, 207, 206, 194, 182, 185, 186, 194, 171, 175, 203, 161, 148, 208, 171, 166, 203, 203, 203, 203, 197, 184, 186, 189, 189, 186, 188, 197, 201, + 202, 209, 208, 203, 190, 190, 180, 182, 199, 174, 176, 197, 160, 146, 193, 200, 167, 185, 204, 204, 204, 204, 205, 205, 205, 203, 203, 202, 201, 201, + 213, 211, 211, 209, 184, 181, 195, 196, 194, 179, 176, 196, 172, 155, 187, 211, 201, 167, 163, 176, 204, 206, 206, 206, 206, 205, 204, 203, 203, 147, + 214, 188, 184, 204, 211, 182, 208, 208, 181, 180, 176, 200, 168, 181, 188, 211, 194, 181, 168, 145, 154, 181, 195, 208, 207, 206, 198, 148, 86, 112, + 186, 173, 174, 174, 203, 211, 196, 188, 188, 182, 168, 203, 171, 180, 210, 194, 190, 173, 179, 194, 191, 160, 125, 129, 171, 146, 76, 61, 128, 205, +}; + +uint8_t test_good_uv[] = { + 104, 164, 105, 166, 105, 167, 106, 168, 111, 168, 117, 169, 124, 169, 130, 162, 135, 160, 143, 168, 150, 167, 156, 166, 158, 166, 158, 166, 158, 167, + 103, 163, 103, 164, 103, 165, 104, 167, 109, 167, 115, 167, 122, 166, 128, 162, 136, 169, 142, 167, 149, 166, 156, 165, 157, 166, 158, 166, 158, 166, + 102, 159, 102, 161, 102, 162, 103, 163, 108, 164, 113, 164, 121, 162, 127, 159, 134, 165, 141, 163, 148, 163, 155, 163, 157, 163, 157, 164, 158, 164, + 101, 152, 101, 154, 101, 155, 103, 156, 108, 157, 114, 157, 121, 156, 127, 153, 134, 157, 141, 156, 148, 156, 155, 155, 157, 157, 158, 157, 158, 158, + 101, 145, 101, 147, 101, 148, 103, 149, 108, 150, 114, 150, 121, 149, 127, 147, 134, 150, 141, 150, 148, 150, 155, 149, 157, 150, 158, 151, 158, 151, + 101, 139, 101, 140, 102, 141, 102, 142, 107, 143, 114, 143, 120, 142, 128, 141, 135, 143, 141, 143, 148, 143, 155, 142, 157, 143, 158, 144, 159, 145, + 101, 132, 100, 133, 101, 135, 103, 135, 107, 136, 113, 136, 120, 136, 127, 135, 134, 136, 141, 136, 148, 136, 155, 136, 158, 137, 158, 137, 156, 137, + 103, 126, 104, 127, 104, 128, 105, 129, 109, 129, 114, 129, 121, 129, 127, 129, 134, 129, 141, 129, 147, 129, 153, 129, 155, 129, 156, 131, 151, 131, + 101, 119, 102, 120, 102, 121, 103, 122, 107, 122, 114, 122, 120, 123, 127, 123, 134, 123, 140, 123, 146, 123, 153, 122, 155, 124, 155, 124, 157, 125, + 101, 112, 101, 113, 102, 114, 103, 115, 108, 116, 113, 116, 120, 115, 127, 117, 134, 115, 142, 116, 148, 116, 156, 116, 158, 117, 159, 118, 159, 118, + 101, 105, 102, 106, 102, 108, 103, 108, 108, 109, 114, 109, 120, 109, 127, 111, 134, 109, 141, 109, 148, 110, 155, 110, 158, 110, 158, 112, 158, 112, + 101, 100, 102, 101, 102, 101, 103, 103, 108, 103, 113, 103, 120, 103, 127, 106, 134, 103, 141, 104, 148, 103, 155, 104, 157, 105, 158, 105, 158, 107, + 101, 95, 101, 95, 102, 96, 103, 97, 107, 98, 112, 97, 120, 97, 127, 102, 134, 98, 140, 98, 147, 99, 154, 99, 156, 100, 157, 101, 157, 103, + 100, 92, 101, 92, 101, 94, 102, 94, 107, 94, 112, 95, 118, 94, 125, 99, 131, 94, 139, 95, 146, 96, 152, 96, 155, 97, 156, 98, 156, 99, + 99, 90, 99, 91, 99, 91, 101, 92, 105, 92, 110, 93, 117, 92, 124, 97, 130, 93, 137, 94, 144, 95, 151, 95, 153, 96, 154, 97, 155, 98, +}; diff --git a/local_pod_repo/objcTox/Tests/YUVPlanes/test_straight3p.h b/local_pod_repo/objcTox/Tests/YUVPlanes/test_straight3p.h new file mode 100644 index 0000000..7853cfa --- /dev/null +++ b/local_pod_repo/objcTox/Tests/YUVPlanes/test_straight3p.h @@ -0,0 +1,75 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/* + * Tightly packed (i.e. stride == width) tri-planar + * 30x30 YUV stream. + */ + +uint8_t test_str83p_y[] = { + 204, 201, 199, 198, 197, 197, 196, 196, 195, 195, 194, 176, 189, 191, 206, 206, 204, 206, 195, 197, 198, 199, 199, 200, 200, 200, 187, 193, 199, 199, + 200, 199, 199, 198, 198, 196, 196, 195, 195, 195, 180, 179, 196, 198, 204, 201, 209, 210, 194, 197, 197, 198, 199, 199, 199, 199, 181, 187, 198, 197, + 205, 201, 200, 199, 199, 198, 198, 197, 196, 192, 178, 195, 198, 201, 205, 204, 191, 195, 198, 198, 199, 199, 199, 200, 200, 200, 190, 191, 198, 200, + 207, 203, 202, 56, 56, 200, 199, 199, 199, 181, 184, 197, 199, 202, 206, 205, 196, 199, 199, 183, 182, 200, 201, 202, 201, 200, 195, 195, 199, 200, + 208, 204, 59, 202, 202, 56, 200, 200, 195, 169, 193, 199, 200, 199, 206, 206, 198, 200, 201, 154, 174, 201, 202, 202, 202, 201, 192, 184, 200, 196, + 208, 205, 58, 203, 202, 56, 200, 200, 181, 186, 199, 199, 202, 203, 208, 207, 197, 200, 189, 155, 187, 201, 201, 202, 201, 201, 187, 171, 199, 188, + 208, 204, 59, 203, 202, 56, 201, 194, 185, 199, 190, 199, 201, 202, 207, 206, 197, 200, 179, 174, 180, 201, 201, 201, 200, 201, 193, 167, 188, 171, + 207, 204, 58, 203, 202, 56, 200, 185, 193, 176, 185, 198, 200, 200, 206, 205, 198, 181, 163, 184, 175, 193, 200, 201, 198, 196, 198, 159, 184, 183, + 207, 204, 203, 57, 56, 201, 200, 192, 178, 169, 199, 198, 201, 203, 207, 201, 184, 165, 172, 194, 179, 170, 196, 201, 198, 192, 200, 171, 178, 199, + 208, 204, 203, 203, 202, 201, 197, 184, 177, 190, 199, 192, 192, 201, 207, 199, 189, 174, 196, 200, 194, 161, 177, 201, 199, 178, 181, 192, 158, 180, + 208, 204, 59, 203, 202, 56, 196, 179, 172, 186, 199, 187, 191, 201, 207, 206, 178, 180, 200, 200, 200, 182, 148, 190, 200, 173, 154, 194, 168, 159, + 208, 204, 58, 203, 57, 201, 194, 184, 169, 185, 199, 192, 197, 202, 207, 200, 185, 190, 187, 200, 200, 200, 147, 168, 200, 187, 154, 180, 162, 107, + 208, 204, 58, 57, 202, 201, 188, 177, 179, 198, 199, 198, 200, 200, 196, 196, 165, 126, 147, 140, 171, 200, 169, 139, 197, 200, 151, 164, 151, 132, + 207, 204, 58, 202, 57, 200, 195, 191, 182, 189, 198, 188, 194, 201, 207, 137, 117, 159, 122, 91, 165, 200, 198, 165, 171, 182, 158, 162, 215, 116, + 209, 205, 60, 203, 203, 57, 201, 200, 174, 166, 200, 177, 177, 202, 196, 113, 180, 120, 56, 109, 201, 201, 201, 202, 193, 166, 133, 184, 160, 65, + 216, 212, 213, 212, 210, 211, 210, 208, 182, 168, 208, 179, 158, 206, 181, 148, 179, 66, 69, 129, 200, 208, 207, 209, 208, 202, 167, 170, 148, 62, + 209, 206, 60, 204, 58, 203, 202, 201, 176, 157, 200, 181, 137, 198, 200, 154, 160, 106, 61, 60, 136, 206, 206, 207, 207, 204, 205, 198, 199, 94, + 207, 204, 58, 203, 56, 201, 200, 200, 185, 146, 182, 193, 132, 185, 206, 146, 174, 178, 85, 75, 147, 203, 204, 204, 203, 201, 201, 202, 186, 130, + 208, 205, 59, 203, 57, 202, 201, 201, 195, 161, 178, 181, 150, 131, 190, 171, 168, 178, 145, 179, 195, 199, 200, 199, 199, 199, 197, 196, 193, 169, + 208, 205, 204, 203, 203, 202, 201, 200, 200, 189, 188, 174, 125, 138, 187, 156, 170, 192, 166, 191, 200, 201, 201, 201, 200, 199, 199, 198, 198, 196, + 207, 204, 59, 203, 58, 201, 201, 201, 200, 187, 173, 200, 146, 153, 207, 160, 172, 199, 179, 198, 200, 201, 197, 184, 198, 200, 199, 198, 198, 188, + 202, 201, 201, 202, 201, 200, 201, 201, 201, 178, 165, 200, 160, 168, 199, 188, 167, 173, 173, 186, 198, 201, 198, 183, 197, 200, 199, 198, 172, 157, + 194, 195, 196, 196, 198, 203, 202, 200, 201, 180, 168, 200, 153, 177, 198, 175, 170, 167, 168, 179, 200, 201, 202, 202, 201, 200, 199, 198, 181, 176, + 195, 191, 193, 195, 204, 203, 196, 197, 201, 183, 171, 200, 153, 165, 207, 183, 196, 196, 195, 201, 202, 202, 202, 202, 201, 201, 200, 199, 198, 199, + 209, 201, 200, 205, 203, 189, 187, 193, 201, 174, 171, 202, 157, 160, 208, 176, 199, 202, 202, 202, 202, 195, 183, 194, 198, 196, 189, 182, 178, 192, + 207, 208, 207, 206, 194, 182, 185, 186, 194, 171, 175, 203, 161, 148, 208, 171, 166, 203, 203, 203, 203, 197, 184, 186, 189, 189, 186, 188, 197, 201, + 202, 209, 208, 203, 190, 190, 180, 182, 199, 174, 176, 197, 160, 146, 193, 200, 167, 185, 204, 204, 204, 204, 205, 205, 205, 203, 203, 202, 201, 201, + 213, 211, 211, 209, 184, 181, 195, 196, 194, 179, 176, 196, 172, 155, 187, 211, 201, 167, 163, 176, 204, 206, 206, 206, 206, 205, 204, 203, 203, 147, + 214, 188, 184, 204, 211, 182, 208, 208, 181, 180, 176, 200, 168, 181, 188, 211, 194, 181, 168, 145, 154, 181, 195, 208, 207, 206, 198, 148, 86, 112, + 186, 173, 174, 174, 203, 211, 196, 188, 188, 182, 168, 203, 171, 180, 210, 194, 190, 173, 179, 194, 191, 160, 125, 129, 171, 146, 76, 61, 128, 205, +}; +uint8_t test_str83p_u[] = { + 104, 105, 105, 106, 111, 117, 124, 130, 135, 143, 150, 156, 158, 158, 158, + 103, 103, 103, 104, 109, 115, 122, 128, 136, 142, 149, 156, 157, 158, 158, + 102, 102, 102, 103, 108, 113, 121, 127, 134, 141, 148, 155, 157, 157, 158, + 101, 101, 101, 103, 108, 114, 121, 127, 134, 141, 148, 155, 157, 158, 158, + 101, 101, 101, 103, 108, 114, 121, 127, 134, 141, 148, 155, 157, 158, 158, + 101, 101, 102, 102, 107, 114, 120, 128, 135, 141, 148, 155, 157, 158, 159, + 101, 100, 101, 103, 107, 113, 120, 127, 134, 141, 148, 155, 158, 158, 156, + 103, 104, 104, 105, 109, 114, 121, 127, 134, 141, 147, 153, 155, 156, 151, + 101, 102, 102, 103, 107, 114, 120, 127, 134, 140, 146, 153, 155, 155, 157, + 101, 101, 102, 103, 108, 113, 120, 127, 134, 142, 148, 156, 158, 159, 159, + 101, 102, 102, 103, 108, 114, 120, 127, 134, 141, 148, 155, 158, 158, 158, + 101, 102, 102, 103, 108, 113, 120, 127, 134, 141, 148, 155, 157, 158, 158, + 101, 101, 102, 103, 107, 112, 120, 127, 134, 140, 147, 154, 156, 157, 157, + 100, 101, 101, 102, 107, 112, 118, 125, 131, 139, 146, 152, 155, 156, 156, + 99, 99, 99, 101, 105, 110, 117, 124, 130, 137, 144, 151, 153, 154, 155, +}; +uint8_t test_str83p_v[] = { + 164, 166, 167, 168, 168, 169, 169, 162, 160, 168, 167, 166, 166, 166, 167, + 163, 164, 165, 167, 167, 167, 166, 162, 169, 167, 166, 165, 166, 166, 166, + 159, 161, 162, 163, 164, 164, 162, 159, 165, 163, 163, 163, 163, 164, 164, + 152, 154, 155, 156, 157, 157, 156, 153, 157, 156, 156, 155, 157, 157, 158, + 145, 147, 148, 149, 150, 150, 149, 147, 150, 150, 150, 149, 150, 151, 151, + 139, 140, 141, 142, 143, 143, 142, 141, 143, 143, 143, 142, 143, 144, 145, + 132, 133, 135, 135, 136, 136, 136, 135, 136, 136, 136, 136, 137, 137, 137, + 126, 127, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 131, 131, + 119, 120, 121, 122, 122, 122, 123, 123, 123, 123, 123, 122, 124, 124, 125, + 112, 113, 114, 115, 116, 116, 115, 117, 115, 116, 116, 116, 117, 118, 118, + 105, 106, 108, 108, 109, 109, 109, 111, 109, 109, 110, 110, 110, 112, 112, + 100, 101, 101, 103, 103, 103, 103, 106, 103, 104, 103, 104, 105, 105, 107, + 95, 95, 96, 97, 98, 97, 97, 102, 98, 98, 99, 99, 100, 101, 103, + 92, 92, 94, 94, 94, 95, 94, 99, 94, 95, 96, 96, 97, 98, 99, + 90, 91, 91, 92, 92, 93, 92, 97, 93, 94, 95, 95, 96, 97, 98, +}; diff --git a/local_pod_repo/objcTox/Tests/YUVPlanes/test_stride13p.h b/local_pod_repo/objcTox/Tests/YUVPlanes/test_stride13p.h new file mode 100644 index 0000000..23b8b55 --- /dev/null +++ b/local_pod_repo/objcTox/Tests/YUVPlanes/test_stride13p.h @@ -0,0 +1,74 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/* + * Tri-planar 30x30 YUV stream with 1 extra padding per row. + */ + +uint8_t test_stride13p_y[] = { + 204, 201, 199, 198, 197, 197, 196, 196, 195, 195, 194, 176, 189, 191, 206, 206, 204, 206, 195, 197, 198, 199, 199, 200, 200, 200, 187, 193, 199, 199, 119, + 200, 199, 199, 198, 198, 196, 196, 195, 195, 195, 180, 179, 196, 198, 204, 201, 209, 210, 194, 197, 197, 198, 199, 199, 199, 199, 181, 187, 198, 197, 97, + 205, 201, 200, 199, 199, 198, 198, 197, 196, 192, 178, 195, 198, 201, 205, 204, 191, 195, 198, 198, 199, 199, 199, 200, 200, 200, 190, 191, 198, 200, 102, + 207, 203, 202, 56, 56, 200, 199, 199, 199, 181, 184, 197, 199, 202, 206, 205, 196, 199, 199, 183, 182, 200, 201, 202, 201, 200, 195, 195, 199, 200, 117, + 208, 204, 59, 202, 202, 56, 200, 200, 195, 169, 193, 199, 200, 199, 206, 206, 198, 200, 201, 154, 174, 201, 202, 202, 202, 201, 192, 184, 200, 196, 126, + 208, 205, 58, 203, 202, 56, 200, 200, 181, 186, 199, 199, 202, 203, 208, 207, 197, 200, 189, 155, 187, 201, 201, 202, 201, 201, 187, 171, 199, 188, 119, + 208, 204, 59, 203, 202, 56, 201, 194, 185, 199, 190, 199, 201, 202, 207, 206, 197, 200, 179, 174, 180, 201, 201, 201, 200, 201, 193, 167, 188, 171, 97, + 207, 204, 58, 203, 202, 56, 200, 185, 193, 176, 185, 198, 200, 200, 206, 205, 198, 181, 163, 184, 175, 193, 200, 201, 198, 196, 198, 159, 184, 183, 102, + 207, 204, 203, 57, 56, 201, 200, 192, 178, 169, 199, 198, 201, 203, 207, 201, 184, 165, 172, 194, 179, 170, 196, 201, 198, 192, 200, 171, 178, 199, 117, + 208, 204, 203, 203, 202, 201, 197, 184, 177, 190, 199, 192, 192, 201, 207, 199, 189, 174, 196, 200, 194, 161, 177, 201, 199, 178, 181, 192, 158, 180, 126, + 208, 204, 59, 203, 202, 56, 196, 179, 172, 186, 199, 187, 191, 201, 207, 206, 178, 180, 200, 200, 200, 182, 148, 190, 200, 173, 154, 194, 168, 159, 119, + 208, 204, 58, 203, 57, 201, 194, 184, 169, 185, 199, 192, 197, 202, 207, 200, 185, 190, 187, 200, 200, 200, 147, 168, 200, 187, 154, 180, 162, 107, 97, + 208, 204, 58, 57, 202, 201, 188, 177, 179, 198, 199, 198, 200, 200, 196, 196, 165, 126, 147, 140, 171, 200, 169, 139, 197, 200, 151, 164, 151, 132, 102, + 207, 204, 58, 202, 57, 200, 195, 191, 182, 189, 198, 188, 194, 201, 207, 137, 117, 159, 122, 91, 165, 200, 198, 165, 171, 182, 158, 162, 215, 116, 117, + 209, 205, 60, 203, 203, 57, 201, 200, 174, 166, 200, 177, 177, 202, 196, 113, 180, 120, 56, 109, 201, 201, 201, 202, 193, 166, 133, 184, 160, 65, 126, + 216, 212, 213, 212, 210, 211, 210, 208, 182, 168, 208, 179, 158, 206, 181, 148, 179, 66, 69, 129, 200, 208, 207, 209, 208, 202, 167, 170, 148, 62, 119, + 209, 206, 60, 204, 58, 203, 202, 201, 176, 157, 200, 181, 137, 198, 200, 154, 160, 106, 61, 60, 136, 206, 206, 207, 207, 204, 205, 198, 199, 94, 97, + 207, 204, 58, 203, 56, 201, 200, 200, 185, 146, 182, 193, 132, 185, 206, 146, 174, 178, 85, 75, 147, 203, 204, 204, 203, 201, 201, 202, 186, 130, 102, + 208, 205, 59, 203, 57, 202, 201, 201, 195, 161, 178, 181, 150, 131, 190, 171, 168, 178, 145, 179, 195, 199, 200, 199, 199, 199, 197, 196, 193, 169, 117, + 208, 205, 204, 203, 203, 202, 201, 200, 200, 189, 188, 174, 125, 138, 187, 156, 170, 192, 166, 191, 200, 201, 201, 201, 200, 199, 199, 198, 198, 196, 126, + 207, 204, 59, 203, 58, 201, 201, 201, 200, 187, 173, 200, 146, 153, 207, 160, 172, 199, 179, 198, 200, 201, 197, 184, 198, 200, 199, 198, 198, 188, 119, + 202, 201, 201, 202, 201, 200, 201, 201, 201, 178, 165, 200, 160, 168, 199, 188, 167, 173, 173, 186, 198, 201, 198, 183, 197, 200, 199, 198, 172, 157, 97, + 194, 195, 196, 196, 198, 203, 202, 200, 201, 180, 168, 200, 153, 177, 198, 175, 170, 167, 168, 179, 200, 201, 202, 202, 201, 200, 199, 198, 181, 176, 102, + 195, 191, 193, 195, 204, 203, 196, 197, 201, 183, 171, 200, 153, 165, 207, 183, 196, 196, 195, 201, 202, 202, 202, 202, 201, 201, 200, 199, 198, 199, 117, + 209, 201, 200, 205, 203, 189, 187, 193, 201, 174, 171, 202, 157, 160, 208, 176, 199, 202, 202, 202, 202, 195, 183, 194, 198, 196, 189, 182, 178, 192, 126, + 207, 208, 207, 206, 194, 182, 185, 186, 194, 171, 175, 203, 161, 148, 208, 171, 166, 203, 203, 203, 203, 197, 184, 186, 189, 189, 186, 188, 197, 201, 119, + 202, 209, 208, 203, 190, 190, 180, 182, 199, 174, 176, 197, 160, 146, 193, 200, 167, 185, 204, 204, 204, 204, 205, 205, 205, 203, 203, 202, 201, 201, 97, + 213, 211, 211, 209, 184, 181, 195, 196, 194, 179, 176, 196, 172, 155, 187, 211, 201, 167, 163, 176, 204, 206, 206, 206, 206, 205, 204, 203, 203, 147, 102, + 214, 188, 184, 204, 211, 182, 208, 208, 181, 180, 176, 200, 168, 181, 188, 211, 194, 181, 168, 145, 154, 181, 195, 208, 207, 206, 198, 148, 86, 112, 117, + 186, 173, 174, 174, 203, 211, 196, 188, 188, 182, 168, 203, 171, 180, 210, 194, 190, 173, 179, 194, 191, 160, 125, 129, 171, 146, 76, 61, 128, 205, 126, +}; +uint8_t test_stride13p_u[] = { + 104, 105, 105, 106, 111, 117, 124, 130, 135, 143, 150, 156, 158, 158, 158, 119, + 103, 103, 103, 104, 109, 115, 122, 128, 136, 142, 149, 156, 157, 158, 158, 97, + 102, 102, 102, 103, 108, 113, 121, 127, 134, 141, 148, 155, 157, 157, 158, 102, + 101, 101, 101, 103, 108, 114, 121, 127, 134, 141, 148, 155, 157, 158, 158, 117, + 101, 101, 101, 103, 108, 114, 121, 127, 134, 141, 148, 155, 157, 158, 158, 126, + 101, 101, 102, 102, 107, 114, 120, 128, 135, 141, 148, 155, 157, 158, 159, 119, + 101, 100, 101, 103, 107, 113, 120, 127, 134, 141, 148, 155, 158, 158, 156, 97, + 103, 104, 104, 105, 109, 114, 121, 127, 134, 141, 147, 153, 155, 156, 151, 102, + 101, 102, 102, 103, 107, 114, 120, 127, 134, 140, 146, 153, 155, 155, 157, 117, + 101, 101, 102, 103, 108, 113, 120, 127, 134, 142, 148, 156, 158, 159, 159, 126, + 101, 102, 102, 103, 108, 114, 120, 127, 134, 141, 148, 155, 158, 158, 158, 119, + 101, 102, 102, 103, 108, 113, 120, 127, 134, 141, 148, 155, 157, 158, 158, 97, + 101, 101, 102, 103, 107, 112, 120, 127, 134, 140, 147, 154, 156, 157, 157, 102, + 100, 101, 101, 102, 107, 112, 118, 125, 131, 139, 146, 152, 155, 156, 156, 117, + 99, 99, 99, 101, 105, 110, 117, 124, 130, 137, 144, 151, 153, 154, 155, 126, +}; +uint8_t test_stride13p_v[] = { + 164, 166, 167, 168, 168, 169, 169, 162, 160, 168, 167, 166, 166, 166, 167, 119, + 163, 164, 165, 167, 167, 167, 166, 162, 169, 167, 166, 165, 166, 166, 166, 97, + 159, 161, 162, 163, 164, 164, 162, 159, 165, 163, 163, 163, 163, 164, 164, 102, + 152, 154, 155, 156, 157, 157, 156, 153, 157, 156, 156, 155, 157, 157, 158, 117, + 145, 147, 148, 149, 150, 150, 149, 147, 150, 150, 150, 149, 150, 151, 151, 126, + 139, 140, 141, 142, 143, 143, 142, 141, 143, 143, 143, 142, 143, 144, 145, 119, + 132, 133, 135, 135, 136, 136, 136, 135, 136, 136, 136, 136, 137, 137, 137, 97, + 126, 127, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 131, 131, 102, + 119, 120, 121, 122, 122, 122, 123, 123, 123, 123, 123, 122, 124, 124, 125, 117, + 112, 113, 114, 115, 116, 116, 115, 117, 115, 116, 116, 116, 117, 118, 118, 126, + 105, 106, 108, 108, 109, 109, 109, 111, 109, 109, 110, 110, 110, 112, 112, 119, + 100, 101, 101, 103, 103, 103, 103, 106, 103, 104, 103, 104, 105, 105, 107, 97, + 95, 95, 96, 97, 98, 97, 97, 102, 98, 98, 99, 99, 100, 101, 103, 102, + 92, 92, 94, 94, 94, 95, 94, 99, 94, 95, 96, 96, 97, 98, 99, 117, + 90, 91, 91, 92, 92, 93, 92, 97, 93, 94, 95, 95, 96, 97, 98, 126, +}; diff --git a/local_pod_repo/objcTox/Tests/unencrypted-database.realm b/local_pod_repo/objcTox/Tests/unencrypted-database.realm new file mode 100644 index 0000000..0de68fa Binary files /dev/null and b/local_pod_repo/objcTox/Tests/unencrypted-database.realm differ diff --git a/local_pod_repo/objcTox/codecov.yml b/local_pod_repo/objcTox/codecov.yml new file mode 100644 index 0000000..0ebca77 --- /dev/null +++ b/local_pod_repo/objcTox/codecov.yml @@ -0,0 +1,12 @@ +comment: + layout: header, sunburst +coverage: + ignore: + - iOSDemo/* + - iOSDemoTests/* + - OSXDemo/* + - OSXDemoTests/* + - Pods/.* + - Tests/.* + - Classes/Public/Manager/RBQFetchedResultsController/.* + diff --git a/local_pod_repo/objcTox/iOSDemo/AppDelegate.h b/local_pod_repo/objcTox/iOSDemo/AppDelegate.h new file mode 100644 index 0000000..10fbfc7 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/AppDelegate.h @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/AppDelegate.m b/local_pod_repo/objcTox/iOSDemo/AppDelegate.m new file mode 100644 index 0000000..d8bd79e --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/AppDelegate.m @@ -0,0 +1,60 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "AppDelegate.h" +#import "OCTStartDemoViewController.h" +#import "DDLog.h" +#import "DDASLLogger.h" +#import "DDTTYLogger.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + + [DDLog addLogger:[DDASLLogger sharedInstance]]; + [DDLog addLogger:[DDTTYLogger sharedInstance]]; + + self.window.rootViewController = [OCTStartDemoViewController new]; + + [self.window makeKeyAndVisible]; + + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application +{ + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application +{ + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application +{ + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application +{ + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application +{ + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/Base.lproj/LaunchScreen.xib b/local_pod_repo/objcTox/iOSDemo/Base.lproj/LaunchScreen.xib new file mode 100644 index 0000000..b6a0e5a --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/Base.lproj/LaunchScreen.xib @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_pod_repo/objcTox/iOSDemo/Images.xcassets/AppIcon.appiconset/Contents.json b/local_pod_repo/objcTox/iOSDemo/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..1d060ed --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,93 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/local_pod_repo/objcTox/iOSDemo/Info.plist b/local_pod_repo/objcTox/iOSDemo/Info.plist new file mode 100644 index 0000000..939599c --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + me.dvor.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/local_pod_repo/objcTox/iOSDemo/OCTCallsViewController.h b/local_pod_repo/objcTox/iOSDemo/OCTCallsViewController.h new file mode 100644 index 0000000..41f7004 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTCallsViewController.h @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTTableViewController.h" + +@interface OCTCallsViewController : OCTTableViewController + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTCallsViewController.m b/local_pod_repo/objcTox/iOSDemo/OCTCallsViewController.m new file mode 100644 index 0000000..a28a9fd --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTCallsViewController.m @@ -0,0 +1,244 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +#import +#import +#import + +#import "OCTCallsViewController.h" +#import "OCTSubmanagerCalls.h" +#import "OCTSubmanagerObjects.h" +#import "OCTCall.h" +#import "OCTVideoViewController.h" + +@interface OCTCallsViewController () + +@property (strong, nonatomic) RLMResults *calls; +@property (strong, nonatomic) RLMNotificationToken *callsNotificationToken; +@property (strong, nonatomic) OCTCall *selectedCall; + +@end + +@implementation OCTCallsViewController + +#pragma mark - Lifecycle + +- (instancetype)initWithManager:(id)manager +{ + self = [super initWithManager:manager]; + + if (! self) { + return nil; + } + + _calls = [self.manager.objects objectsForType:OCTFetchRequestTypeCall predicate:nil]; + + self.title = @"Calls"; + + return self; +} + +- (void)dealloc +{ + [self.callsNotificationToken invalidate]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + __weak typeof(self)weakSelf = self; + + self.callsNotificationToken = [self.calls addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *error) { + if (error) { + NSLog(@"Failed to open Realm on background worker: %@", error); + return; + } + + UITableView *tableView = weakSelf.tableView; + + // Initial run of the query will pass nil for the change information + if (! changes) { + [tableView reloadData]; + return; + } + + // Query results have changed, so apply them to the UITableView + [tableView beginUpdates]; + [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; + [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; + [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; + [tableView endUpdates]; + }]; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + self.selectedCall = self.calls[indexPath.row]; + + [self showActionDialog]; + + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +#pragma mark - UITableViewDataSource + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [self cellForIndexPath:indexPath]; + + OCTCall *call = self.calls[indexPath.row]; + + cell.textLabel.text = [NSString stringWithFormat:@"Call\n" + @"Chat identifier %@\n" + @"call status: %ld\n" + @"callDuration: %f\n" + @"friend sending audio: %d\n" + @"friend receiving audio: %d\n" + @"friend sending video: %d\n" + @"friend receiving Video: %d\n", + call.chat.uniqueIdentifier, (long)call.status, call.callDuration, call.friendSendingAudio, call.friendAcceptingAudio, + call.friendSendingVideo, call.friendAcceptingVideo]; + + return cell; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return self.calls.count; +} + +#pragma mark - Private + +- (void)showActionDialog +{ + __weak OCTCallsViewController *weakSelf = self; + + [self showActionSheet:^(UIActionSheet *sheet) { + [sheet bk_addButtonWithTitle:@"Send call controls" handler:^{ + [weakSelf showSendControlDialog]; + }]; + + [sheet bk_addButtonWithTitle:@"Mute/Unmute Mic" handler:^{ + [weakSelf toggleMuteMic]; + }]; + + [sheet bk_addButtonWithTitle:@"Use speaker phone" handler:^{ + [weakSelf useSpeaker]; + }]; + + [sheet bk_addButtonWithTitle:@"Use default speakers" handler:^{ + [weakSelf useDefaultSpeaker]; + }]; + + [sheet bk_addButtonWithTitle:@"Show video" handler:^{ + [weakSelf showVideo]; + }]; + }]; + +} + +- (void)showSendControlDialog +{ + __weak OCTCallsViewController *weakSelf = self; + + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Call control" + message:@"Pick call control to send to friend" + preferredStyle:UIAlertControllerStyleActionSheet]; + + UIAlertAction *pauseAction = [UIAlertAction actionWithTitle:@"Pause" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) {[weakSelf pause]; + }]; + + UIAlertAction *resumeAction = [UIAlertAction actionWithTitle:@"Resume" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) {[weakSelf resume]; + }]; + + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"End/Reject Call" + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction *action) {[weakSelf cancel]; + }]; + + UIAlertAction *muteAction = [UIAlertAction actionWithTitle:@"Mute Audio" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) {[weakSelf muteFriend]; + }]; + + UIAlertAction *unmuteAction = [UIAlertAction actionWithTitle:@"Unmute Audio" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) {[weakSelf unmuteFriend]; + }]; + + [alertController addAction:pauseAction]; + [alertController addAction:resumeAction]; + [alertController addAction:cancelAction]; + [alertController addAction:muteAction]; + [alertController addAction:unmuteAction]; + + [self presentViewController:alertController animated:YES completion:nil]; +} + + +#pragma mark - Call methods + +- (void)cancel +{ + NSError *error; + if (! [self.manager.calls sendCallControl:OCTToxAVCallControlCancel toCall:self.selectedCall error:&error]) { + NSLog(@"%@ Error %@", self, error.localizedDescription); + NSLog(@"%@ Reason: %@", self, error.localizedFailureReason); + } +} + +- (void)useSpeaker +{ + [self.manager.calls routeAudioToSpeaker:YES error:nil]; +} + +- (void)useDefaultSpeaker +{ + [self.manager.calls routeAudioToSpeaker:NO error:nil]; +} + +- (void)toggleMuteMic +{ + BOOL currentStatus = self.manager.calls.enableMicrophone; + self.manager.calls.enableMicrophone = ! currentStatus; +} + +- (void)pause +{ + [self.manager.calls sendCallControl:OCTToxAVCallControlPause toCall:self.selectedCall error:nil]; +} + +- (void)resume +{ + [self.manager.calls sendCallControl:OCTToxAVCallControlResume toCall:self.selectedCall error:nil]; +} + +- (void)muteFriend +{ + [self.manager.calls sendCallControl:OCTToxAVCallControlMuteAudio toCall:self.selectedCall error:nil]; +} + +- (void)unmuteFriend +{ + [self.manager.calls sendCallControl:OCTToxAVCallControlUnmuteAudio toCall:self.selectedCall error:nil]; +} + +- (void)showVideo +{ + [self.manager.calls enableVideoSending:YES forCall:self.selectedCall error:nil]; + + OCTVideoViewController *videoViewController = [[OCTVideoViewController alloc] initWithCallManager:self.manager.calls call:self.selectedCall]; + videoViewController.modalInPopover = YES; + videoViewController.modalPresentationStyle = UIModalPresentationOverFullScreen; + + [self presentViewController:videoViewController animated:YES completion:nil]; +} + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTChatsViewController.h b/local_pod_repo/objcTox/iOSDemo/OCTChatsViewController.h new file mode 100644 index 0000000..b5fc1b4 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTChatsViewController.h @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTTableViewController.h" + +@interface OCTChatsViewController : OCTTableViewController + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTChatsViewController.m b/local_pod_repo/objcTox/iOSDemo/OCTChatsViewController.m new file mode 100644 index 0000000..de0452e --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTChatsViewController.m @@ -0,0 +1,106 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTChatsViewController.h" +#import "OCTConversationViewController.h" +#import "OCTChat.h" +#import "OCTSubmanagerObjects.h" + +@interface OCTChatsViewController () + +@property (strong, nonatomic) RLMResults *chats; +@property (strong, nonatomic) RLMNotificationToken *chatsNotificationToken; + +@end + +@implementation OCTChatsViewController + +#pragma mark - Lifecycle + +- (instancetype)initWithManager:(id)manager +{ + self = [super initWithManager:manager]; + + if (! self) { + return nil; + } + + _chats = [self.manager.objects objectsForType:OCTFetchRequestTypeChat predicate:nil]; + self.title = @"Chats"; + + return self; +} + +- (void)dealloc +{ + [self.chatsNotificationToken invalidate]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + __weak typeof(self)weakSelf = self; + + self.chatsNotificationToken = [self.chats addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *error) { + if (error) { + NSLog(@"Failed to open Realm on background worker: %@", error); + return; + } + + UITableView *tableView = weakSelf.tableView; + + // Initial run of the query will pass nil for the change information + if (! changes) { + [tableView reloadData]; + return; + } + + // Query results have changed, so apply them to the UITableView + [tableView beginUpdates]; + [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; + [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; + [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; + [tableView endUpdates]; + }]; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + OCTChat *chat = self.chats[indexPath.row]; + + OCTConversationViewController *cv = [[OCTConversationViewController alloc] initWithManager:self.manager chat:chat]; + [self.navigationController pushViewController:cv animated:YES]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return self.chats.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [self cellForIndexPath:indexPath]; + + OCTChat *chat = self.chats[indexPath.row]; + + cell.textLabel.text = [NSString stringWithFormat:@"Chat\n" + @"uniqueIdentifier %@\n" + @"friends %@\n" + @"enteredText %@\n" + @"lastReadDate %@\n" + @"hasUnreadMessages %d\n" + @"lastMessage: %@", + chat.uniqueIdentifier, chat.friends, chat.enteredText, chat.lastReadDate, [chat hasUnreadMessages], chat.lastMessage]; + + return cell; +} + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTConversationViewController.h b/local_pod_repo/objcTox/iOSDemo/OCTConversationViewController.h new file mode 100644 index 0000000..6db5a43 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTConversationViewController.h @@ -0,0 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTTableViewController.h" + +@class OCTChat; + +@interface OCTConversationViewController : OCTTableViewController + +- (instancetype)initWithManager:(id)manager chat:(OCTChat *)chat; + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTConversationViewController.m b/local_pod_repo/objcTox/iOSDemo/OCTConversationViewController.m new file mode 100644 index 0000000..27eda57 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTConversationViewController.m @@ -0,0 +1,159 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import +#import + +#import "OCTConversationViewController.h" +#import "OCTChat.h" +#import "OCTMessageAbstract.h" +#import "OCTSubmanagerObjects.h" +#import "OCTSubmanagerChats.h" +#import "OCTSubmanagerCalls.h" + +@interface OCTConversationViewController () + +@property (strong, nonatomic) OCTChat *chat; +@property (strong, nonatomic) RLMResults *messages; +@property (strong, nonatomic) RLMNotificationToken *messagesNotificationToken; + +@end + +@implementation OCTConversationViewController + +- (instancetype)initWithManager:(id)manager chat:(OCTChat *)chat +{ + self = [super initWithManager:manager]; + + if (! self) { + return nil; + } + + _chat = chat; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@", chat.uniqueIdentifier]; + _messages = [self.manager.objects objectsForType:OCTFetchRequestTypeMessageAbstract predicate:predicate]; + + self.title = [NSString stringWithFormat:@"%@", chat.uniqueIdentifier]; + + return self; +} + +- (void)dealloc +{ + [self.messagesNotificationToken invalidate]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + __weak typeof(self)weakSelf = self; + + self.messagesNotificationToken = [self.messages addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *error) { + if (error) { + NSLog(@"Failed to open Realm on background worker: %@", error); + return; + } + + UITableView *tableView = weakSelf.tableView; + + // Initial run of the query will pass nil for the change information + if (! changes) { + [tableView reloadData]; + return; + } + + // Query results have changed, so apply them to the UITableView + [tableView beginUpdates]; + [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; + [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; + [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; + [tableView endUpdates]; + }]; + + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] + bk_initWithBarButtonSystemItem:UIBarButtonSystemItemAdd handler:^(id handler) { + [weakSelf showSendDialog]; + }]; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return self.messages.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [self cellForIndexPath:indexPath]; + + OCTMessageAbstract *message = self.messages[indexPath.row]; + + cell.textLabel.text = [message description]; + + return cell; +} + +#pragma mark - Private + +- (void)showSendDialog +{ + __weak OCTConversationViewController *weakSelf = self; + + [self showActionSheet:^(UIActionSheet *sheet) { + [sheet bk_addButtonWithTitle:@"Send message" handler:^{ + [weakSelf sendMessage]; + }]; + + [sheet bk_addButtonWithTitle:@"Call friend" handler:^{ + [weakSelf callFriend]; + }]; + }]; +} + + + +- (void)sendMessage +{ + UIAlertView *alert = [UIAlertView bk_alertViewWithTitle:@"Send message" message:nil]; + + alert.alertViewStyle = UIAlertViewStylePlainTextInput; + UITextField *messageField = [alert textFieldAtIndex:0]; + messageField.placeholder = @"Message"; + + __weak OCTConversationViewController *weakSelf = self; + [alert bk_addButtonWithTitle:@"OK" handler:^{ + [weakSelf.manager.chats sendMessageToChat:weakSelf.chat text:messageField.text type:OCTToxMessageTypeNormal successBlock:^(OCTMessageAbstract *_) { + [weakSelf.tableView reloadData]; + } failureBlock:nil]; + }]; + + [alert bk_setCancelButtonWithTitle:@"Cancel" handler:nil]; + + [alert show]; +} + +#pragma mark - Call methods + +- (void)callFriend +{ + NSError *error; + OCTCall *call = [self.manager.calls callToChat:self.chat enableAudio:YES enableVideo:NO error:&error]; + + if (! call) { + NSLog(@"Unable to create call, %@", error.localizedFailureReason); + } +} + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTFriendsViewController.h b/local_pod_repo/objcTox/iOSDemo/OCTFriendsViewController.h new file mode 100644 index 0000000..430dcf3 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTFriendsViewController.h @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTTableViewController.h" + +@interface OCTFriendsViewController : OCTTableViewController + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTFriendsViewController.m b/local_pod_repo/objcTox/iOSDemo/OCTFriendsViewController.m new file mode 100644 index 0000000..357fe41 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTFriendsViewController.m @@ -0,0 +1,264 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import +#import + +#import "OCTFriendsViewController.h" +#import "OCTFriend.h" +#import "OCTFriendRequest.h" +#import "OCTSubmanagerObjects.h" +#import "OCTSubmanagerChats.h" +#import "OCTSubmanagerFriends.h" + +typedef NS_ENUM(NSUInteger, SectionType) { + SectionTypeFriends = 0, + SectionTypeFriendRequests, + SectionTypeCount, +}; + +@interface OCTFriendsViewController () + +@property (strong, nonatomic) RLMResults *friends; +@property (strong, nonatomic) RLMNotificationToken *friendsNotificationToken; +@property (strong, nonatomic) RLMResults *friendRequests; +@property (strong, nonatomic) RLMNotificationToken *friendRequestsNotificationToken; + +@end + +@implementation OCTFriendsViewController + +#pragma mark - Lifecycle + +- (instancetype)initWithManager:(id)manager +{ + self = [super initWithManager:manager]; + + if (! self) { + return nil; + } + + _friends = [self.manager.objects objectsForType:OCTFetchRequestTypeFriend predicate:nil]; + _friendRequests = [self.manager.objects objectsForType:OCTFetchRequestTypeFriendRequest predicate:nil]; + + self.title = @"Friends"; + + return self; +} + +- (void)dealloc +{ + [self.friendsNotificationToken invalidate]; + [self.friendRequestsNotificationToken invalidate]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + __weak typeof(self)weakSelf = self; + + self.friendsNotificationToken = [self.friends addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *error) { + [weakSelf realmWasUpdated:changes sectionType:SectionTypeFriends error:error]; + }]; + + self.friendRequestsNotificationToken = [self.friendRequests addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *error) { + [weakSelf realmWasUpdated:changes sectionType:SectionTypeFriendRequests error:error]; + }]; + + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] + bk_initWithBarButtonSystemItem:UIBarButtonSystemItemAdd handler:^(id handler) { + [weakSelf sendFriendRequest]; + }]; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + SectionType type = indexPath.section; + + switch (type) { + case SectionTypeFriends: + [self didSelectFriend:self.friends[indexPath.row]]; + break; + case SectionTypeFriendRequests: + [self didSelectFriendRequest:self.friendRequests[indexPath.row]]; + break; + case SectionTypeCount: + // nop + break; + } +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return SectionTypeCount; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + SectionType type = section; + + switch (type) { + case SectionTypeFriends: + return self.friends.count; + case SectionTypeFriendRequests: + return self.friendRequests.count; + case SectionTypeCount: + return 0; + } +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +{ + SectionType type = section; + + switch (type) { + case SectionTypeFriends: + return @"Friends"; + case SectionTypeFriendRequests: + return @"FriendRequests"; + case SectionTypeCount: + return nil; + } +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + SectionType type = indexPath.section; + + switch (type) { + case SectionTypeFriends: + return [self friendCellAtIndexPath:indexPath]; + case SectionTypeFriendRequests: + return [self friendRequestCellAtIndexPath:indexPath]; + case SectionTypeCount: + return nil; + } +} + +#pragma mark - Private + +- (UITableViewCell *)friendCellAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [self cellForIndexPath:indexPath]; + OCTFriend *friend = self.friends[indexPath.row]; + + cell.textLabel.text = [NSString stringWithFormat:@"Friend\n" + @"friendNumber %u\n" + @"publicKey %@\n" + @"name %@\n" + @"nickname %@\n" + @"statusMessage %@\n" + @"status %@\n" + @"isConnected %d\n" + @"connectionStatus %@\n" + @"lastSeenOnline %@\n" + @"isTyping %d", + friend.friendNumber, + friend.publicKey, + friend.name, + friend.nickname, + friend.statusMessage, + [self stringFromUserStatus:friend.status], + friend.isConnected, + [self stringFromConnectionStatus:friend.connectionStatus], + friend.lastSeenOnline, + friend.isTyping]; + + return cell; +} + +- (UITableViewCell *)friendRequestCellAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [self cellForIndexPath:indexPath]; + OCTFriendRequest *request = self.friendRequests[indexPath.row]; + + cell.textLabel.text = [NSString stringWithFormat:@"Friend request\n" + @"publicKey %@\n" + @"message %@\n", + request.publicKey, request.message]; + + return cell; +} + +- (void)didSelectFriend:(OCTFriend *)friend +{ + __weak OCTFriendsViewController *weakSelf = self; + + [self showActionSheet:^(UIActionSheet *sheet) { + [sheet bk_addButtonWithTitle:@"Create chat" handler:^{ + [weakSelf.manager.chats getOrCreateChatWithFriend:friend]; + }]; + + [sheet bk_addButtonWithTitle:@"Remove" handler:^{ + [weakSelf.manager.friends removeFriend:friend error:nil]; + }]; + }]; +} + +- (void)didSelectFriendRequest:(OCTFriendRequest *)request +{ + __weak OCTFriendsViewController *weakSelf = self; + + [self showActionSheet:^(UIActionSheet *sheet) { + [sheet bk_addButtonWithTitle:@"Add" handler:^{ + [weakSelf.manager.friends approveFriendRequest:request error:nil]; + }]; + + [sheet bk_addButtonWithTitle:@"Remove" handler:^{ + [weakSelf.manager.friends removeFriendRequest:request]; + }]; + }]; +} + +- (void)sendFriendRequest +{ + UIAlertView *alert = [UIAlertView bk_alertViewWithTitle:@"Send friend request" message:nil]; + + alert.alertViewStyle = UIAlertViewStyleLoginAndPasswordInput; + UITextField *addressField = [alert textFieldAtIndex:0]; + addressField.placeholder = @"Address"; + UITextField *messageField = [alert textFieldAtIndex:1]; + messageField.placeholder = @"Message"; + messageField.secureTextEntry = NO; + + __weak OCTFriendsViewController *weakSelf = self; + [alert bk_addButtonWithTitle:@"OK" handler:^{ + [weakSelf.manager.friends sendFriendRequestToAddress:addressField.text message:messageField.text error:nil]; + [weakSelf.tableView reloadData]; + }]; + + [alert bk_setCancelButtonWithTitle:@"Cancel" handler:nil]; + + [alert show]; +} + +- (void)realmWasUpdated:(RLMCollectionChange *)changes sectionType:(SectionType)sectionType error:(NSError *)error +{ + if (error) { + NSLog(@"Failed to open Realm on background worker: %@", error); + return; + } + + // Initial run of the query will pass nil for the change information + if (! changes) { + [self.tableView reloadData]; + return; + } + + [self.tableView beginUpdates]; + [self.tableView deleteRowsAtIndexPaths:[changes deletionsInSection:sectionType] withRowAnimation:UITableViewRowAnimationAutomatic]; + [self.tableView insertRowsAtIndexPaths:[changes insertionsInSection:sectionType] withRowAnimation:UITableViewRowAnimationAutomatic]; + [self.tableView reloadRowsAtIndexPaths:[changes modificationsInSection:sectionType] withRowAnimation:UITableViewRowAnimationAutomatic]; + [self.tableView endUpdates]; +} + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTStartDemoViewController.h b/local_pod_repo/objcTox/iOSDemo/OCTStartDemoViewController.h new file mode 100644 index 0000000..19c17ff --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTStartDemoViewController.h @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTTableViewController.h" + +@interface OCTStartDemoViewController : OCTTableViewController + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTStartDemoViewController.m b/local_pod_repo/objcTox/iOSDemo/OCTStartDemoViewController.m new file mode 100644 index 0000000..4b34e2e --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTStartDemoViewController.m @@ -0,0 +1,158 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTStartDemoViewController.h" +#import "OCTUserViewController.h" +#import "OCTFriendsViewController.h" +#import "OCTChatsViewController.h" +#import "OCTCallsViewController.h" +#import "OCTTabBarControllerViewController.h" +#import "AppDelegate.h" +#import "OCTManagerConfiguration.h" +#import "OCTManagerFactory.h" +#import "OCTSubmanagerBootstrap.h" + +#define NAVIGATION_WITH_CONTROLLER(class) \ + [[UINavigationController alloc] initWithRootViewController:[[class alloc] initWithManager:manager]] + +static NSString *const kLoginIdentifier = @"kLoginIdentifier"; + +typedef NS_ENUM(NSUInteger, Row) { + RowBootstrap, + RowIPv6Enabled, + RowUDPEnabled, + __RowCount, +}; + +@interface OCTStartDemoViewController () + +@property (strong, nonatomic) OCTManagerConfiguration *configuration; + +@end + +@implementation OCTStartDemoViewController + +#pragma mark - Lifecycle + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Start"; + self.configuration = [OCTManagerConfiguration defaultConfiguration]; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + Row row = indexPath.row; + + switch (row) { + case RowBootstrap: + [self bootstrap]; + break; + case RowIPv6Enabled: + case RowUDPEnabled: + [self showActionSheetForRow:row]; + break; + case __RowCount: + // nop + break; + } +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return __RowCount; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [self cellForIndexPath:indexPath]; + + Row row = indexPath.row; + cell.textLabel.textColor = [UIColor blackColor]; + cell.textLabel.textAlignment = NSTextAlignmentLeft; + cell.textLabel.font = [UIFont boldSystemFontOfSize:16.0]; + + switch (row) { + case RowBootstrap: + cell.textLabel.text = @"Bootstrap"; + cell.textLabel.textColor = [UIColor blueColor]; + cell.textLabel.textAlignment = NSTextAlignmentCenter; + cell.textLabel.font = [UIFont boldSystemFontOfSize:20.0]; + break; + + case RowIPv6Enabled: + cell.textLabel.text = [NSString stringWithFormat:@"IPv6Enabled %u", self.configuration.options.ipv6Enabled]; + break; + case RowUDPEnabled: + cell.textLabel.text = [NSString stringWithFormat:@"UDPEnabled %u", self.configuration.options.udpEnabled]; + break; + case __RowCount: + // nop + break; + } + + return cell; +} + +#pragma mark - Private + +- (void)bootstrap +{ + [OCTManagerFactory managerWithConfiguration:self.configuration encryptPassword:@"123" successBlock:^(id < OCTManager > manager) { + OCTTabBarControllerViewController *tabBar = [OCTTabBarControllerViewController new]; + tabBar.viewControllers = @[ + NAVIGATION_WITH_CONTROLLER(OCTUserViewController), + NAVIGATION_WITH_CONTROLLER(OCTFriendsViewController), + NAVIGATION_WITH_CONTROLLER(OCTChatsViewController), + NAVIGATION_WITH_CONTROLLER(OCTCallsViewController), + ]; + + AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate; + delegate.window.rootViewController = tabBar; + manager.calls.delegate = tabBar; + + [manager.bootstrap addPredefinedNodes]; + [manager.bootstrap bootstrap]; + } failureBlock:nil]; +} + +- (void)showActionSheetForRow:(Row)row +{ + __weak OCTStartDemoViewController *weakSelf = self; + + [self showActionSheet:^(UIActionSheet *sheet) { + switch (row) { + case RowIPv6Enabled: + { + [weakSelf addToSheet:sheet multiEditButtonWithOptions:@[ @"NO", @"YES" ] block:^(NSUInteger index) { + weakSelf.configuration.options.ipv6Enabled = (BOOL)index; + }]; + break; + } + case RowUDPEnabled: + { + [weakSelf addToSheet:sheet multiEditButtonWithOptions:@[ @"NO", @"YES" ] block:^(NSUInteger index) { + weakSelf.configuration.options.udpEnabled = (BOOL)index; + }]; + break; + } + case RowBootstrap: + case __RowCount: + // nop + break; + } + }]; +} + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTTabBarControllerViewController.h b/local_pod_repo/objcTox/iOSDemo/OCTTabBarControllerViewController.h new file mode 100644 index 0000000..0e44906 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTTabBarControllerViewController.h @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTSubmanagerCalls.h" + +@interface OCTTabBarControllerViewController : UITabBarController + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTTabBarControllerViewController.m b/local_pod_repo/objcTox/iOSDemo/OCTTabBarControllerViewController.m new file mode 100644 index 0000000..7402c0b --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTTabBarControllerViewController.m @@ -0,0 +1,53 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTTabBarControllerViewController.h" + +@interface OCTTabBarControllerViewController () + +@end + +@implementation OCTTabBarControllerViewController + +#pragma mark - OCTSubmanagerCalls Delegate + +- (void)callSubmanager:(id)callSubmanager receiveCall:(OCTCall *)call audioEnabled:(BOOL)audioEnabled videoEnabled:(BOOL)videoEnabled +{ + NSString *title = [NSString stringWithFormat:@"audio: %d video: %d", audioEnabled, videoEnabled]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Receiving call" message:title preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction *rejectAction = [UIAlertAction actionWithTitle:@"Reject Call" + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *action) { + NSError *error; + BOOL status = [callSubmanager sendCallControl:OCTToxAVCallControlCancel toCall:call error:&error]; + if (! status) { + NSLog(@"End call error: %@, %@", error.localizedDescription + , error.localizedFailureReason); + } + }]; + + UIAlertAction *acceptAction = [UIAlertAction actionWithTitle:@"Accept" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + NSError *error; + BOOL status = [callSubmanager answerCall:call + enableAudio:YES + enableVideo:YES + error:&error]; + if (! status) { + NSLog(@"Accept call error: %@, %@", error.localizedDescription + , error.localizedFailureReason); + } + }]; + + [alertController addAction:rejectAction]; + [alertController addAction:acceptAction]; + + [self presentViewController:alertController animated:YES completion:nil]; +} + +- (void)callSubmanager:(id)callSubmanager audioBitRateChanged:(OCTToxAVAudioBitRate)bitRate stable:(BOOL)stable forCall:(OCTCall *)call +{} +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTTableViewController.h b/local_pod_repo/objcTox/iOSDemo/OCTTableViewController.h new file mode 100644 index 0000000..afaa985 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTTableViewController.h @@ -0,0 +1,32 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "OCTManager.h" + +@interface OCTTableViewController : UIViewController + +@property (strong, nonatomic, readonly) UITableView *tableView; +@property (strong, nonatomic, readonly) id manager; + +- (instancetype)initWithManager:(id)manager; + +- (UITableViewCell *)cellForIndexPath:(NSIndexPath *)indexPath; + +- (void)showActionSheet:(void (^)(UIActionSheet *sheet))block; +- (void)addToSheet:(UIActionSheet *)sheet copyButtonWithValue:(id)value; +- (void)addToSheet:(UIActionSheet *)sheet textEditButtonWithValue:(id)value block:(void (^)(NSString *string))block; +- (void) addToSheet:(UIActionSheet *)sheet + multiEditButtonWithOptions:(NSArray *)options + block:(void (^)(NSUInteger index))block; + +// Value can be NSString or NSNumber +- (NSString *)stringFromValue:(id)value; +- (NSString *)stringFromUserStatus:(OCTToxUserStatus)status; +- (NSString *)stringFromConnectionStatus:(OCTToxConnectionStatus)status; + +// Shows error if it isn't nil. +- (void)maybeShowError:(NSError *)error; + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTTableViewController.m b/local_pod_repo/objcTox/iOSDemo/OCTTableViewController.m new file mode 100644 index 0000000..91e948f --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTTableViewController.m @@ -0,0 +1,203 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import +#import + +#import "OCTTableViewController.h" + +static NSString *const kUITableViewCellIdentifier = @"kUITableViewCellIdentifier"; + +@interface OCTTableViewController () + +@property (strong, nonatomic) UITableView *tableView; +@property (strong, nonatomic) id manager; + +@end + +@implementation OCTTableViewController + +#pragma mark - Lifecycle + +- (instancetype)initWithManager:(id)manager +{ + self = [super init]; + + if (! self) { + return nil; + } + + _manager = manager; + + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; + self.tableView.delegate = self; + self.tableView.dataSource = self; + self.tableView.rowHeight = UITableViewAutomaticDimension; + self.tableView.estimatedRowHeight = 44.0; + [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kUITableViewCellIdentifier]; + [self.view addSubview:self.tableView]; + + [self installConstraints]; +} + +#pragma mark - Public + +- (UITableViewCell *)cellForIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kUITableViewCellIdentifier + forIndexPath:indexPath]; + cell.textLabel.numberOfLines = 0; + + return cell; +} + +- (void)showActionSheet:(void (^)(UIActionSheet *sheet))block +{ + if (! block) { + return; + } + + UIActionSheet *sheet = [UIActionSheet bk_actionSheetWithTitle:@"Select action"]; + [sheet bk_setCancelButtonWithTitle:@"Cancel" handler:nil]; + + block(sheet); + + [sheet showInView:self.view]; +} + +- (void)addToSheet:(UIActionSheet *)sheet copyButtonWithValue:(id)value +{ + [sheet bk_addButtonWithTitle:@"Copy" handler:^{ + UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; + pasteboard.string = [self stringFromValue:value]; + }]; +} + +- (void)addToSheet:(UIActionSheet *)sheet textEditButtonWithValue:(id)value block:(void (^)(NSString *string))block +{ + NSParameterAssert(block); + + __weak OCTTableViewController *weakSelf = self; + + [sheet bk_addButtonWithTitle:@"Edit" handler:^{ + UIAlertView *alert = [UIAlertView bk_alertViewWithTitle:@"" message:nil]; + + alert.alertViewStyle = UIAlertViewStylePlainTextInput; + [[alert textFieldAtIndex:0] setText:[self stringFromValue:value]]; + + [alert bk_addButtonWithTitle:@"OK" handler:^{ + block([alert textFieldAtIndex:0].text); + + [weakSelf.tableView reloadData]; + }]; + + [alert bk_setCancelButtonWithTitle:@"Cancel" handler:nil]; + + [alert show]; + }]; +} + +- (void) addToSheet:(UIActionSheet *)sheet + multiEditButtonWithOptions:(NSArray *)options + block:(void (^)(NSUInteger index))block +{ + NSParameterAssert(block); + + __weak OCTTableViewController *weakSelf = self; + + [sheet bk_addButtonWithTitle:@"Edit" handler:^{ + UIActionSheet *editSheet = [UIActionSheet bk_actionSheetWithTitle:@"Select action"]; + + for (NSUInteger index = 0; index < options.count; index++) { + [editSheet bk_addButtonWithTitle:options[index] handler:^{ + block(index); + [weakSelf.tableView reloadData]; + }]; + } + + [editSheet bk_setCancelButtonWithTitle:@"Cancel" handler:nil]; + [editSheet showInView:weakSelf.view]; + }]; +} +- (NSString *)stringFromValue:(id)value +{ + if ([value isKindOfClass:[NSString class]]) { + return value; + } + + if ([value isKindOfClass:[NSNumber class]]) { + return [NSString stringWithFormat:@"%@", value]; + } + + return nil; +} + +- (NSString *)stringFromUserStatus:(OCTToxUserStatus)status +{ + switch (status) { + case OCTToxUserStatusNone: + return @"None"; + case OCTToxUserStatusAway: + return @"Away"; + case OCTToxUserStatusBusy: + return @"Busy"; + } +} + +- (NSString *)stringFromConnectionStatus:(OCTToxConnectionStatus)status +{ + switch (status) { + case OCTToxConnectionStatusNone: + return @"None"; + case OCTToxConnectionStatusTCP: + return @"TCP"; + case OCTToxConnectionStatusUDP: + return @"UDP"; + } +} + +- (void)maybeShowError:(NSError *)error +{ + if (! error) { + return; + } + + UIAlertView *alert = [UIAlertView bk_alertViewWithTitle:error.localizedDescription + message:error.localizedFailureReason]; + [alert bk_setCancelButtonWithTitle:@"OK" handler:nil]; + [alert show]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + NSAssert(NO, @"Implement in subclass"); + return 0; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSAssert(NO, @"Implement in subclass"); + return nil; +} + +#pragma mark - Private + +- (void)installConstraints +{ + [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.view); + }]; +} + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTUserViewController.h b/local_pod_repo/objcTox/iOSDemo/OCTUserViewController.h new file mode 100644 index 0000000..ab096c3 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTUserViewController.h @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTTableViewController.h" + +@interface OCTUserViewController : OCTTableViewController + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTUserViewController.m b/local_pod_repo/objcTox/iOSDemo/OCTUserViewController.m new file mode 100644 index 0000000..9e01058 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTUserViewController.m @@ -0,0 +1,189 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +#import "OCTUserViewController.h" +#import "OCTStartDemoViewController.h" +#import "AppDelegate.h" +#import "OCTSubmanagerUser.h" + +typedef NS_ENUM(NSUInteger, Row) { + RowConnectionStatus, + RowAddress, + RowPublicKey, + RowNospam, + RowStatus, + RowName, + RowStatusMessage, + RowLogOut, +}; + +@interface OCTUserViewController () + +@property (strong, nonatomic) NSArray *userData; + +@end + +@implementation OCTUserViewController + +#pragma mark - Lifecycle + +- (instancetype)initWithManager:(id)manager +{ + self = [super initWithManager:manager]; + + if (! self) { + return nil; + } + + manager.user.delegate = self; + + _userData = @[ + @(RowConnectionStatus), + @(RowAddress), + @(RowPublicKey), + @(RowNospam), + @(RowStatus), + @(RowName), + @(RowStatusMessage), + @(RowLogOut), + ]; + + self.title = @"User"; + + return self; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + __weak OCTUserViewController *weakSelf = self; + + [self showActionSheet:^(UIActionSheet *sheet) { + switch (indexPath.row) { + case RowConnectionStatus: + // nop + break; + case RowAddress: + [weakSelf addToSheet:sheet copyButtonWithValue:weakSelf.manager.user.userAddress]; + break; + case RowPublicKey: + [weakSelf addToSheet:sheet copyButtonWithValue:weakSelf.manager.user.publicKey]; + break; + case RowNospam: + { + [weakSelf addToSheet:sheet copyButtonWithValue:@(weakSelf.manager.user.nospam)]; + [weakSelf addToSheet:sheet textEditButtonWithValue:@(weakSelf.manager.user.nospam) block:^(NSString *string) { + weakSelf.manager.user.nospam = (OCTToxNoSpam)[string integerValue]; + }]; + break; + } + case RowStatus: + { + [weakSelf addToSheet:sheet multiEditButtonWithOptions:@[ + [weakSelf stringFromUserStatus:0], + [weakSelf stringFromUserStatus:1], + [weakSelf stringFromUserStatus:2], + + ] block:^(NSUInteger index) { + weakSelf.manager.user.userStatus = index; + }]; + break; + } + case RowName: + { + [weakSelf addToSheet:sheet copyButtonWithValue:weakSelf.manager.user.userName]; + [weakSelf addToSheet:sheet textEditButtonWithValue:weakSelf.manager.user.userName block:^(NSString *string) { + NSError *error; + [weakSelf.manager.user setUserName:string error:&error]; + [self maybeShowError:error]; + }]; + break; + } + case RowStatusMessage: + { + [weakSelf addToSheet:sheet copyButtonWithValue:weakSelf.manager.user.userStatusMessage]; + [weakSelf addToSheet:sheet textEditButtonWithValue:weakSelf.manager.user.userStatusMessage block:^(NSString *string) { + NSError *error; + [weakSelf.manager.user setUserStatusMessage:string error:&error]; + [self maybeShowError:error]; + }]; + break; + } + case RowLogOut: + { + [sheet bk_addButtonWithTitle:@"Log out" handler:^{ + [weakSelf logOut]; + }]; + break; + } + } + }]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return self.userData.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [self cellForIndexPath:indexPath]; + + Row row = indexPath.row; + + switch (row) { + case RowConnectionStatus: + cell.textLabel.text = [NSString stringWithFormat:@"connectionStatus: %@", + [self stringFromConnectionStatus:self.manager.user.connectionStatus]]; + break; + case RowAddress: + cell.textLabel.text = [NSString stringWithFormat:@"userAddress: %@", self.manager.user.userAddress]; + break; + case RowPublicKey: + cell.textLabel.text = [NSString stringWithFormat:@"publicKey: %@", self.manager.user.publicKey]; + break; + case RowNospam: + cell.textLabel.text = [NSString stringWithFormat:@"nospam: %u", self.manager.user.nospam]; + break; + case RowStatus: + cell.textLabel.text = [NSString stringWithFormat:@"userStatus: %@", + [self stringFromUserStatus:self.manager.user.userStatus]]; + break; + case RowName: + cell.textLabel.text = [NSString stringWithFormat:@"userName: %@", self.manager.user.userName]; + break; + case RowStatusMessage: + cell.textLabel.text = [NSString stringWithFormat:@"userStatusMessage: %@", self.manager.user.userStatusMessage]; + break; + case RowLogOut: + cell.textLabel.text = @"Log Out"; + break; + } + + return cell; +} + +#pragma mark - OCTSubmanagerUserDelegate + +- (void)submanagerUser:(id)submanager connectionStatusUpdate:(OCTToxConnectionStatus)connectionStatus +{ + [self.tableView reloadData]; +} + +#pragma mark - Private + +- (void)logOut +{ + AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate; + delegate.window.rootViewController = [OCTStartDemoViewController new]; +} + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTVideoViewController.h b/local_pod_repo/objcTox/iOSDemo/OCTVideoViewController.h new file mode 100644 index 0000000..3200c1a --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTVideoViewController.h @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import + +@protocol OCTSubmanagerCalls; +@class OCTCall; + +@interface OCTVideoViewController : UIViewController + +- (instancetype)initWithCallManager:(id)manager call:(OCTCall *)call; + +@end diff --git a/local_pod_repo/objcTox/iOSDemo/OCTVideoViewController.m b/local_pod_repo/objcTox/iOSDemo/OCTVideoViewController.m new file mode 100644 index 0000000..5c2e87a --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/OCTVideoViewController.m @@ -0,0 +1,248 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import "OCTVideoViewController.h" +#import "OCTSubmanagerCalls.h" +#import "OCTTableViewController.h" + +static const CGFloat kEdgeInsets = 25.0; + +@interface OCTVideoViewController () + +@property (nonatomic, strong) id manager; +@property (nonatomic, strong) UIButton *menuActionButton; +@property (nonatomic, strong) UIView *previewView; +@property (nonatomic, weak) CALayer *previewLayer; +@property (nonatomic, strong) UIView *videoFeed; +@property (nonatomic, strong) OCTCall *call; +@end + +@implementation OCTVideoViewController + +- (instancetype)initWithCallManager:(id)manager call:(OCTCall *)call +{ + self = [super init]; + + if (! self) { + return nil; + } + + _manager = manager; + _call = call; + + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + [self createDismissVCButton]; + [self createPreviewView]; + [self createVideoFeedView]; + + [self createPreviewViewConstraints]; +} + +- (void)viewDidLayoutSubviews +{ + [self adjustPreviewLayer]; +} + +- (void)createVideoViewConstraints +{ + NSLayoutConstraint *videoViewTop = [NSLayoutConstraint constraintWithItem:self.videoFeed + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeTop + multiplier:1.0 + constant:kEdgeInsets]; + + NSLayoutConstraint *videoViewRight = [NSLayoutConstraint constraintWithItem:self.videoFeed + attribute:NSLayoutAttributeRight + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeRight + multiplier:1.0 + constant:-kEdgeInsets]; + + NSLayoutConstraint *videoViewLeft = [NSLayoutConstraint constraintWithItem:self.videoFeed + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeLeft + multiplier:1.0 + constant:kEdgeInsets]; + + NSLayoutConstraint *videoViewBottom = [NSLayoutConstraint constraintWithItem:self.videoFeed + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self.previewView + attribute:NSLayoutAttributeTop + multiplier:1.0 + constant:0]; + + [self.view addConstraints:@[videoViewBottom, videoViewLeft, videoViewRight, videoViewTop]]; +} + +- (void)createPreviewViewConstraints +{ + NSLayoutConstraint *previewViewBottom = [NSLayoutConstraint constraintWithItem:self.previewView + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeBottom + multiplier:1.0 + constant:-kEdgeInsets]; + + NSLayoutConstraint *previewViewLeft = [NSLayoutConstraint constraintWithItem:self.previewView + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeLeft + multiplier:1.0 + constant:kEdgeInsets]; + + NSLayoutConstraint *previewViewRight = [NSLayoutConstraint constraintWithItem:self.previewView + attribute:NSLayoutAttributeRight + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeRight + multiplier:1.0 + constant:-kEdgeInsets]; + + NSLayoutConstraint *previewViewHeight = [NSLayoutConstraint constraintWithItem:self.previewView + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeHeight + multiplier:0.5 + constant:-kEdgeInsets]; + + [self.view addConstraints:@[previewViewBottom, previewViewHeight, previewViewLeft, previewViewRight]]; +} + +- (void)createPreviewView +{ + self.previewView = [UIView new]; + self.previewView.userInteractionEnabled = NO; + self.previewView.backgroundColor = [UIColor blackColor]; + self.previewView.translatesAutoresizingMaskIntoConstraints = NO; + + [self.view addSubview:self.previewView]; +} + +- (void)adjustPreviewLayer +{ + if (! self.previewLayer) { + __weak OCTVideoViewController *weakSelf = self; + + [self.previewView.layer addSublayer:self.previewLayer]; + [self.manager getVideoCallPreview:^(CALayer *layer) { + OCTVideoViewController *strongSelf = weakSelf; + [strongSelf.previewView.layer addSublayer:layer]; + strongSelf.previewLayer = layer; + strongSelf.previewLayer.frame = strongSelf.previewView.bounds; + }]; + } + else { + self.previewLayer.frame = self.previewView.bounds; + } +} + +- (void)createVideoFeedView +{ + self.videoFeed = [self.manager videoFeed]; + self.videoFeed.translatesAutoresizingMaskIntoConstraints = NO; + self.videoFeed.userInteractionEnabled = NO; + [self.view addSubview:self.videoFeed]; + [self createVideoViewConstraints]; +} + +- (void)createDismissVCButton +{ + self.menuActionButton = [[UIButton alloc] initWithFrame:self.view.bounds]; + self.menuActionButton.backgroundColor = [UIColor clearColor]; + [self.menuActionButton addTarget:self + action:@selector(showActionDialog) + forControlEvents:UIControlEventTouchUpInside]; + + [self.view addSubview:self.menuActionButton]; +} + +- (void)showActionDialog +{ + __weak OCTVideoViewController *weakSelf = self; + + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Video" + message:@"actions" + preferredStyle:UIAlertControllerStyleActionSheet]; + + UIAlertAction *stopSendingVideoAction = [UIAlertAction actionWithTitle:@"Stop sending video" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [weakSelf stopSendingVideo]; + }]; + + UIAlertAction *startSendingVideoAction = [UIAlertAction actionWithTitle:@"Start sending video" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [weakSelf startSendingVideo]; + }]; + + UIAlertAction *switchToRearAction = [UIAlertAction actionWithTitle:@"Use rear camera" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [weakSelf switchToBack]; + }]; + + UIAlertAction *switchToFrontAction = [UIAlertAction actionWithTitle:@"Use front camera" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [weakSelf switchToFront]; + }]; + + UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:@"Dismiss view" + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction *action) { + [weakSelf dismissViewButtonPressed]; + }]; + + [alertController addAction:stopSendingVideoAction]; + [alertController addAction:startSendingVideoAction]; + [alertController addAction:switchToRearAction]; + [alertController addAction:switchToFrontAction]; + [alertController addAction:dismissAction]; + + [self presentViewController:alertController animated:YES completion:nil]; +} + +#pragma mark - Touch actions + +- (void)dismissViewButtonPressed +{ + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)stopSendingVideo +{ + [self.manager enableVideoSending:NO forCall:self.call error:nil]; +} + +- (void)startSendingVideo +{ + [self.manager enableVideoSending:YES forCall:self.call error:nil]; +} + +- (void)switchToFront +{ + [self.manager switchToCameraFront:YES error:nil]; +} + +- (void)switchToBack +{ + [self.manager switchToCameraFront:NO error:nil]; +} +@end diff --git a/local_pod_repo/objcTox/iOSDemo/main.m b/local_pod_repo/objcTox/iOSDemo/main.m new file mode 100644 index 0000000..2645bb7 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemo/main.m @@ -0,0 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#import +#import "AppDelegate.h" + +int main(int argc, char *argv[]) +{ + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/local_pod_repo/objcTox/iOSDemoTests/Info.plist b/local_pod_repo/objcTox/iOSDemoTests/Info.plist new file mode 100644 index 0000000..74d0101 --- /dev/null +++ b/local_pod_repo/objcTox/iOSDemoTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + me.dvor.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/local_pod_repo/objcTox/install.sh b/local_pod_repo/objcTox/install.sh new file mode 100755 index 0000000..16af63c --- /dev/null +++ b/local_pod_repo/objcTox/install.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +bundle install + +bundle exec pod install diff --git a/local_pod_repo/objcTox/objcTox.podspec b/local_pod_repo/objcTox/objcTox.podspec new file mode 100644 index 0000000..b3421b4 --- /dev/null +++ b/local_pod_repo/objcTox/objcTox.podspec @@ -0,0 +1,37 @@ +# +# Be sure to run `pod lib lint objcTox.podspec' to ensure this is a +# valid spec and remove all comments before submitting the spec. +# +# Any lines starting with a # are optional, but encouraged +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = "objcTox" + s.version = "0.7.8" + s.summary = "Objective-C wrapper for Tox" + s.homepage = "https://github.com/Zoxcore/objcTox" + s.license = "MIT" + s.author = { "Dmytro Vorobiov" => "d@dvor.me" } + s.source = { + :git => "https://github.com/Zoxcore/objcTox.git", + :tag => s.version.to_s, + :submodules => false + } + + s.ios.deployment_target = '8.0' + s.osx.deployment_target = '10.9' + s.requires_arc = true + + s.source_files = 'Classes/**/*.{m,h}' + s.public_header_files = 'Classes/Public/**/*.h' + s.dependency 'toxcore', '~> 0.2.12' + s.dependency 'TPCircularBuffer', '~> 0.0.1' + s.dependency 'CocoaLumberjack', '1.9.2' + s.dependency 'Realm', '10.36.0' + + s.resource_bundle = { + 'objcTox' => 'Classes/Public/Manager/nodes.json' + } +end diff --git a/local_pod_repo/objcTox/objcTox.xcodeproj/project.pbxproj b/local_pod_repo/objcTox/objcTox.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a2fb762 --- /dev/null +++ b/local_pod_repo/objcTox/objcTox.xcodeproj/project.pbxproj @@ -0,0 +1,1922 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 112779D41B9B6F0700E475B4 /* OCTToxEncryptSaveTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 112779D31B9B6F0700E475B4 /* OCTToxEncryptSaveTests.m */; }; + 112779D51B9B6F0B00E475B4 /* OCTToxEncryptSaveTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 112779D31B9B6F0700E475B4 /* OCTToxEncryptSaveTests.m */; }; + 113187611DD683E400E6FAA2 /* OCTSendMessageOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 113187601DD683E400E6FAA2 /* OCTSendMessageOperation.m */; }; + 113187621DD683E400E6FAA2 /* OCTSendMessageOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 113187601DD683E400E6FAA2 /* OCTSendMessageOperation.m */; }; + 113187631DD683E400E6FAA2 /* OCTSendMessageOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 113187601DD683E400E6FAA2 /* OCTSendMessageOperation.m */; }; + 113187641DD683E400E6FAA2 /* OCTSendMessageOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 113187601DD683E400E6FAA2 /* OCTSendMessageOperation.m */; }; + 116634F91C80C7280072C980 /* nodes.json in Resources */ = {isa = PBXBuildFile; fileRef = 116634F81C80C7280072C980 /* nodes.json */; }; + 116634FA1C80C7280072C980 /* nodes.json in Resources */ = {isa = PBXBuildFile; fileRef = 116634F81C80C7280072C980 /* nodes.json */; }; + 116634FB1C80C7280072C980 /* nodes.json in Resources */ = {isa = PBXBuildFile; fileRef = 116634F81C80C7280072C980 /* nodes.json */; }; + 116634FC1C80C7280072C980 /* nodes.json in Resources */ = {isa = PBXBuildFile; fileRef = 116634F81C80C7280072C980 /* nodes.json */; }; + 1183BC691C9EBBE5000CD310 /* OCTFileUploadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC681C9EBBE5000CD310 /* OCTFileUploadOperation.m */; }; + 1183BC6A1C9EBBE5000CD310 /* OCTFileUploadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC681C9EBBE5000CD310 /* OCTFileUploadOperation.m */; }; + 1183BC6B1C9EBBE5000CD310 /* OCTFileUploadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC681C9EBBE5000CD310 /* OCTFileUploadOperation.m */; }; + 1183BC6C1C9EBBE5000CD310 /* OCTFileUploadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC681C9EBBE5000CD310 /* OCTFileUploadOperation.m */; }; + 1183BC7C1CA02755000CD310 /* OCTFilePathInput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC7B1CA02755000CD310 /* OCTFilePathInput.m */; }; + 1183BC7D1CA02755000CD310 /* OCTFilePathInput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC7B1CA02755000CD310 /* OCTFilePathInput.m */; }; + 1183BC7E1CA02755000CD310 /* OCTFilePathInput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC7B1CA02755000CD310 /* OCTFilePathInput.m */; }; + 1183BC7F1CA02755000CD310 /* OCTFilePathInput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC7B1CA02755000CD310 /* OCTFilePathInput.m */; }; + 1183BC831CA02AA9000CD310 /* OCTFilePathOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC821CA02AA9000CD310 /* OCTFilePathOutput.m */; }; + 1183BC841CA02AA9000CD310 /* OCTFilePathOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC821CA02AA9000CD310 /* OCTFilePathOutput.m */; }; + 1183BC851CA02AA9000CD310 /* OCTFilePathOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC821CA02AA9000CD310 /* OCTFilePathOutput.m */; }; + 1183BC861CA02AA9000CD310 /* OCTFilePathOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC821CA02AA9000CD310 /* OCTFilePathOutput.m */; }; + 1183BC891CA035CD000CD310 /* OCTFileDataInput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC881CA035CD000CD310 /* OCTFileDataInput.m */; }; + 1183BC8A1CA035CD000CD310 /* OCTFileDataInput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC881CA035CD000CD310 /* OCTFileDataInput.m */; }; + 1183BC8B1CA035CD000CD310 /* OCTFileDataInput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC881CA035CD000CD310 /* OCTFileDataInput.m */; }; + 1183BC8C1CA035CD000CD310 /* OCTFileDataInput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC881CA035CD000CD310 /* OCTFileDataInput.m */; }; + 1183BC8F1CA036AA000CD310 /* OCTFileDataOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC8E1CA036AA000CD310 /* OCTFileDataOutput.m */; }; + 1183BC901CA036AA000CD310 /* OCTFileDataOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC8E1CA036AA000CD310 /* OCTFileDataOutput.m */; }; + 1183BC911CA036AA000CD310 /* OCTFileDataOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC8E1CA036AA000CD310 /* OCTFileDataOutput.m */; }; + 1183BC921CA036AA000CD310 /* OCTFileDataOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC8E1CA036AA000CD310 /* OCTFileDataOutput.m */; }; + 1183BC951CA076BC000CD310 /* NSError+OCTFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC941CA076BC000CD310 /* NSError+OCTFile.m */; }; + 1183BC961CA076BC000CD310 /* NSError+OCTFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC941CA076BC000CD310 /* NSError+OCTFile.m */; }; + 1183BC971CA076BC000CD310 /* NSError+OCTFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC941CA076BC000CD310 /* NSError+OCTFile.m */; }; + 1183BC981CA076BC000CD310 /* NSError+OCTFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 1183BC941CA076BC000CD310 /* NSError+OCTFile.m */; }; + 119B13E51B9AF5A2006DA6FF /* OCTToxEncryptSave.m in Sources */ = {isa = PBXBuildFile; fileRef = 119B13E41B9AF5A2006DA6FF /* OCTToxEncryptSave.m */; }; + 119B13E61B9AF5A2006DA6FF /* OCTToxEncryptSave.m in Sources */ = {isa = PBXBuildFile; fileRef = 119B13E41B9AF5A2006DA6FF /* OCTToxEncryptSave.m */; }; + 119B13E71B9AF5A2006DA6FF /* OCTToxEncryptSave.m in Sources */ = {isa = PBXBuildFile; fileRef = 119B13E41B9AF5A2006DA6FF /* OCTToxEncryptSave.m */; }; + 119B13E81B9AF5A2006DA6FF /* OCTToxEncryptSave.m in Sources */ = {isa = PBXBuildFile; fileRef = 119B13E41B9AF5A2006DA6FF /* OCTToxEncryptSave.m */; }; + 11AF88FB1C98976F00AD1D9F /* OCTFileDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 11AF88FA1C98976F00AD1D9F /* OCTFileDownloadOperation.m */; }; + 11AF88FC1C98976F00AD1D9F /* OCTFileDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 11AF88FA1C98976F00AD1D9F /* OCTFileDownloadOperation.m */; }; + 11AF88FD1C98976F00AD1D9F /* OCTFileDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 11AF88FA1C98976F00AD1D9F /* OCTFileDownloadOperation.m */; }; + 11AF88FE1C98976F00AD1D9F /* OCTFileDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 11AF88FA1C98976F00AD1D9F /* OCTFileDownloadOperation.m */; }; + 11AF89011C98C25A00AD1D9F /* OCTFileBaseOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 11AF89001C98C25A00AD1D9F /* OCTFileBaseOperation.m */; }; + 11AF89021C98C25A00AD1D9F /* OCTFileBaseOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 11AF89001C98C25A00AD1D9F /* OCTFileBaseOperation.m */; }; + 11AF89031C98C25A00AD1D9F /* OCTFileBaseOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 11AF89001C98C25A00AD1D9F /* OCTFileBaseOperation.m */; }; + 11AF89041C98C25A00AD1D9F /* OCTFileBaseOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 11AF89001C98C25A00AD1D9F /* OCTFileBaseOperation.m */; }; + 11AF89111C9D570000AD1D9F /* OCTFilesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 11AF890F1C9D570000AD1D9F /* OCTFilesViewController.m */; }; + 11AF89121C9D570000AD1D9F /* OCTFilesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 11AF89101C9D570000AD1D9F /* OCTFilesViewController.xib */; }; + 11BB6EAE1CC3930A00A531A8 /* OCTFileTools.m in Sources */ = {isa = PBXBuildFile; fileRef = 11BB6EAD1CC3930A00A531A8 /* OCTFileTools.m */; }; + 11BB6EAF1CC3930A00A531A8 /* OCTFileTools.m in Sources */ = {isa = PBXBuildFile; fileRef = 11BB6EAD1CC3930A00A531A8 /* OCTFileTools.m */; }; + 11BB6EB01CC3930A00A531A8 /* OCTFileTools.m in Sources */ = {isa = PBXBuildFile; fileRef = 11BB6EAD1CC3930A00A531A8 /* OCTFileTools.m */; }; + 11BB6EB11CC3930A00A531A8 /* OCTFileTools.m in Sources */ = {isa = PBXBuildFile; fileRef = 11BB6EAD1CC3930A00A531A8 /* OCTFileTools.m */; }; + 11BB6EB31CC3932B00A531A8 /* OCTFileToolsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 11BB6EB21CC3932B00A531A8 /* OCTFileToolsTests.m */; }; + 11BB6EB41CC3932B00A531A8 /* OCTFileToolsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 11BB6EB21CC3932B00A531A8 /* OCTFileToolsTests.m */; }; + 11D650D01B89225400C3DD23 /* OCTAudioEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650CF1B89225400C3DD23 /* OCTAudioEngine.m */; }; + 11D650D11B89225400C3DD23 /* OCTAudioEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650CF1B89225400C3DD23 /* OCTAudioEngine.m */; }; + 11D650D21B89225400C3DD23 /* OCTAudioEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650CF1B89225400C3DD23 /* OCTAudioEngine.m */; }; + 11D650D31B89225400C3DD23 /* OCTAudioEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650CF1B89225400C3DD23 /* OCTAudioEngine.m */; }; + 11D650D91B89226800C3DD23 /* OCTCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650D41B89226800C3DD23 /* OCTCall.m */; }; + 11D650DA1B89226800C3DD23 /* OCTCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650D41B89226800C3DD23 /* OCTCall.m */; }; + 11D650DB1B89226800C3DD23 /* OCTCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650D41B89226800C3DD23 /* OCTCall.m */; }; + 11D650DC1B89226800C3DD23 /* OCTCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650D41B89226800C3DD23 /* OCTCall.m */; }; + 11D650DD1B89226800C3DD23 /* OCTCall+Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650D61B89226800C3DD23 /* OCTCall+Utilities.m */; }; + 11D650DE1B89226800C3DD23 /* OCTCall+Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650D61B89226800C3DD23 /* OCTCall+Utilities.m */; }; + 11D650DF1B89226800C3DD23 /* OCTCall+Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650D61B89226800C3DD23 /* OCTCall+Utilities.m */; }; + 11D650E01B89226800C3DD23 /* OCTCall+Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650D61B89226800C3DD23 /* OCTCall+Utilities.m */; }; + 11D650E11B89226800C3DD23 /* OCTCallTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650D81B89226800C3DD23 /* OCTCallTimer.m */; }; + 11D650E21B89226800C3DD23 /* OCTCallTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650D81B89226800C3DD23 /* OCTCallTimer.m */; }; + 11D650E31B89226800C3DD23 /* OCTCallTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650D81B89226800C3DD23 /* OCTCallTimer.m */; }; + 11D650E41B89226800C3DD23 /* OCTCallTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650D81B89226800C3DD23 /* OCTCallTimer.m */; }; + 11D650E61B89227300C3DD23 /* OCTMessageCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650E51B89227300C3DD23 /* OCTMessageCall.m */; }; + 11D650E71B89227300C3DD23 /* OCTMessageCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650E51B89227300C3DD23 /* OCTMessageCall.m */; }; + 11D650E81B89227300C3DD23 /* OCTMessageCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650E51B89227300C3DD23 /* OCTMessageCall.m */; }; + 11D650E91B89227300C3DD23 /* OCTMessageCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650E51B89227300C3DD23 /* OCTMessageCall.m */; }; + 11D650EC1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650EA1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m */; }; + 11D650ED1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650EA1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m */; }; + 11D650EE1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650EA1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m */; }; + 11D650EF1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650EA1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m */; }; + 11D650F71B89229B00C3DD23 /* OCTPixelBufferPool.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650F21B89229B00C3DD23 /* OCTPixelBufferPool.m */; }; + 11D650F81B89229B00C3DD23 /* OCTPixelBufferPool.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650F21B89229B00C3DD23 /* OCTPixelBufferPool.m */; }; + 11D650F91B89229B00C3DD23 /* OCTPixelBufferPool.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650F21B89229B00C3DD23 /* OCTPixelBufferPool.m */; }; + 11D650FA1B89229B00C3DD23 /* OCTPixelBufferPool.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650F21B89229B00C3DD23 /* OCTPixelBufferPool.m */; }; + 11D650FB1B89229B00C3DD23 /* OCTVideoEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650F41B89229B00C3DD23 /* OCTVideoEngine.m */; }; + 11D650FC1B89229B00C3DD23 /* OCTVideoEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650F41B89229B00C3DD23 /* OCTVideoEngine.m */; }; + 11D650FD1B89229B00C3DD23 /* OCTVideoEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650F41B89229B00C3DD23 /* OCTVideoEngine.m */; }; + 11D650FE1B89229B00C3DD23 /* OCTVideoEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650F41B89229B00C3DD23 /* OCTVideoEngine.m */; }; + 11D650FF1B89229B00C3DD23 /* OCTVideoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650F61B89229B00C3DD23 /* OCTVideoView.m */; }; + 11D651001B89229B00C3DD23 /* OCTVideoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650F61B89229B00C3DD23 /* OCTVideoView.m */; }; + 11D651011B89229B00C3DD23 /* OCTVideoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650F61B89229B00C3DD23 /* OCTVideoView.m */; }; + 11D651021B89229B00C3DD23 /* OCTVideoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D650F61B89229B00C3DD23 /* OCTVideoView.m */; }; + 11D651061B8922AF00C3DD23 /* OCTToxAV.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651031B8922AF00C3DD23 /* OCTToxAV.m */; }; + 11D651071B8922AF00C3DD23 /* OCTToxAV.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651031B8922AF00C3DD23 /* OCTToxAV.m */; }; + 11D651081B8922AF00C3DD23 /* OCTToxAV.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651031B8922AF00C3DD23 /* OCTToxAV.m */; }; + 11D651091B8922AF00C3DD23 /* OCTToxAV.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651031B8922AF00C3DD23 /* OCTToxAV.m */; }; + 11D6510A1B8922AF00C3DD23 /* OCTToxAVConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651051B8922AF00C3DD23 /* OCTToxAVConstants.m */; }; + 11D6510B1B8922AF00C3DD23 /* OCTToxAVConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651051B8922AF00C3DD23 /* OCTToxAVConstants.m */; }; + 11D6510C1B8922AF00C3DD23 /* OCTToxAVConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651051B8922AF00C3DD23 /* OCTToxAVConstants.m */; }; + 11D6510D1B8922AF00C3DD23 /* OCTToxAVConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651051B8922AF00C3DD23 /* OCTToxAVConstants.m */; }; + 11D651181B89232200C3DD23 /* OCTCallsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651171B89232200C3DD23 /* OCTCallsViewController.m */; }; + 11D6511E1B89233A00C3DD23 /* OCTTabBarControllerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D6511D1B89233A00C3DD23 /* OCTTabBarControllerViewController.m */; }; + 11D651211B89234300C3DD23 /* OCTVideoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651201B89234300C3DD23 /* OCTVideoViewController.m */; }; + 11D651241B89236A00C3DD23 /* OCTAudioEngineTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651221B89236A00C3DD23 /* OCTAudioEngineTests.m */; }; + 11D651251B89236A00C3DD23 /* OCTAudioEngineTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651221B89236A00C3DD23 /* OCTAudioEngineTests.m */; }; + 11D651261B89236A00C3DD23 /* OCTCallTimerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651231B89236A00C3DD23 /* OCTCallTimerTests.m */; }; + 11D651271B89236A00C3DD23 /* OCTCallTimerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651231B89236A00C3DD23 /* OCTCallTimerTests.m */; }; + 11D651291B89237D00C3DD23 /* OCTSubmanagerCallsImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651281B89237D00C3DD23 /* OCTSubmanagerCallsImplTests.m */; }; + 11D6512A1B89237D00C3DD23 /* OCTSubmanagerCallsImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D651281B89237D00C3DD23 /* OCTSubmanagerCallsImplTests.m */; }; + 11D6512C1B89238900C3DD23 /* OCTToxAVTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D6512B1B89238900C3DD23 /* OCTToxAVTests.m */; }; + 11D6512D1B89238900C3DD23 /* OCTToxAVTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D6512B1B89238900C3DD23 /* OCTToxAVTests.m */; }; + 11D6512F1B89238F00C3DD23 /* OCTVideoEngineTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D6512E1B89238F00C3DD23 /* OCTVideoEngineTests.m */; }; + 11D651301B89238F00C3DD23 /* OCTVideoEngineTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D6512E1B89238F00C3DD23 /* OCTVideoEngineTests.m */; }; + 11E80D831B98C647008DFC47 /* OCTSettingsStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 11E80D821B98C647008DFC47 /* OCTSettingsStorageObject.m */; }; + 11E80D841B98C647008DFC47 /* OCTSettingsStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 11E80D821B98C647008DFC47 /* OCTSettingsStorageObject.m */; }; + 11E80D851B98C647008DFC47 /* OCTSettingsStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 11E80D821B98C647008DFC47 /* OCTSettingsStorageObject.m */; }; + 11E80D861B98C647008DFC47 /* OCTSettingsStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 11E80D821B98C647008DFC47 /* OCTSettingsStorageObject.m */; }; + 447C84AF29005A2116AADE2D /* libPods-iOSDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B6D6D28CCB11A29144A37A53 /* libPods-iOSDemo.a */; }; + 844ACC71B8A64F9A5412EDA3 /* libPods-OSXDemoTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 086B5D0EF83D1DB88AD12AB6 /* libPods-OSXDemoTests.a */; }; + 9C3F49F71CDCE81B005E2A61 /* RLMCollectionChange+IndexSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C3F49F61CDCE81B005E2A61 /* RLMCollectionChange+IndexSet.m */; }; + 9CB1F9591D5B671E00105858 /* OCTManagerFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB1F9581D5B671E00105858 /* OCTManagerFactory.m */; }; + 9CB1F95A1D5B671E00105858 /* OCTManagerFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB1F9581D5B671E00105858 /* OCTManagerFactory.m */; }; + 9CB1F95B1D5B671E00105858 /* OCTManagerFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB1F9581D5B671E00105858 /* OCTManagerFactory.m */; }; + 9CB1F95C1D5B671E00105858 /* OCTManagerFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB1F9581D5B671E00105858 /* OCTManagerFactory.m */; }; + 9CB44B641B84D8C1007FA7B6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44B631B84D8C1007FA7B6 /* main.m */; }; + 9CB44B671B84D8C1007FA7B6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44B661B84D8C1007FA7B6 /* AppDelegate.m */; }; + 9CB44B6F1B84D8C1007FA7B6 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9CB44B6E1B84D8C1007FA7B6 /* Images.xcassets */; }; + 9CB44B721B84D8C1007FA7B6 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9CB44B701B84D8C1007FA7B6 /* LaunchScreen.xib */; }; + 9CB44B8F1B84D91C007FA7B6 /* OCTChatsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44B841B84D91C007FA7B6 /* OCTChatsViewController.m */; }; + 9CB44B901B84D91C007FA7B6 /* OCTConversationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44B861B84D91C007FA7B6 /* OCTConversationViewController.m */; }; + 9CB44B911B84D91C007FA7B6 /* OCTFriendsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44B881B84D91C007FA7B6 /* OCTFriendsViewController.m */; }; + 9CB44B921B84D91C007FA7B6 /* OCTStartDemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44B8A1B84D91C007FA7B6 /* OCTStartDemoViewController.m */; }; + 9CB44B931B84D91C007FA7B6 /* OCTTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44B8C1B84D91C007FA7B6 /* OCTTableViewController.m */; }; + 9CB44B941B84D91C007FA7B6 /* OCTUserViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44B8E1B84D91C007FA7B6 /* OCTUserViewController.m */; }; + 9CB44BE11B84D9E1007FA7B6 /* OCTDefaultFileStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = E9E579DEF80DD05B8F30CA2822233B2B /* OCTDefaultFileStorage.m */; }; + 9CB44BE31B84D9E1007FA7B6 /* OCTManagerConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B75FA045E32B1417FDC20970961725C /* OCTManagerConfiguration.m */; }; + 9CB44BE41B84D9E1007FA7B6 /* OCTRealmManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 110EE0D91B387C3D00CC347A /* OCTRealmManager.m */; }; + 9CB44BE61B84D9E1007FA7B6 /* OCTChat.m in Sources */ = {isa = PBXBuildFile; fileRef = 72B2CBBB2987645B0CA4475581E7B86A /* OCTChat.m */; }; + 9CB44BE71B84D9E1007FA7B6 /* OCTFriend.m in Sources */ = {isa = PBXBuildFile; fileRef = ACBB7FE1C1F9FD951C5A5D6DFA12C7FC /* OCTFriend.m */; }; + 9CB44BE81B84D9E1007FA7B6 /* OCTFriendRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E81B002B36CF7385268F31A5263A10DF /* OCTFriendRequest.m */; }; + 9CB44BE91B84D9E1007FA7B6 /* OCTMessageAbstract.m in Sources */ = {isa = PBXBuildFile; fileRef = 5309FB4CA1007014A575D75CB9387C5C /* OCTMessageAbstract.m */; }; + 9CB44BEA1B84D9E1007FA7B6 /* OCTMessageFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 65A7B20C506A85C1668D26921218DFA5 /* OCTMessageFile.m */; }; + 9CB44BEB1B84D9E1007FA7B6 /* OCTMessageText.m in Sources */ = {isa = PBXBuildFile; fileRef = E02C2F73766CD67B62FDBB73841065AF /* OCTMessageText.m */; }; + 9CB44BEC1B84D9E1007FA7B6 /* OCTNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D497B51B72BE5700634B56 /* OCTNode.m */; }; + 9CB44BED1B84D9E1007FA7B6 /* OCTObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 11CD5AE81B386A4D00585194 /* OCTObject.m */; }; + 9CB44BEF1B84D9E1007FA7B6 /* OCTSubmanagerBootstrapImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 117D37D21B72B476006E885C /* OCTSubmanagerBootstrapImpl.m */; }; + 9CB44BF01B84D9E1007FA7B6 /* OCTSubmanagerChatsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F8DB9E80557EDCED72F1E0D5E868D9F /* OCTSubmanagerChatsImpl.m */; }; + 9CB44BF11B84D9E1007FA7B6 /* OCTSubmanagerFilesImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 11B4BAF01B11E2800001A96A /* OCTSubmanagerFilesImpl.m */; }; + 9CB44BF21B84D9E1007FA7B6 /* OCTSubmanagerFriendsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = A61DE127803013E8229FED6D1DADFCBD /* OCTSubmanagerFriendsImpl.m */; }; + 9CB44BF31B84D9E1007FA7B6 /* OCTSubmanagerObjectsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D6C9911B3B251000E21A35 /* OCTSubmanagerObjectsImpl.m */; }; + 9CB44BF41B84D9E1007FA7B6 /* OCTSubmanagerUserImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = C2F358725E9EF3F392D73A1ABBDD3DAB /* OCTSubmanagerUserImpl.m */; }; + 9CB44BF51B84D9E1007FA7B6 /* OCTManagerImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = AEAF02FD4B49A2DF3C13562D48C9FEA0 /* OCTManagerImpl.m */; }; + 9CB44BF61B84D9E1007FA7B6 /* OCTManagerConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = CE5DBC12869590BEAD637C9DF06567EF /* OCTManagerConstants.m */; }; + 9CB44BF71B84D9E1007FA7B6 /* OCTTox.m in Sources */ = {isa = PBXBuildFile; fileRef = B08EB0152D5B2993766DD654578C3493 /* OCTTox.m */; }; + 9CB44BF81B84D9E1007FA7B6 /* OCTToxConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 872468E740CD9DEC5E48ED9646696C62 /* OCTToxConstants.m */; }; + 9CB44BF91B84D9E1007FA7B6 /* OCTToxOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AED5AE89C3775595A525D3B45B80637 /* OCTToxOptions.m */; }; + 9CB44C071B84DBA3007FA7B6 /* OCTChat.m in Sources */ = {isa = PBXBuildFile; fileRef = 72B2CBBB2987645B0CA4475581E7B86A /* OCTChat.m */; }; + 9CB44C081B84DBA3007FA7B6 /* OCTFriend.m in Sources */ = {isa = PBXBuildFile; fileRef = ACBB7FE1C1F9FD951C5A5D6DFA12C7FC /* OCTFriend.m */; }; + 9CB44C091B84DBA3007FA7B6 /* OCTMessageAbstract.m in Sources */ = {isa = PBXBuildFile; fileRef = 5309FB4CA1007014A575D75CB9387C5C /* OCTMessageAbstract.m */; }; + 9CB44C0A1B84DBA3007FA7B6 /* OCTFriendRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E81B002B36CF7385268F31A5263A10DF /* OCTFriendRequest.m */; }; + 9CB44C0B1B84DBA3007FA7B6 /* OCTNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D497B51B72BE5700634B56 /* OCTNode.m */; }; + 9CB44C0C1B84DBA3007FA7B6 /* OCTObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 11CD5AE81B386A4D00585194 /* OCTObject.m */; }; + 9CB44C0E1B84DBA3007FA7B6 /* OCTMessageText.m in Sources */ = {isa = PBXBuildFile; fileRef = E02C2F73766CD67B62FDBB73841065AF /* OCTMessageText.m */; }; + 9CB44C0F1B84DBA3007FA7B6 /* OCTMessageFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 65A7B20C506A85C1668D26921218DFA5 /* OCTMessageFile.m */; }; + 9CB44C101B84DBA3007FA7B6 /* OCTManagerConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B75FA045E32B1417FDC20970961725C /* OCTManagerConfiguration.m */; }; + 9CB44C121B84DBA3007FA7B6 /* OCTDefaultFileStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = E9E579DEF80DD05B8F30CA2822233B2B /* OCTDefaultFileStorage.m */; }; + 9CB44C131B84DBA3007FA7B6 /* OCTRealmManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 110EE0D91B387C3D00CC347A /* OCTRealmManager.m */; }; + 9CB44C141B84DBA3007FA7B6 /* OCTSubmanagerChatsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F8DB9E80557EDCED72F1E0D5E868D9F /* OCTSubmanagerChatsImpl.m */; }; + 9CB44C151B84DBA3007FA7B6 /* OCTSubmanagerObjectsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D6C9911B3B251000E21A35 /* OCTSubmanagerObjectsImpl.m */; }; + 9CB44C171B84DBA3007FA7B6 /* OCTSubmanagerBootstrapImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 117D37D21B72B476006E885C /* OCTSubmanagerBootstrapImpl.m */; }; + 9CB44C181B84DBA3007FA7B6 /* OCTSubmanagerUserImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = C2F358725E9EF3F392D73A1ABBDD3DAB /* OCTSubmanagerUserImpl.m */; }; + 9CB44C191B84DBA3007FA7B6 /* OCTSubmanagerFriendsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = A61DE127803013E8229FED6D1DADFCBD /* OCTSubmanagerFriendsImpl.m */; }; + 9CB44C1A1B84DBA3007FA7B6 /* OCTSubmanagerFilesImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 11B4BAF01B11E2800001A96A /* OCTSubmanagerFilesImpl.m */; }; + 9CB44C1B1B84DBA3007FA7B6 /* OCTManagerImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = AEAF02FD4B49A2DF3C13562D48C9FEA0 /* OCTManagerImpl.m */; }; + 9CB44C1C1B84DBA3007FA7B6 /* OCTManagerConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = CE5DBC12869590BEAD637C9DF06567EF /* OCTManagerConstants.m */; }; + 9CB44C1D1B84DBA3007FA7B6 /* OCTToxConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 872468E740CD9DEC5E48ED9646696C62 /* OCTToxConstants.m */; }; + 9CB44C1E1B84DBA3007FA7B6 /* OCTToxOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AED5AE89C3775595A525D3B45B80637 /* OCTToxOptions.m */; }; + 9CB44C1F1B84DBA3007FA7B6 /* OCTTox.m in Sources */ = {isa = PBXBuildFile; fileRef = B08EB0152D5B2993766DD654578C3493 /* OCTTox.m */; }; + 9CB44C371B84DCCC007FA7B6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44C361B84DCCC007FA7B6 /* AppDelegate.m */; }; + 9CB44C391B84DCCC007FA7B6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44C381B84DCCC007FA7B6 /* main.m */; }; + 9CB44C3B1B84DCCC007FA7B6 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9CB44C3A1B84DCCC007FA7B6 /* Images.xcassets */; }; + 9CB44C3E1B84DCCC007FA7B6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9CB44C3C1B84DCCC007FA7B6 /* MainMenu.xib */; }; + 9CB44C4F1B84DCFB007FA7B6 /* OCTDefaultFileStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = E9E579DEF80DD05B8F30CA2822233B2B /* OCTDefaultFileStorage.m */; }; + 9CB44C511B84DCFB007FA7B6 /* OCTManagerConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B75FA045E32B1417FDC20970961725C /* OCTManagerConfiguration.m */; }; + 9CB44C521B84DCFB007FA7B6 /* OCTRealmManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 110EE0D91B387C3D00CC347A /* OCTRealmManager.m */; }; + 9CB44C541B84DCFB007FA7B6 /* OCTChat.m in Sources */ = {isa = PBXBuildFile; fileRef = 72B2CBBB2987645B0CA4475581E7B86A /* OCTChat.m */; }; + 9CB44C551B84DCFB007FA7B6 /* OCTFriend.m in Sources */ = {isa = PBXBuildFile; fileRef = ACBB7FE1C1F9FD951C5A5D6DFA12C7FC /* OCTFriend.m */; }; + 9CB44C561B84DCFB007FA7B6 /* OCTFriendRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E81B002B36CF7385268F31A5263A10DF /* OCTFriendRequest.m */; }; + 9CB44C571B84DCFB007FA7B6 /* OCTMessageAbstract.m in Sources */ = {isa = PBXBuildFile; fileRef = 5309FB4CA1007014A575D75CB9387C5C /* OCTMessageAbstract.m */; }; + 9CB44C581B84DCFB007FA7B6 /* OCTMessageFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 65A7B20C506A85C1668D26921218DFA5 /* OCTMessageFile.m */; }; + 9CB44C591B84DCFB007FA7B6 /* OCTMessageText.m in Sources */ = {isa = PBXBuildFile; fileRef = E02C2F73766CD67B62FDBB73841065AF /* OCTMessageText.m */; }; + 9CB44C5A1B84DCFB007FA7B6 /* OCTNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D497B51B72BE5700634B56 /* OCTNode.m */; }; + 9CB44C5B1B84DCFB007FA7B6 /* OCTObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 11CD5AE81B386A4D00585194 /* OCTObject.m */; }; + 9CB44C5D1B84DCFB007FA7B6 /* OCTSubmanagerBootstrapImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 117D37D21B72B476006E885C /* OCTSubmanagerBootstrapImpl.m */; }; + 9CB44C5E1B84DCFB007FA7B6 /* OCTSubmanagerChatsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F8DB9E80557EDCED72F1E0D5E868D9F /* OCTSubmanagerChatsImpl.m */; }; + 9CB44C5F1B84DCFB007FA7B6 /* OCTSubmanagerFilesImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 11B4BAF01B11E2800001A96A /* OCTSubmanagerFilesImpl.m */; }; + 9CB44C601B84DCFB007FA7B6 /* OCTSubmanagerFriendsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = A61DE127803013E8229FED6D1DADFCBD /* OCTSubmanagerFriendsImpl.m */; }; + 9CB44C611B84DCFB007FA7B6 /* OCTSubmanagerObjectsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D6C9911B3B251000E21A35 /* OCTSubmanagerObjectsImpl.m */; }; + 9CB44C621B84DCFB007FA7B6 /* OCTSubmanagerUserImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = C2F358725E9EF3F392D73A1ABBDD3DAB /* OCTSubmanagerUserImpl.m */; }; + 9CB44C631B84DCFB007FA7B6 /* OCTManagerImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = AEAF02FD4B49A2DF3C13562D48C9FEA0 /* OCTManagerImpl.m */; }; + 9CB44C641B84DCFB007FA7B6 /* OCTManagerConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = CE5DBC12869590BEAD637C9DF06567EF /* OCTManagerConstants.m */; }; + 9CB44C651B84DCFB007FA7B6 /* OCTTox.m in Sources */ = {isa = PBXBuildFile; fileRef = B08EB0152D5B2993766DD654578C3493 /* OCTTox.m */; }; + 9CB44C661B84DCFB007FA7B6 /* OCTToxConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 872468E740CD9DEC5E48ED9646696C62 /* OCTToxConstants.m */; }; + 9CB44C671B84DCFB007FA7B6 /* OCTToxOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AED5AE89C3775595A525D3B45B80637 /* OCTToxOptions.m */; }; + 9CB44C751B84DCFB007FA7B6 /* OCTDefaultFileStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = E9E579DEF80DD05B8F30CA2822233B2B /* OCTDefaultFileStorage.m */; }; + 9CB44C771B84DCFB007FA7B6 /* OCTManagerConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B75FA045E32B1417FDC20970961725C /* OCTManagerConfiguration.m */; }; + 9CB44C781B84DCFB007FA7B6 /* OCTRealmManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 110EE0D91B387C3D00CC347A /* OCTRealmManager.m */; }; + 9CB44C7A1B84DCFB007FA7B6 /* OCTChat.m in Sources */ = {isa = PBXBuildFile; fileRef = 72B2CBBB2987645B0CA4475581E7B86A /* OCTChat.m */; }; + 9CB44C7B1B84DCFB007FA7B6 /* OCTFriend.m in Sources */ = {isa = PBXBuildFile; fileRef = ACBB7FE1C1F9FD951C5A5D6DFA12C7FC /* OCTFriend.m */; }; + 9CB44C7C1B84DCFB007FA7B6 /* OCTFriendRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E81B002B36CF7385268F31A5263A10DF /* OCTFriendRequest.m */; }; + 9CB44C7D1B84DCFB007FA7B6 /* OCTMessageAbstract.m in Sources */ = {isa = PBXBuildFile; fileRef = 5309FB4CA1007014A575D75CB9387C5C /* OCTMessageAbstract.m */; }; + 9CB44C7E1B84DCFB007FA7B6 /* OCTMessageFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 65A7B20C506A85C1668D26921218DFA5 /* OCTMessageFile.m */; }; + 9CB44C7F1B84DCFB007FA7B6 /* OCTMessageText.m in Sources */ = {isa = PBXBuildFile; fileRef = E02C2F73766CD67B62FDBB73841065AF /* OCTMessageText.m */; }; + 9CB44C801B84DCFB007FA7B6 /* OCTNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D497B51B72BE5700634B56 /* OCTNode.m */; }; + 9CB44C811B84DCFB007FA7B6 /* OCTObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 11CD5AE81B386A4D00585194 /* OCTObject.m */; }; + 9CB44C831B84DCFB007FA7B6 /* OCTSubmanagerBootstrapImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 117D37D21B72B476006E885C /* OCTSubmanagerBootstrapImpl.m */; }; + 9CB44C841B84DCFB007FA7B6 /* OCTSubmanagerChatsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F8DB9E80557EDCED72F1E0D5E868D9F /* OCTSubmanagerChatsImpl.m */; }; + 9CB44C851B84DCFB007FA7B6 /* OCTSubmanagerFilesImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 11B4BAF01B11E2800001A96A /* OCTSubmanagerFilesImpl.m */; }; + 9CB44C861B84DCFB007FA7B6 /* OCTSubmanagerFriendsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = A61DE127803013E8229FED6D1DADFCBD /* OCTSubmanagerFriendsImpl.m */; }; + 9CB44C871B84DCFB007FA7B6 /* OCTSubmanagerObjectsImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 11D6C9911B3B251000E21A35 /* OCTSubmanagerObjectsImpl.m */; }; + 9CB44C881B84DCFB007FA7B6 /* OCTSubmanagerUserImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = C2F358725E9EF3F392D73A1ABBDD3DAB /* OCTSubmanagerUserImpl.m */; }; + 9CB44C891B84DCFB007FA7B6 /* OCTManagerImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = AEAF02FD4B49A2DF3C13562D48C9FEA0 /* OCTManagerImpl.m */; }; + 9CB44C8A1B84DCFB007FA7B6 /* OCTManagerConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = CE5DBC12869590BEAD637C9DF06567EF /* OCTManagerConstants.m */; }; + 9CB44C8B1B84DCFB007FA7B6 /* OCTTox.m in Sources */ = {isa = PBXBuildFile; fileRef = B08EB0152D5B2993766DD654578C3493 /* OCTTox.m */; }; + 9CB44C8C1B84DCFB007FA7B6 /* OCTToxConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 872468E740CD9DEC5E48ED9646696C62 /* OCTToxConstants.m */; }; + 9CB44C8D1B84DCFB007FA7B6 /* OCTToxOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AED5AE89C3775595A525D3B45B80637 /* OCTToxOptions.m */; }; + 9CB44CB21B84DF46007FA7B6 /* OCTChatTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44C9E1B84DF46007FA7B6 /* OCTChatTests.m */; }; + 9CB44CB31B84DF46007FA7B6 /* OCTChatTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44C9E1B84DF46007FA7B6 /* OCTChatTests.m */; }; + 9CB44CB41B84DF46007FA7B6 /* OCTDefaultFileStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44C9F1B84DF46007FA7B6 /* OCTDefaultFileStorageTests.m */; }; + 9CB44CB51B84DF46007FA7B6 /* OCTDefaultFileStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44C9F1B84DF46007FA7B6 /* OCTDefaultFileStorageTests.m */; }; + 9CB44CB81B84DF46007FA7B6 /* OCTFriendRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA11B84DF46007FA7B6 /* OCTFriendRequestTests.m */; }; + 9CB44CB91B84DF46007FA7B6 /* OCTFriendRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA11B84DF46007FA7B6 /* OCTFriendRequestTests.m */; }; + 9CB44CBA1B84DF46007FA7B6 /* OCTManagerConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA21B84DF46007FA7B6 /* OCTManagerConfigurationTests.m */; }; + 9CB44CBB1B84DF46007FA7B6 /* OCTManagerConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA21B84DF46007FA7B6 /* OCTManagerConfigurationTests.m */; }; + 9CB44CBC1B84DF46007FA7B6 /* OCTManagerImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA31B84DF46007FA7B6 /* OCTManagerImplTests.m */; }; + 9CB44CBD1B84DF46007FA7B6 /* OCTManagerImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA31B84DF46007FA7B6 /* OCTManagerImplTests.m */; }; + 9CB44CBE1B84DF46007FA7B6 /* OCTMessageAbstractTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA41B84DF46007FA7B6 /* OCTMessageAbstractTests.m */; }; + 9CB44CBF1B84DF46007FA7B6 /* OCTMessageAbstractTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA41B84DF46007FA7B6 /* OCTMessageAbstractTests.m */; }; + 9CB44CC01B84DF46007FA7B6 /* OCTObjectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA51B84DF46007FA7B6 /* OCTObjectTests.m */; }; + 9CB44CC11B84DF46007FA7B6 /* OCTObjectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA51B84DF46007FA7B6 /* OCTObjectTests.m */; }; + 9CB44CC21B84DF46007FA7B6 /* OCTRealmTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA71B84DF46007FA7B6 /* OCTRealmTests.m */; }; + 9CB44CC31B84DF46007FA7B6 /* OCTRealmTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA71B84DF46007FA7B6 /* OCTRealmTests.m */; }; + 9CB44CC61B84DF46007FA7B6 /* OCTSubmanagerBootstrapImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA91B84DF46007FA7B6 /* OCTSubmanagerBootstrapImplTests.m */; }; + 9CB44CC71B84DF46007FA7B6 /* OCTSubmanagerBootstrapImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CA91B84DF46007FA7B6 /* OCTSubmanagerBootstrapImplTests.m */; }; + 9CB44CC81B84DF46007FA7B6 /* OCTSubmanagerChatsImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CAA1B84DF46007FA7B6 /* OCTSubmanagerChatsImplTests.m */; }; + 9CB44CC91B84DF46007FA7B6 /* OCTSubmanagerChatsImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CAA1B84DF46007FA7B6 /* OCTSubmanagerChatsImplTests.m */; }; + 9CB44CCA1B84DF46007FA7B6 /* OCTSubmanagerFilesImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CAB1B84DF46007FA7B6 /* OCTSubmanagerFilesImplTests.m */; }; + 9CB44CCB1B84DF46007FA7B6 /* OCTSubmanagerFilesImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CAB1B84DF46007FA7B6 /* OCTSubmanagerFilesImplTests.m */; }; + 9CB44CCC1B84DF46007FA7B6 /* OCTSubmanagerFriendsImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CAC1B84DF46007FA7B6 /* OCTSubmanagerFriendsImplTests.m */; }; + 9CB44CCD1B84DF46007FA7B6 /* OCTSubmanagerFriendsImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CAC1B84DF46007FA7B6 /* OCTSubmanagerFriendsImplTests.m */; }; + 9CB44CCE1B84DF46007FA7B6 /* OCTSubmanagerObjectsImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CAD1B84DF46007FA7B6 /* OCTSubmanagerObjectsImplTests.m */; }; + 9CB44CCF1B84DF46007FA7B6 /* OCTSubmanagerObjectsImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CAD1B84DF46007FA7B6 /* OCTSubmanagerObjectsImplTests.m */; }; + 9CB44CD01B84DF46007FA7B6 /* OCTSubmanagerUserImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CAE1B84DF46007FA7B6 /* OCTSubmanagerUserImplTests.m */; }; + 9CB44CD11B84DF46007FA7B6 /* OCTSubmanagerUserImplTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CAE1B84DF46007FA7B6 /* OCTSubmanagerUserImplTests.m */; }; + 9CB44CD21B84DF46007FA7B6 /* OCTToxTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CAF1B84DF46007FA7B6 /* OCTToxTests.m */; }; + 9CB44CD31B84DF46007FA7B6 /* OCTToxTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CB44CAF1B84DF46007FA7B6 /* OCTToxTests.m */; }; + 9CF2F4091D5363420002E175 /* unencrypted-database.realm in Resources */ = {isa = PBXBuildFile; fileRef = 9CF2F4081D5363420002E175 /* unencrypted-database.realm */; }; + 9CF2F40A1D5363420002E175 /* unencrypted-database.realm in Resources */ = {isa = PBXBuildFile; fileRef = 9CF2F4081D5363420002E175 /* unencrypted-database.realm */; }; + E58B1AAF894CEF7C1E9D8364 /* libPods-OSXDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C1B17E748892445A436C846 /* libPods-OSXDemo.a */; }; + F009D9AA1C195D77008243EF /* OCTMainWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = F009D9A91C195D77008243EF /* OCTMainWindowController.m */; }; + F009D9AE1C195DBA008243EF /* OCTBootStrapViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F009D9AC1C195DBA008243EF /* OCTBootStrapViewController.m */; }; + F019291B1C2797D2001A5F45 /* OCTConversationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F01929191C2797D2001A5F45 /* OCTConversationViewController.m */; }; + F019291C1C2797D2001A5F45 /* OCTConversationViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F019291A1C2797D2001A5F45 /* OCTConversationViewController.xib */; }; + F02C7EB31C1CCF1200D144BD /* OCTFriendsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F02C7EB11C1CCF1200D144BD /* OCTFriendsViewController.m */; }; + F02C7EB41C1CCF1200D144BD /* OCTFriendsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F02C7EB21C1CCF1200D144BD /* OCTFriendsViewController.xib */; }; + F07992DA1C2E5B1900B0D267 /* OCTCallsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F07992D81C2E5B1900B0D267 /* OCTCallsViewController.m */; }; + F07992DB1C2E5B1900B0D267 /* OCTCallsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F07992D91C2E5B1900B0D267 /* OCTCallsViewController.xib */; }; + F09D0A461C1B9877008E74AF /* OCTUserViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F09D0A441C1B9877008E74AF /* OCTUserViewController.m */; }; + F09D0A471C1B9877008E74AF /* OCTUserViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F09D0A451C1B9877008E74AF /* OCTUserViewController.xib */; }; + F0BB04E375482D8C4AF2C5C1 /* libPods-iOSDemoTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C38C5849F5CBA7BDADFD685 /* libPods-iOSDemoTests.a */; }; + F0EAD8431C1A38DA0056587D /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0EAD8421C1A38DA0056587D /* MainWindow.xib */; }; + F0EAD8451C1A58B20056587D /* OCTBootStrap.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0EAD8441C1A58B20056587D /* OCTBootStrap.xib */; }; + F50269671C31CB0700E05351 /* OCTAudioQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F50269661C31CB0700E05351 /* OCTAudioQueueTests.m */; }; + F50269681C31CB0700E05351 /* OCTAudioQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F50269661C31CB0700E05351 /* OCTAudioQueueTests.m */; }; + F5BF42781C2D1F75008283E0 /* OCTAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = F5F6FA1E1C268B5000607306 /* OCTAudioQueue.m */; }; + F5BF42791C2D1F7D008283E0 /* OCTAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = F5F6FA1E1C268B5000607306 /* OCTAudioQueue.m */; }; + F5F6FA1F1C268B5000607306 /* OCTAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = F5F6FA1E1C268B5000607306 /* OCTAudioQueue.m */; }; + F5F6FA201C268B5000607306 /* OCTAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = F5F6FA1E1C268B5000607306 /* OCTAudioQueue.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 9CB44B781B84D8C1007FA7B6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1A384FEB508C718A4A7522BB0A835B5F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9CB44B5E1B84D8C1007FA7B6; + remoteInfo = iOSDemo; + }; + 9CB44C441B84DCCC007FA7B6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1A384FEB508C718A4A7522BB0A835B5F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9CB44C301B84DCCC007FA7B6; + remoteInfo = OSXDemo; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 086B5D0EF83D1DB88AD12AB6 /* libPods-OSXDemoTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OSXDemoTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 110152851DC4A9D600044852 /* OCTManagerFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTManagerFactory.h; sourceTree = ""; }; + 110152861DC4ABF700044852 /* OCTManagerImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTManagerImpl.h; sourceTree = ""; }; + 110EE0D81B387C3D00CC347A /* OCTRealmManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTRealmManager.h; sourceTree = ""; }; + 110EE0D91B387C3D00CC347A /* OCTRealmManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTRealmManager.m; sourceTree = ""; }; + 1125DC491B2107E900C8DB98 /* OCTTox+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OCTTox+Private.h"; sourceTree = ""; }; + 112779D31B9B6F0700E475B4 /* OCTToxEncryptSaveTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTToxEncryptSaveTests.m; sourceTree = ""; }; + 1131875F1DD683E400E6FAA2 /* OCTSendMessageOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OCTSendMessageOperation.h; path = Messages/OCTSendMessageOperation.h; sourceTree = ""; }; + 113187601DD683E400E6FAA2 /* OCTSendMessageOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OCTSendMessageOperation.m; path = Messages/OCTSendMessageOperation.m; sourceTree = ""; }; + 115B6FBF1C9DD46700C65334 /* OCTSubmanagerFilesProgressSubscriber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerFilesProgressSubscriber.h; sourceTree = ""; }; + 115B6FC11C9DDB6D00C65334 /* OCTFileBaseOperation+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OCTFileBaseOperation+Private.h"; sourceTree = ""; }; + 116634F81C80C7280072C980 /* nodes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = nodes.json; sourceTree = ""; }; + 117D37CB1B72B284006E885C /* OCTSubmanagerProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerProtocol.h; sourceTree = ""; }; + 117D37CC1B72B45B006E885C /* OCTSubmanagerBootstrap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerBootstrap.h; sourceTree = ""; }; + 117D37CF1B72B46D006E885C /* OCTSubmanagerBootstrapImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerBootstrapImpl.h; sourceTree = ""; }; + 117D37D21B72B476006E885C /* OCTSubmanagerBootstrapImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerBootstrapImpl.m; sourceTree = ""; }; + 1183BC671C9EBBE5000CD310 /* OCTFileUploadOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFileUploadOperation.h; sourceTree = ""; }; + 1183BC681C9EBBE5000CD310 /* OCTFileUploadOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFileUploadOperation.m; sourceTree = ""; }; + 1183BC791CA026F0000CD310 /* OCTFileInputProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFileInputProtocol.h; sourceTree = ""; }; + 1183BC7A1CA02755000CD310 /* OCTFilePathInput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFilePathInput.h; sourceTree = ""; }; + 1183BC7B1CA02755000CD310 /* OCTFilePathInput.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFilePathInput.m; sourceTree = ""; }; + 1183BC801CA02AA1000CD310 /* OCTFileOutputProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFileOutputProtocol.h; sourceTree = ""; }; + 1183BC811CA02AA9000CD310 /* OCTFilePathOutput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFilePathOutput.h; sourceTree = ""; }; + 1183BC821CA02AA9000CD310 /* OCTFilePathOutput.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFilePathOutput.m; sourceTree = ""; }; + 1183BC871CA035CD000CD310 /* OCTFileDataInput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFileDataInput.h; sourceTree = ""; }; + 1183BC881CA035CD000CD310 /* OCTFileDataInput.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFileDataInput.m; sourceTree = ""; }; + 1183BC8D1CA036AA000CD310 /* OCTFileDataOutput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFileDataOutput.h; sourceTree = ""; }; + 1183BC8E1CA036AA000CD310 /* OCTFileDataOutput.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFileDataOutput.m; sourceTree = ""; }; + 1183BC931CA076BC000CD310 /* NSError+OCTFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+OCTFile.h"; sourceTree = ""; }; + 1183BC941CA076BC000CD310 /* NSError+OCTFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+OCTFile.m"; sourceTree = ""; }; + 119B13DE1B9AF58B006DA6FF /* OCTToxEncryptSave.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTToxEncryptSave.h; sourceTree = ""; }; + 119B13E41B9AF5A2006DA6FF /* OCTToxEncryptSave.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTToxEncryptSave.m; sourceTree = ""; }; + 119B13E91B9AF78F006DA6FF /* OCTToxEncryptSaveConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTToxEncryptSaveConstants.h; sourceTree = ""; }; + 11AF88F91C98976F00AD1D9F /* OCTFileDownloadOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFileDownloadOperation.h; sourceTree = ""; }; + 11AF88FA1C98976F00AD1D9F /* OCTFileDownloadOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFileDownloadOperation.m; sourceTree = ""; }; + 11AF88FF1C98C25A00AD1D9F /* OCTFileBaseOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFileBaseOperation.h; sourceTree = ""; }; + 11AF89001C98C25A00AD1D9F /* OCTFileBaseOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFileBaseOperation.m; sourceTree = ""; }; + 11AF890D1C9B57F900AD1D9F /* OCTLogging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTLogging.h; sourceTree = ""; }; + 11AF890E1C9D570000AD1D9F /* OCTFilesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFilesViewController.h; sourceTree = ""; }; + 11AF890F1C9D570000AD1D9F /* OCTFilesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFilesViewController.m; sourceTree = ""; }; + 11AF89101C9D570000AD1D9F /* OCTFilesViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OCTFilesViewController.xib; sourceTree = ""; }; + 11B4BAEF1B11E2530001A96A /* OCTSubmanagerFiles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerFiles.h; sourceTree = ""; }; + 11B4BAF01B11E2800001A96A /* OCTSubmanagerFilesImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerFilesImpl.m; sourceTree = ""; }; + 11B4BAF31B11E29A0001A96A /* OCTSubmanagerFilesImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerFilesImpl.h; sourceTree = ""; }; + 11BB6EAC1CC3930A00A531A8 /* OCTFileTools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFileTools.h; sourceTree = ""; }; + 11BB6EAD1CC3930A00A531A8 /* OCTFileTools.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFileTools.m; sourceTree = ""; }; + 11BB6EB21CC3932B00A531A8 /* OCTFileToolsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFileToolsTests.m; sourceTree = ""; }; + 11CD5AE51B386A3A00585194 /* OCTObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTObject.h; sourceTree = ""; }; + 11CD5AE81B386A4D00585194 /* OCTObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTObject.m; sourceTree = ""; }; + 11D497B41B72BE5700634B56 /* OCTNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTNode.h; sourceTree = ""; }; + 11D497B51B72BE5700634B56 /* OCTNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTNode.m; sourceTree = ""; }; + 11D650CE1B89225400C3DD23 /* OCTAudioEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTAudioEngine.h; sourceTree = ""; }; + 11D650CF1B89225400C3DD23 /* OCTAudioEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTAudioEngine.m; sourceTree = ""; }; + 11D650D41B89226800C3DD23 /* OCTCall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTCall.m; sourceTree = ""; }; + 11D650D51B89226800C3DD23 /* OCTCall+Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OCTCall+Utilities.h"; sourceTree = ""; }; + 11D650D61B89226800C3DD23 /* OCTCall+Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OCTCall+Utilities.m"; sourceTree = ""; }; + 11D650D71B89226800C3DD23 /* OCTCallTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTCallTimer.h; sourceTree = ""; }; + 11D650D81B89226800C3DD23 /* OCTCallTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTCallTimer.m; sourceTree = ""; }; + 11D650E51B89227300C3DD23 /* OCTMessageCall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTMessageCall.m; sourceTree = ""; }; + 11D650EA1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerCallsImpl.m; sourceTree = ""; }; + 11D650EB1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerCallsImpl.h; sourceTree = ""; }; + 11D650F11B89229B00C3DD23 /* OCTPixelBufferPool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTPixelBufferPool.h; sourceTree = ""; }; + 11D650F21B89229B00C3DD23 /* OCTPixelBufferPool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTPixelBufferPool.m; sourceTree = ""; }; + 11D650F31B89229B00C3DD23 /* OCTVideoEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTVideoEngine.h; sourceTree = ""; }; + 11D650F41B89229B00C3DD23 /* OCTVideoEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTVideoEngine.m; sourceTree = ""; }; + 11D650F51B89229B00C3DD23 /* OCTVideoView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTVideoView.h; sourceTree = ""; }; + 11D650F61B89229B00C3DD23 /* OCTVideoView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTVideoView.m; sourceTree = ""; }; + 11D651031B8922AF00C3DD23 /* OCTToxAV.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTToxAV.m; sourceTree = ""; }; + 11D651041B8922AF00C3DD23 /* OCTToxAV+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OCTToxAV+Private.h"; sourceTree = ""; }; + 11D651051B8922AF00C3DD23 /* OCTToxAVConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTToxAVConstants.m; sourceTree = ""; }; + 11D6510E1B8922C700C3DD23 /* OCTCall.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTCall.h; sourceTree = ""; }; + 11D6510F1B8922CF00C3DD23 /* OCTMessageCall.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTMessageCall.h; sourceTree = ""; }; + 11D651101B8922D900C3DD23 /* OCTView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTView.h; sourceTree = ""; }; + 11D651111B8922EC00C3DD23 /* OCTSubmanagerCalls.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerCalls.h; sourceTree = ""; }; + 11D651121B8922EC00C3DD23 /* OCTSubmanagerCallsDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerCallsDelegate.h; sourceTree = ""; }; + 11D651131B89230200C3DD23 /* OCTToxAV.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTToxAV.h; sourceTree = ""; }; + 11D651141B89230200C3DD23 /* OCTToxAVConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTToxAVConstants.h; sourceTree = ""; }; + 11D651151B89230200C3DD23 /* OCTToxAVDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTToxAVDelegate.h; sourceTree = ""; }; + 11D651161B89232200C3DD23 /* OCTCallsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTCallsViewController.h; sourceTree = ""; }; + 11D651171B89232200C3DD23 /* OCTCallsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTCallsViewController.m; sourceTree = ""; }; + 11D6511C1B89233A00C3DD23 /* OCTTabBarControllerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTTabBarControllerViewController.h; sourceTree = ""; }; + 11D6511D1B89233A00C3DD23 /* OCTTabBarControllerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTTabBarControllerViewController.m; sourceTree = ""; }; + 11D6511F1B89234300C3DD23 /* OCTVideoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTVideoViewController.h; sourceTree = ""; }; + 11D651201B89234300C3DD23 /* OCTVideoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTVideoViewController.m; sourceTree = ""; }; + 11D651221B89236A00C3DD23 /* OCTAudioEngineTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTAudioEngineTests.m; sourceTree = ""; }; + 11D651231B89236A00C3DD23 /* OCTCallTimerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTCallTimerTests.m; sourceTree = ""; }; + 11D651281B89237D00C3DD23 /* OCTSubmanagerCallsImplTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerCallsImplTests.m; sourceTree = ""; }; + 11D6512B1B89238900C3DD23 /* OCTToxAVTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTToxAVTests.m; sourceTree = ""; }; + 11D6512E1B89238F00C3DD23 /* OCTVideoEngineTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTVideoEngineTests.m; sourceTree = ""; }; + 11D651311B8924DA00C3DD23 /* OCTAudioEngine+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCTAudioEngine+Private.h"; sourceTree = ""; }; + 11D6C98E1B3B24F700E21A35 /* OCTSubmanagerObjects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerObjects.h; sourceTree = ""; }; + 11D6C9911B3B251000E21A35 /* OCTSubmanagerObjectsImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerObjectsImpl.m; sourceTree = ""; }; + 11D6C9941B3B252300E21A35 /* OCTSubmanagerObjectsImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerObjectsImpl.h; sourceTree = ""; }; + 11E80D811B98C647008DFC47 /* OCTSettingsStorageObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSettingsStorageObject.h; sourceTree = ""; }; + 11E80D821B98C647008DFC47 /* OCTSettingsStorageObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSettingsStorageObject.m; sourceTree = ""; }; + 11F6C4A51E2A4D9B0096F462 /* OCTToxOptions+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCTToxOptions+Private.h"; sourceTree = ""; }; + 1AED5AE89C3775595A525D3B45B80637 /* OCTToxOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTToxOptions.m; sourceTree = ""; }; + 1B75FA045E32B1417FDC20970961725C /* OCTManagerConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTManagerConfiguration.m; sourceTree = ""; }; + 1D66E7C409BC96AC7372B302BABF1D88 /* OCTTox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTTox.h; sourceTree = ""; }; + 1F210739EBF632594BBF9C51E585A589 /* OCTSubmanagerFriendsImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerFriendsImpl.h; sourceTree = ""; }; + 2369847240F2E8FD3B4AFD5BDDC2DCF2 /* OCTFileStorageProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFileStorageProtocol.h; sourceTree = ""; }; + 2CBE0C2E379DCF740AF5D469F096D309 /* OCTSubmanagerChats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerChats.h; sourceTree = ""; }; + 31BCEE5F6D309D5BA4FAE6E6E70C2CA7 /* OCTManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTManager.h; sourceTree = ""; }; + 4661B7235F65476C9C4E3255DB5BCE79 /* OCTDefaultFileStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTDefaultFileStorage.h; sourceTree = ""; }; + 468C7E16643FCCEB54CFF0414052EC3C /* OCTSubmanagerDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerDataSource.h; sourceTree = ""; }; + 49DADF58900503E712C710CF13F175DD /* OCTToxDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTToxDelegate.h; sourceTree = ""; }; + 4C1B17E748892445A436C846 /* libPods-OSXDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OSXDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5309FB4CA1007014A575D75CB9387C5C /* OCTMessageAbstract.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTMessageAbstract.m; sourceTree = ""; }; + 54FC7A218FD35DB03A172079 /* Pods-iOSDemoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSDemoTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-iOSDemoTests/Pods-iOSDemoTests.release.xcconfig"; sourceTree = ""; }; + 5F8DB9E80557EDCED72F1E0D5E868D9F /* OCTSubmanagerChatsImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerChatsImpl.m; sourceTree = ""; }; + 647688B7386EE74BA1E6359A822B2BE6 /* OCTManagerConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTManagerConstants.h; sourceTree = ""; }; + 65A7B20C506A85C1668D26921218DFA5 /* OCTMessageFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTMessageFile.m; sourceTree = ""; }; + 6733F3E1A4E00CC032F25870E9083F13 /* OCTToxConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTToxConstants.h; sourceTree = ""; }; + 67CE089BF824F5E312E1F2DA82200173 /* OCTSubmanagerChatsImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerChatsImpl.h; sourceTree = ""; }; + 6F97E8DCCDE81B906C0CCAFE /* Pods-iOSDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-iOSDemo/Pods-iOSDemo.release.xcconfig"; sourceTree = ""; }; + 72B2CBBB2987645B0CA4475581E7B86A /* OCTChat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTChat.m; sourceTree = ""; }; + 7C38C5849F5CBA7BDADFD685 /* libPods-iOSDemoTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-iOSDemoTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 80EDC251089F9EE3EA655215 /* Pods-OSXDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OSXDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-OSXDemo/Pods-OSXDemo.release.xcconfig"; sourceTree = ""; }; + 872468E740CD9DEC5E48ED9646696C62 /* OCTToxConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTToxConstants.m; sourceTree = ""; }; + 91FC63A06A08D2865DD4552BB2476EE8 /* OCTSubmanagerFriends.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerFriends.h; sourceTree = ""; }; + 9BB556421978E2471480B277 /* Pods-OSXDemoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OSXDemoTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-OSXDemoTests/Pods-OSXDemoTests.release.xcconfig"; sourceTree = ""; }; + 9C3F49F51CDCE81B005E2A61 /* RLMCollectionChange+IndexSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RLMCollectionChange+IndexSet.h"; sourceTree = ""; }; + 9C3F49F61CDCE81B005E2A61 /* RLMCollectionChange+IndexSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RLMCollectionChange+IndexSet.m"; sourceTree = ""; }; + 9CB1F9581D5B671E00105858 /* OCTManagerFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTManagerFactory.m; sourceTree = ""; }; + 9CB44B5F1B84D8C1007FA7B6 /* iOSDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 9CB44B621B84D8C1007FA7B6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9CB44B631B84D8C1007FA7B6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 9CB44B651B84D8C1007FA7B6 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 9CB44B661B84D8C1007FA7B6 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9CB44B6E1B84D8C1007FA7B6 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 9CB44B711B84D8C1007FA7B6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 9CB44B771B84D8C1007FA7B6 /* iOSDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 9CB44B7C1B84D8C1007FA7B6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9CB44B831B84D91C007FA7B6 /* OCTChatsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTChatsViewController.h; sourceTree = ""; }; + 9CB44B841B84D91C007FA7B6 /* OCTChatsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTChatsViewController.m; sourceTree = ""; }; + 9CB44B851B84D91C007FA7B6 /* OCTConversationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTConversationViewController.h; sourceTree = ""; }; + 9CB44B861B84D91C007FA7B6 /* OCTConversationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTConversationViewController.m; sourceTree = ""; }; + 9CB44B871B84D91C007FA7B6 /* OCTFriendsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFriendsViewController.h; sourceTree = ""; }; + 9CB44B881B84D91C007FA7B6 /* OCTFriendsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFriendsViewController.m; sourceTree = ""; }; + 9CB44B891B84D91C007FA7B6 /* OCTStartDemoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTStartDemoViewController.h; sourceTree = ""; }; + 9CB44B8A1B84D91C007FA7B6 /* OCTStartDemoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTStartDemoViewController.m; sourceTree = ""; }; + 9CB44B8B1B84D91C007FA7B6 /* OCTTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTTableViewController.h; sourceTree = ""; }; + 9CB44B8C1B84D91C007FA7B6 /* OCTTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTTableViewController.m; sourceTree = ""; }; + 9CB44B8D1B84D91C007FA7B6 /* OCTUserViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTUserViewController.h; sourceTree = ""; }; + 9CB44B8E1B84D91C007FA7B6 /* OCTUserViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTUserViewController.m; sourceTree = ""; }; + 9CB44C311B84DCCC007FA7B6 /* OSXDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OSXDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 9CB44C341B84DCCC007FA7B6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9CB44C351B84DCCC007FA7B6 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 9CB44C361B84DCCC007FA7B6 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9CB44C381B84DCCC007FA7B6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 9CB44C3A1B84DCCC007FA7B6 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 9CB44C3D1B84DCCC007FA7B6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 9CB44C431B84DCCC007FA7B6 /* OSXDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OSXDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 9CB44C481B84DCCC007FA7B6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9CB44C9D1B84DF46007FA7B6 /* OCTCAsserts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTCAsserts.h; sourceTree = ""; }; + 9CB44C9E1B84DF46007FA7B6 /* OCTChatTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTChatTests.m; sourceTree = ""; }; + 9CB44C9F1B84DF46007FA7B6 /* OCTDefaultFileStorageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTDefaultFileStorageTests.m; sourceTree = ""; }; + 9CB44CA11B84DF46007FA7B6 /* OCTFriendRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFriendRequestTests.m; sourceTree = ""; }; + 9CB44CA21B84DF46007FA7B6 /* OCTManagerConfigurationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTManagerConfigurationTests.m; sourceTree = ""; }; + 9CB44CA31B84DF46007FA7B6 /* OCTManagerImplTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTManagerImplTests.m; sourceTree = ""; }; + 9CB44CA41B84DF46007FA7B6 /* OCTMessageAbstractTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTMessageAbstractTests.m; sourceTree = ""; }; + 9CB44CA51B84DF46007FA7B6 /* OCTObjectTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTObjectTests.m; sourceTree = ""; }; + 9CB44CA61B84DF46007FA7B6 /* OCTRealmTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTRealmTests.h; sourceTree = ""; }; + 9CB44CA71B84DF46007FA7B6 /* OCTRealmTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTRealmTests.m; sourceTree = ""; }; + 9CB44CA91B84DF46007FA7B6 /* OCTSubmanagerBootstrapImplTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerBootstrapImplTests.m; sourceTree = ""; }; + 9CB44CAA1B84DF46007FA7B6 /* OCTSubmanagerChatsImplTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerChatsImplTests.m; sourceTree = ""; }; + 9CB44CAB1B84DF46007FA7B6 /* OCTSubmanagerFilesImplTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerFilesImplTests.m; sourceTree = ""; }; + 9CB44CAC1B84DF46007FA7B6 /* OCTSubmanagerFriendsImplTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerFriendsImplTests.m; sourceTree = ""; }; + 9CB44CAD1B84DF46007FA7B6 /* OCTSubmanagerObjectsImplTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerObjectsImplTests.m; sourceTree = ""; }; + 9CB44CAE1B84DF46007FA7B6 /* OCTSubmanagerUserImplTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerUserImplTests.m; sourceTree = ""; }; + 9CB44CAF1B84DF46007FA7B6 /* OCTToxTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTToxTests.m; sourceTree = ""; }; + 9CB71FBC1B386B2F00E3C1EF /* OCTChat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTChat.h; sourceTree = ""; }; + 9CB71FBD1B386B2F00E3C1EF /* OCTFriend.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTFriend.h; sourceTree = ""; }; + 9CB71FBE1B386B2F00E3C1EF /* OCTFriendRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTFriendRequest.h; sourceTree = ""; }; + 9CB71FBF1B386B2F00E3C1EF /* OCTMessageAbstract.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTMessageAbstract.h; sourceTree = ""; }; + 9CB71FC01B386B2F00E3C1EF /* OCTMessageFile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTMessageFile.h; sourceTree = ""; }; + 9CB71FC11B386B2F00E3C1EF /* OCTMessageText.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTMessageText.h; sourceTree = ""; }; + 9CF2F4081D5363420002E175 /* unencrypted-database.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "unencrypted-database.realm"; sourceTree = ""; }; + A008344DFB4D30C30A832E50B9DFAA81 /* OCTSubmanagerUserImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerUserImpl.h; sourceTree = ""; }; + A61DE127803013E8229FED6D1DADFCBD /* OCTSubmanagerFriendsImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerFriendsImpl.m; sourceTree = ""; }; + ACBB7FE1C1F9FD951C5A5D6DFA12C7FC /* OCTFriend.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFriend.m; sourceTree = ""; }; + AEAF02FD4B49A2DF3C13562D48C9FEA0 /* OCTManagerImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTManagerImpl.m; sourceTree = ""; }; + B08EB0152D5B2993766DD654578C3493 /* OCTTox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTTox.m; sourceTree = ""; }; + B6D6D28CCB11A29144A37A53 /* libPods-iOSDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-iOSDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + C2F358725E9EF3F392D73A1ABBDD3DAB /* OCTSubmanagerUserImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTSubmanagerUserImpl.m; sourceTree = ""; }; + CE5DBC12869590BEAD637C9DF06567EF /* OCTManagerConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTManagerConstants.m; sourceTree = ""; }; + DC15169F1B57241E08B356608016D41A /* OCTSubmanagerUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTSubmanagerUser.h; sourceTree = ""; }; + E02C2F73766CD67B62FDBB73841065AF /* OCTMessageText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTMessageText.m; sourceTree = ""; }; + E81B002B36CF7385268F31A5263A10DF /* OCTFriendRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFriendRequest.m; sourceTree = ""; }; + E9E579DEF80DD05B8F30CA2822233B2B /* OCTDefaultFileStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTDefaultFileStorage.m; sourceTree = ""; }; + EEE0165339DE12FD2EE1821ACE3E19B8 /* OCTToxOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTToxOptions.h; sourceTree = ""; }; + F009D9A81C195D77008243EF /* OCTMainWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTMainWindowController.h; sourceTree = ""; }; + F009D9A91C195D77008243EF /* OCTMainWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTMainWindowController.m; sourceTree = ""; }; + F009D9AB1C195DBA008243EF /* OCTBootStrapViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTBootStrapViewController.h; sourceTree = ""; }; + F009D9AC1C195DBA008243EF /* OCTBootStrapViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTBootStrapViewController.m; sourceTree = ""; }; + F01929181C2797D2001A5F45 /* OCTConversationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTConversationViewController.h; sourceTree = ""; }; + F01929191C2797D2001A5F45 /* OCTConversationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTConversationViewController.m; sourceTree = ""; }; + F019291A1C2797D2001A5F45 /* OCTConversationViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OCTConversationViewController.xib; sourceTree = ""; }; + F02C7EB01C1CCF1200D144BD /* OCTFriendsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTFriendsViewController.h; sourceTree = ""; }; + F02C7EB11C1CCF1200D144BD /* OCTFriendsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTFriendsViewController.m; sourceTree = ""; }; + F02C7EB21C1CCF1200D144BD /* OCTFriendsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OCTFriendsViewController.xib; sourceTree = ""; }; + F07992D71C2E5B1900B0D267 /* OCTCallsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTCallsViewController.h; sourceTree = ""; }; + F07992D81C2E5B1900B0D267 /* OCTCallsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTCallsViewController.m; sourceTree = ""; }; + F07992D91C2E5B1900B0D267 /* OCTCallsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OCTCallsViewController.xib; sourceTree = ""; }; + F09D0A431C1B9877008E74AF /* OCTUserViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTUserViewController.h; sourceTree = ""; }; + F09D0A441C1B9877008E74AF /* OCTUserViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTUserViewController.m; sourceTree = ""; }; + F09D0A451C1B9877008E74AF /* OCTUserViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OCTUserViewController.xib; sourceTree = ""; }; + F0EAD8421C1A38DA0056587D /* MainWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; + F0EAD8441C1A58B20056587D /* OCTBootStrap.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OCTBootStrap.xib; sourceTree = ""; }; + F50269661C31CB0700E05351 /* OCTAudioQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTAudioQueueTests.m; sourceTree = ""; }; + F5BC99BD1C2A175700425CE3 /* test_straight3p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = test_straight3p.h; sourceTree = ""; }; + F5BC99BE1C2A1AB800425CE3 /* test_good.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = test_good.h; sourceTree = ""; }; + F5BC99BF1C2A1C4600425CE3 /* test_stride13p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = test_stride13p.h; sourceTree = ""; }; + F5BC99C01C2A1E2500425CE3 /* test_backwards_stride13p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = test_backwards_stride13p.h; sourceTree = ""; }; + F5BC99C11C2A1EAB00425CE3 /* test_backwards_3p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = test_backwards_3p.h; sourceTree = ""; }; + F5BF427A1C2D2686008283E0 /* CoreAudioMocks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreAudioMocks.h; sourceTree = ""; }; + F5F6FA1D1C268B5000607306 /* OCTAudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTAudioQueue.h; sourceTree = ""; }; + F5F6FA1E1C268B5000607306 /* OCTAudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCTAudioQueue.m; sourceTree = ""; }; + F967399BFF7F28425C5194F0E5552BCA /* OCTManagerConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCTManagerConfiguration.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 9CB44B5C1B84D8C1007FA7B6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 447C84AF29005A2116AADE2D /* libPods-iOSDemo.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9CB44B741B84D8C1007FA7B6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F0BB04E375482D8C4AF2C5C1 /* libPods-iOSDemoTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9CB44C2E1B84DCCC007FA7B6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E58B1AAF894CEF7C1E9D8364 /* libPods-OSXDemo.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9CB44C401B84DCCC007FA7B6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 844ACC71B8A64F9A5412EDA3 /* libPods-OSXDemoTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00BB1A4B0C327264C880852B33FA911D /* Public */ = { + isa = PBXGroup; + children = ( + 6B2BAAC14E1289AF1DBE31D6A928E9AA /* Manager */, + F3FA418C1201DA30777FEA08FC9406CB /* Wrapper */, + ); + path = Public; + sourceTree = ""; + }; + 1131875E1DD683A700E6FAA2 /* Messages */ = { + isa = PBXGroup; + children = ( + 1131875F1DD683E400E6FAA2 /* OCTSendMessageOperation.h */, + 113187601DD683E400E6FAA2 /* OCTSendMessageOperation.m */, + ); + name = Messages; + sourceTree = ""; + }; + 11AF88F81C9896F000AD1D9F /* Files */ = { + isa = PBXGroup; + children = ( + 1183BC931CA076BC000CD310 /* NSError+OCTFile.h */, + 1183BC941CA076BC000CD310 /* NSError+OCTFile.m */, + 11AF88FF1C98C25A00AD1D9F /* OCTFileBaseOperation.h */, + 11AF89001C98C25A00AD1D9F /* OCTFileBaseOperation.m */, + 115B6FC11C9DDB6D00C65334 /* OCTFileBaseOperation+Private.h */, + 1183BC871CA035CD000CD310 /* OCTFileDataInput.h */, + 1183BC881CA035CD000CD310 /* OCTFileDataInput.m */, + 1183BC8D1CA036AA000CD310 /* OCTFileDataOutput.h */, + 1183BC8E1CA036AA000CD310 /* OCTFileDataOutput.m */, + 11AF88F91C98976F00AD1D9F /* OCTFileDownloadOperation.h */, + 11AF88FA1C98976F00AD1D9F /* OCTFileDownloadOperation.m */, + 1183BC791CA026F0000CD310 /* OCTFileInputProtocol.h */, + 1183BC801CA02AA1000CD310 /* OCTFileOutputProtocol.h */, + 1183BC7A1CA02755000CD310 /* OCTFilePathInput.h */, + 1183BC7B1CA02755000CD310 /* OCTFilePathInput.m */, + 1183BC811CA02AA9000CD310 /* OCTFilePathOutput.h */, + 1183BC821CA02AA9000CD310 /* OCTFilePathOutput.m */, + 11BB6EAC1CC3930A00A531A8 /* OCTFileTools.h */, + 11BB6EAD1CC3930A00A531A8 /* OCTFileTools.m */, + 1183BC671C9EBBE5000CD310 /* OCTFileUploadOperation.h */, + 1183BC681C9EBBE5000CD310 /* OCTFileUploadOperation.m */, + ); + path = Files; + sourceTree = ""; + }; + 11D650CD1B89225400C3DD23 /* Audio */ = { + isa = PBXGroup; + children = ( + 11D650CE1B89225400C3DD23 /* OCTAudioEngine.h */, + 11D650CF1B89225400C3DD23 /* OCTAudioEngine.m */, + 11D651311B8924DA00C3DD23 /* OCTAudioEngine+Private.h */, + F5F6FA1D1C268B5000607306 /* OCTAudioQueue.h */, + F5F6FA1E1C268B5000607306 /* OCTAudioQueue.m */, + ); + path = Audio; + sourceTree = ""; + }; + 11D650F01B89229B00C3DD23 /* Video */ = { + isa = PBXGroup; + children = ( + 11D650F11B89229B00C3DD23 /* OCTPixelBufferPool.h */, + 11D650F21B89229B00C3DD23 /* OCTPixelBufferPool.m */, + 11D650F31B89229B00C3DD23 /* OCTVideoEngine.h */, + 11D650F41B89229B00C3DD23 /* OCTVideoEngine.m */, + 11D650F51B89229B00C3DD23 /* OCTVideoView.h */, + 11D650F61B89229B00C3DD23 /* OCTVideoView.m */, + ); + path = Video; + sourceTree = ""; + }; + 29E30FD5E3A845CEB92CCF1E4DD35ED4 /* Wrapper */ = { + isa = PBXGroup; + children = ( + 11AF890D1C9B57F900AD1D9F /* OCTLogging.h */, + B08EB0152D5B2993766DD654578C3493 /* OCTTox.m */, + 1125DC491B2107E900C8DB98 /* OCTTox+Private.h */, + 11D651031B8922AF00C3DD23 /* OCTToxAV.m */, + 11D651041B8922AF00C3DD23 /* OCTToxAV+Private.h */, + 11D651051B8922AF00C3DD23 /* OCTToxAVConstants.m */, + 872468E740CD9DEC5E48ED9646696C62 /* OCTToxConstants.m */, + 119B13E41B9AF5A2006DA6FF /* OCTToxEncryptSave.m */, + 1AED5AE89C3775595A525D3B45B80637 /* OCTToxOptions.m */, + 11F6C4A51E2A4D9B0096F462 /* OCTToxOptions+Private.h */, + ); + path = Wrapper; + sourceTree = ""; + }; + 343B3C1DC43BA0512FF12BB269169A43 /* Submanagers */ = { + isa = PBXGroup; + children = ( + 117D37D21B72B476006E885C /* OCTSubmanagerBootstrapImpl.m */, + 117D37CF1B72B46D006E885C /* OCTSubmanagerBootstrapImpl.h */, + 11D650EA1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m */, + 11D650EB1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.h */, + 5F8DB9E80557EDCED72F1E0D5E868D9F /* OCTSubmanagerChatsImpl.m */, + 67CE089BF824F5E312E1F2DA82200173 /* OCTSubmanagerChatsImpl.h */, + 468C7E16643FCCEB54CFF0414052EC3C /* OCTSubmanagerDataSource.h */, + 11B4BAF01B11E2800001A96A /* OCTSubmanagerFilesImpl.m */, + 11B4BAF31B11E29A0001A96A /* OCTSubmanagerFilesImpl.h */, + A61DE127803013E8229FED6D1DADFCBD /* OCTSubmanagerFriendsImpl.m */, + 1F210739EBF632594BBF9C51E585A589 /* OCTSubmanagerFriendsImpl.h */, + 11D6C9911B3B251000E21A35 /* OCTSubmanagerObjectsImpl.m */, + 11D6C9941B3B252300E21A35 /* OCTSubmanagerObjectsImpl.h */, + 117D37CB1B72B284006E885C /* OCTSubmanagerProtocol.h */, + C2F358725E9EF3F392D73A1ABBDD3DAB /* OCTSubmanagerUserImpl.m */, + A008344DFB4D30C30A832E50B9DFAA81 /* OCTSubmanagerUserImpl.h */, + ); + path = Submanagers; + sourceTree = ""; + }; + 491C3A03DF405B148E570C976B10BB3D /* Private */ = { + isa = PBXGroup; + children = ( + CF9416C2498272D5823396FB02DF7670 /* Manager */, + 29E30FD5E3A845CEB92CCF1E4DD35ED4 /* Wrapper */, + ); + path = Private; + sourceTree = ""; + }; + 59DFACB59220710D4B2227182A018958 /* Submanagers */ = { + isa = PBXGroup; + children = ( + 117D37CC1B72B45B006E885C /* OCTSubmanagerBootstrap.h */, + 11D651111B8922EC00C3DD23 /* OCTSubmanagerCalls.h */, + 11D651121B8922EC00C3DD23 /* OCTSubmanagerCallsDelegate.h */, + 2CBE0C2E379DCF740AF5D469F096D309 /* OCTSubmanagerChats.h */, + 11B4BAEF1B11E2530001A96A /* OCTSubmanagerFiles.h */, + 115B6FBF1C9DD46700C65334 /* OCTSubmanagerFilesProgressSubscriber.h */, + 91FC63A06A08D2865DD4552BB2476EE8 /* OCTSubmanagerFriends.h */, + 11D6C98E1B3B24F700E21A35 /* OCTSubmanagerObjects.h */, + DC15169F1B57241E08B356608016D41A /* OCTSubmanagerUser.h */, + ); + path = Submanagers; + sourceTree = ""; + }; + 5A2B53F1E418A85F8FF2B2AE2AD91B94 = { + isa = PBXGroup; + children = ( + B6667C5B1813BB0AD0E5589A2B981680 /* Classes */, + 9CB44C9B1B84DF46007FA7B6 /* Tests */, + 9CB44B601B84D8C1007FA7B6 /* iOSDemo */, + 9CB44B7A1B84D8C1007FA7B6 /* iOSDemoTests */, + 9CB44C321B84DCCC007FA7B6 /* OSXDemo */, + 9CB44C461B84DCCC007FA7B6 /* OSXDemoTests */, + DB96D4CB88F5590E960C0A563BE0D34C /* Products */, + 80CF0B409DDB069A7EEA9DBE /* Pods */, + C8C1A05BBBD16A566A208AA1 /* Frameworks */, + ); + sourceTree = ""; + }; + 60183FD68841CB45990A4852224E9705 /* Objects */ = { + isa = PBXGroup; + children = ( + 11D650D41B89226800C3DD23 /* OCTCall.m */, + 11D650D51B89226800C3DD23 /* OCTCall+Utilities.h */, + 11D650D61B89226800C3DD23 /* OCTCall+Utilities.m */, + 11D650D71B89226800C3DD23 /* OCTCallTimer.h */, + 11D650D81B89226800C3DD23 /* OCTCallTimer.m */, + 72B2CBBB2987645B0CA4475581E7B86A /* OCTChat.m */, + ACBB7FE1C1F9FD951C5A5D6DFA12C7FC /* OCTFriend.m */, + E81B002B36CF7385268F31A5263A10DF /* OCTFriendRequest.m */, + 5309FB4CA1007014A575D75CB9387C5C /* OCTMessageAbstract.m */, + 11D650E51B89227300C3DD23 /* OCTMessageCall.m */, + 65A7B20C506A85C1668D26921218DFA5 /* OCTMessageFile.m */, + E02C2F73766CD67B62FDBB73841065AF /* OCTMessageText.m */, + 11D497B41B72BE5700634B56 /* OCTNode.h */, + 11D497B51B72BE5700634B56 /* OCTNode.m */, + 11CD5AE81B386A4D00585194 /* OCTObject.m */, + 11E80D811B98C647008DFC47 /* OCTSettingsStorageObject.h */, + 11E80D821B98C647008DFC47 /* OCTSettingsStorageObject.m */, + ); + path = Objects; + sourceTree = ""; + }; + 6B2BAAC14E1289AF1DBE31D6A928E9AA /* Manager */ = { + isa = PBXGroup; + children = ( + B729102B12162A2F537D836802D56E79 /* Configuration */, + 714C4AAE365D94A8BC642E0D66F052D4 /* Objects */, + 59DFACB59220710D4B2227182A018958 /* Submanagers */, + 31BCEE5F6D309D5BA4FAE6E6E70C2CA7 /* OCTManager.h */, + 110152851DC4A9D600044852 /* OCTManagerFactory.h */, + 647688B7386EE74BA1E6359A822B2BE6 /* OCTManagerConstants.h */, + 116634F81C80C7280072C980 /* nodes.json */, + ); + path = Manager; + sourceTree = ""; + }; + 714C4AAE365D94A8BC642E0D66F052D4 /* Objects */ = { + isa = PBXGroup; + children = ( + 11D6510E1B8922C700C3DD23 /* OCTCall.h */, + 9CB71FBC1B386B2F00E3C1EF /* OCTChat.h */, + 9CB71FBD1B386B2F00E3C1EF /* OCTFriend.h */, + 9CB71FBE1B386B2F00E3C1EF /* OCTFriendRequest.h */, + 9CB71FBF1B386B2F00E3C1EF /* OCTMessageAbstract.h */, + 11D6510F1B8922CF00C3DD23 /* OCTMessageCall.h */, + 9CB71FC01B386B2F00E3C1EF /* OCTMessageFile.h */, + 9CB71FC11B386B2F00E3C1EF /* OCTMessageText.h */, + 11CD5AE51B386A3A00585194 /* OCTObject.h */, + 11D651101B8922D900C3DD23 /* OCTView.h */, + ); + path = Objects; + sourceTree = ""; + }; + 7DB8B234977816236C8F57408DD904FB /* Configuration */ = { + isa = PBXGroup; + children = ( + E9E579DEF80DD05B8F30CA2822233B2B /* OCTDefaultFileStorage.m */, + 1B75FA045E32B1417FDC20970961725C /* OCTManagerConfiguration.m */, + ); + path = Configuration; + sourceTree = ""; + }; + 80CF0B409DDB069A7EEA9DBE /* Pods */ = { + isa = PBXGroup; + children = ( + 80EDC251089F9EE3EA655215 /* Pods-OSXDemo.release.xcconfig */, + 9BB556421978E2471480B277 /* Pods-OSXDemoTests.release.xcconfig */, + 6F97E8DCCDE81B906C0CCAFE /* Pods-iOSDemo.release.xcconfig */, + 54FC7A218FD35DB03A172079 /* Pods-iOSDemoTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 8588AB583843DF5ABD08944E00D9704D /* Database */ = { + isa = PBXGroup; + children = ( + 110EE0D81B387C3D00CC347A /* OCTRealmManager.h */, + 110EE0D91B387C3D00CC347A /* OCTRealmManager.m */, + ); + path = Database; + sourceTree = ""; + }; + 9CB44B601B84D8C1007FA7B6 /* iOSDemo */ = { + isa = PBXGroup; + children = ( + 9CB44B651B84D8C1007FA7B6 /* AppDelegate.h */, + 9CB44B661B84D8C1007FA7B6 /* AppDelegate.m */, + 11D651161B89232200C3DD23 /* OCTCallsViewController.h */, + 11D651171B89232200C3DD23 /* OCTCallsViewController.m */, + 9CB44B831B84D91C007FA7B6 /* OCTChatsViewController.h */, + 9CB44B841B84D91C007FA7B6 /* OCTChatsViewController.m */, + 9CB44B851B84D91C007FA7B6 /* OCTConversationViewController.h */, + 9CB44B861B84D91C007FA7B6 /* OCTConversationViewController.m */, + 9CB44B871B84D91C007FA7B6 /* OCTFriendsViewController.h */, + 9CB44B881B84D91C007FA7B6 /* OCTFriendsViewController.m */, + 9CB44B891B84D91C007FA7B6 /* OCTStartDemoViewController.h */, + 9CB44B8A1B84D91C007FA7B6 /* OCTStartDemoViewController.m */, + 11D6511C1B89233A00C3DD23 /* OCTTabBarControllerViewController.h */, + 11D6511D1B89233A00C3DD23 /* OCTTabBarControllerViewController.m */, + 9CB44B8B1B84D91C007FA7B6 /* OCTTableViewController.h */, + 9CB44B8C1B84D91C007FA7B6 /* OCTTableViewController.m */, + 9CB44B8D1B84D91C007FA7B6 /* OCTUserViewController.h */, + 9CB44B8E1B84D91C007FA7B6 /* OCTUserViewController.m */, + 11D6511F1B89234300C3DD23 /* OCTVideoViewController.h */, + 11D651201B89234300C3DD23 /* OCTVideoViewController.m */, + 9CB44B6E1B84D8C1007FA7B6 /* Images.xcassets */, + 9CB44B701B84D8C1007FA7B6 /* LaunchScreen.xib */, + 9CB44B611B84D8C1007FA7B6 /* Supporting Files */, + ); + path = iOSDemo; + sourceTree = ""; + }; + 9CB44B611B84D8C1007FA7B6 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 9CB44B621B84D8C1007FA7B6 /* Info.plist */, + 9CB44B631B84D8C1007FA7B6 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 9CB44B7A1B84D8C1007FA7B6 /* iOSDemoTests */ = { + isa = PBXGroup; + children = ( + 9CB44B7B1B84D8C1007FA7B6 /* Supporting Files */, + ); + path = iOSDemoTests; + sourceTree = ""; + }; + 9CB44B7B1B84D8C1007FA7B6 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 9CB44B7C1B84D8C1007FA7B6 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 9CB44C321B84DCCC007FA7B6 /* OSXDemo */ = { + isa = PBXGroup; + children = ( + 9CB44C351B84DCCC007FA7B6 /* AppDelegate.h */, + 9CB44C361B84DCCC007FA7B6 /* AppDelegate.m */, + F009D9A81C195D77008243EF /* OCTMainWindowController.h */, + F009D9A91C195D77008243EF /* OCTMainWindowController.m */, + 9C3F49F51CDCE81B005E2A61 /* RLMCollectionChange+IndexSet.h */, + 9C3F49F61CDCE81B005E2A61 /* RLMCollectionChange+IndexSet.m */, + 9CB44C3A1B84DCCC007FA7B6 /* Images.xcassets */, + 9CB44C3C1B84DCCC007FA7B6 /* MainMenu.xib */, + 9CB44C331B84DCCC007FA7B6 /* Supporting Files */, + F009D9AB1C195DBA008243EF /* OCTBootStrapViewController.h */, + F009D9AC1C195DBA008243EF /* OCTBootStrapViewController.m */, + F0EAD8421C1A38DA0056587D /* MainWindow.xib */, + F0EAD8441C1A58B20056587D /* OCTBootStrap.xib */, + F09D0A431C1B9877008E74AF /* OCTUserViewController.h */, + F09D0A441C1B9877008E74AF /* OCTUserViewController.m */, + F09D0A451C1B9877008E74AF /* OCTUserViewController.xib */, + F02C7EB01C1CCF1200D144BD /* OCTFriendsViewController.h */, + F02C7EB11C1CCF1200D144BD /* OCTFriendsViewController.m */, + F02C7EB21C1CCF1200D144BD /* OCTFriendsViewController.xib */, + F01929181C2797D2001A5F45 /* OCTConversationViewController.h */, + F01929191C2797D2001A5F45 /* OCTConversationViewController.m */, + F019291A1C2797D2001A5F45 /* OCTConversationViewController.xib */, + F07992D71C2E5B1900B0D267 /* OCTCallsViewController.h */, + F07992D81C2E5B1900B0D267 /* OCTCallsViewController.m */, + F07992D91C2E5B1900B0D267 /* OCTCallsViewController.xib */, + 11AF890E1C9D570000AD1D9F /* OCTFilesViewController.h */, + 11AF890F1C9D570000AD1D9F /* OCTFilesViewController.m */, + 11AF89101C9D570000AD1D9F /* OCTFilesViewController.xib */, + ); + path = OSXDemo; + sourceTree = ""; + }; + 9CB44C331B84DCCC007FA7B6 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 9CB44C341B84DCCC007FA7B6 /* Info.plist */, + 9CB44C381B84DCCC007FA7B6 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 9CB44C461B84DCCC007FA7B6 /* OSXDemoTests */ = { + isa = PBXGroup; + children = ( + 9CB44C471B84DCCC007FA7B6 /* Supporting Files */, + ); + path = OSXDemoTests; + sourceTree = ""; + }; + 9CB44C471B84DCCC007FA7B6 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 9CB44C481B84DCCC007FA7B6 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 9CB44C9B1B84DF46007FA7B6 /* Tests */ = { + isa = PBXGroup; + children = ( + 9CF2F4081D5363420002E175 /* unencrypted-database.realm */, + F5BF427A1C2D2686008283E0 /* CoreAudioMocks.h */, + 9CB44C9D1B84DF46007FA7B6 /* OCTCAsserts.h */, + 9CB44CA61B84DF46007FA7B6 /* OCTRealmTests.h */, + 11D651221B89236A00C3DD23 /* OCTAudioEngineTests.m */, + F50269661C31CB0700E05351 /* OCTAudioQueueTests.m */, + 11D651231B89236A00C3DD23 /* OCTCallTimerTests.m */, + 9CB44C9E1B84DF46007FA7B6 /* OCTChatTests.m */, + 9CB44C9F1B84DF46007FA7B6 /* OCTDefaultFileStorageTests.m */, + 11BB6EB21CC3932B00A531A8 /* OCTFileToolsTests.m */, + 9CB44CA11B84DF46007FA7B6 /* OCTFriendRequestTests.m */, + 9CB44CA21B84DF46007FA7B6 /* OCTManagerConfigurationTests.m */, + 9CB44CA31B84DF46007FA7B6 /* OCTManagerImplTests.m */, + 9CB44CA41B84DF46007FA7B6 /* OCTMessageAbstractTests.m */, + 9CB44CA51B84DF46007FA7B6 /* OCTObjectTests.m */, + 9CB44CA71B84DF46007FA7B6 /* OCTRealmTests.m */, + 9CB44CA91B84DF46007FA7B6 /* OCTSubmanagerBootstrapImplTests.m */, + 11D651281B89237D00C3DD23 /* OCTSubmanagerCallsImplTests.m */, + 9CB44CAA1B84DF46007FA7B6 /* OCTSubmanagerChatsImplTests.m */, + 9CB44CAB1B84DF46007FA7B6 /* OCTSubmanagerFilesImplTests.m */, + 9CB44CAC1B84DF46007FA7B6 /* OCTSubmanagerFriendsImplTests.m */, + 9CB44CAD1B84DF46007FA7B6 /* OCTSubmanagerObjectsImplTests.m */, + 9CB44CAE1B84DF46007FA7B6 /* OCTSubmanagerUserImplTests.m */, + 11D6512B1B89238900C3DD23 /* OCTToxAVTests.m */, + 112779D31B9B6F0700E475B4 /* OCTToxEncryptSaveTests.m */, + 9CB44CAF1B84DF46007FA7B6 /* OCTToxTests.m */, + 11D6512E1B89238F00C3DD23 /* OCTVideoEngineTests.m */, + F5BC99BC1C2A171D00425CE3 /* YUVPlanes */, + ); + path = Tests; + sourceTree = ""; + }; + B6667C5B1813BB0AD0E5589A2B981680 /* Classes */ = { + isa = PBXGroup; + children = ( + 491C3A03DF405B148E570C976B10BB3D /* Private */, + 00BB1A4B0C327264C880852B33FA911D /* Public */, + ); + path = Classes; + sourceTree = ""; + }; + B729102B12162A2F537D836802D56E79 /* Configuration */ = { + isa = PBXGroup; + children = ( + 4661B7235F65476C9C4E3255DB5BCE79 /* OCTDefaultFileStorage.h */, + 2369847240F2E8FD3B4AFD5BDDC2DCF2 /* OCTFileStorageProtocol.h */, + F967399BFF7F28425C5194F0E5552BCA /* OCTManagerConfiguration.h */, + ); + path = Configuration; + sourceTree = ""; + }; + C8C1A05BBBD16A566A208AA1 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4C1B17E748892445A436C846 /* libPods-OSXDemo.a */, + 086B5D0EF83D1DB88AD12AB6 /* libPods-OSXDemoTests.a */, + B6D6D28CCB11A29144A37A53 /* libPods-iOSDemo.a */, + 7C38C5849F5CBA7BDADFD685 /* libPods-iOSDemoTests.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + CF9416C2498272D5823396FB02DF7670 /* Manager */ = { + isa = PBXGroup; + children = ( + 110152861DC4ABF700044852 /* OCTManagerImpl.h */, + CE5DBC12869590BEAD637C9DF06567EF /* OCTManagerConstants.m */, + 9CB1F9581D5B671E00105858 /* OCTManagerFactory.m */, + AEAF02FD4B49A2DF3C13562D48C9FEA0 /* OCTManagerImpl.m */, + 11D650CD1B89225400C3DD23 /* Audio */, + 7DB8B234977816236C8F57408DD904FB /* Configuration */, + 8588AB583843DF5ABD08944E00D9704D /* Database */, + 11AF88F81C9896F000AD1D9F /* Files */, + 1131875E1DD683A700E6FAA2 /* Messages */, + 60183FD68841CB45990A4852224E9705 /* Objects */, + 343B3C1DC43BA0512FF12BB269169A43 /* Submanagers */, + 11D650F01B89229B00C3DD23 /* Video */, + ); + path = Manager; + sourceTree = ""; + }; + DB96D4CB88F5590E960C0A563BE0D34C /* Products */ = { + isa = PBXGroup; + children = ( + 9CB44B5F1B84D8C1007FA7B6 /* iOSDemo.app */, + 9CB44B771B84D8C1007FA7B6 /* iOSDemoTests.xctest */, + 9CB44C311B84DCCC007FA7B6 /* OSXDemo.app */, + 9CB44C431B84DCCC007FA7B6 /* OSXDemoTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + F3FA418C1201DA30777FEA08FC9406CB /* Wrapper */ = { + isa = PBXGroup; + children = ( + 1D66E7C409BC96AC7372B302BABF1D88 /* OCTTox.h */, + 11D651131B89230200C3DD23 /* OCTToxAV.h */, + 11D651141B89230200C3DD23 /* OCTToxAVConstants.h */, + 11D651151B89230200C3DD23 /* OCTToxAVDelegate.h */, + 6733F3E1A4E00CC032F25870E9083F13 /* OCTToxConstants.h */, + 49DADF58900503E712C710CF13F175DD /* OCTToxDelegate.h */, + 119B13DE1B9AF58B006DA6FF /* OCTToxEncryptSave.h */, + 119B13E91B9AF78F006DA6FF /* OCTToxEncryptSaveConstants.h */, + EEE0165339DE12FD2EE1821ACE3E19B8 /* OCTToxOptions.h */, + ); + path = Wrapper; + sourceTree = ""; + }; + F5BC99BC1C2A171D00425CE3 /* YUVPlanes */ = { + isa = PBXGroup; + children = ( + F5BC99C01C2A1E2500425CE3 /* test_backwards_stride13p.h */, + F5BC99C11C2A1EAB00425CE3 /* test_backwards_3p.h */, + F5BC99BF1C2A1C4600425CE3 /* test_stride13p.h */, + F5BC99BE1C2A1AB800425CE3 /* test_good.h */, + F5BC99BD1C2A175700425CE3 /* test_straight3p.h */, + ); + path = YUVPlanes; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 9CB44B5E1B84D8C1007FA7B6 /* iOSDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9CB44B7F1B84D8C1007FA7B6 /* Build configuration list for PBXNativeTarget "iOSDemo" */; + buildPhases = ( + 1A0DE562B641CFDFBE0AFFEB /* [CP] Check Pods Manifest.lock */, + 9CB44B5B1B84D8C1007FA7B6 /* Sources */, + 9CB44B5C1B84D8C1007FA7B6 /* Frameworks */, + 9CB44B5D1B84D8C1007FA7B6 /* Resources */, + 1AE3FA2EF5896FAB63CEED26 /* [CP] Embed Pods Frameworks */, + 27FAD714500DBB214100D6A9 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iOSDemo; + productName = iOSDemo; + productReference = 9CB44B5F1B84D8C1007FA7B6 /* iOSDemo.app */; + productType = "com.apple.product-type.application"; + }; + 9CB44B761B84D8C1007FA7B6 /* iOSDemoTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9CB44B811B84D8C1007FA7B6 /* Build configuration list for PBXNativeTarget "iOSDemoTests" */; + buildPhases = ( + E94B5F357EC3125CEB95827F /* [CP] Check Pods Manifest.lock */, + 9CB44B731B84D8C1007FA7B6 /* Sources */, + 9CB44B741B84D8C1007FA7B6 /* Frameworks */, + 9CB44B751B84D8C1007FA7B6 /* Resources */, + 34BD33C231B0DC4C812E6CD4 /* [CP] Embed Pods Frameworks */, + F238569579B9956880F66E28 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 9CB44B791B84D8C1007FA7B6 /* PBXTargetDependency */, + ); + name = iOSDemoTests; + productName = iOSDemoTests; + productReference = 9CB44B771B84D8C1007FA7B6 /* iOSDemoTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 9CB44C301B84DCCC007FA7B6 /* OSXDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9CB44C4B1B84DCCC007FA7B6 /* Build configuration list for PBXNativeTarget "OSXDemo" */; + buildPhases = ( + 5939D0C49BC321A42C08BA49 /* [CP] Check Pods Manifest.lock */, + 9CB44C2D1B84DCCC007FA7B6 /* Sources */, + 9CB44C2E1B84DCCC007FA7B6 /* Frameworks */, + 9CB44C2F1B84DCCC007FA7B6 /* Resources */, + 8DACE7DE62FF348B30F0696F /* [CP] Embed Pods Frameworks */, + 49819FDE533D3A05C392778B /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = OSXDemo; + productName = OSXDemo; + productReference = 9CB44C311B84DCCC007FA7B6 /* OSXDemo.app */; + productType = "com.apple.product-type.application"; + }; + 9CB44C421B84DCCC007FA7B6 /* OSXDemoTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9CB44C4D1B84DCCC007FA7B6 /* Build configuration list for PBXNativeTarget "OSXDemoTests" */; + buildPhases = ( + B699B233074D6E44E9F6DD10 /* [CP] Check Pods Manifest.lock */, + 9CB44C3F1B84DCCC007FA7B6 /* Sources */, + 9CB44C401B84DCCC007FA7B6 /* Frameworks */, + 9CB44C411B84DCCC007FA7B6 /* Resources */, + 49814162CF2B25A0EF83F743 /* [CP] Embed Pods Frameworks */, + 7BC9F17BA589F0241ED5F6C8 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 9CB44C451B84DCCC007FA7B6 /* PBXTargetDependency */, + ); + name = OSXDemoTests; + productName = OSXDemoTests; + productReference = 9CB44C431B84DCCC007FA7B6 /* OSXDemoTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 1A384FEB508C718A4A7522BB0A835B5F /* Project object */ = { + isa = PBXProject; + attributes = { + CLASSPREFIX = OCT; + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = dvor; + TargetAttributes = { + 9CB44B5E1B84D8C1007FA7B6 = { + CreatedOnToolsVersion = 6.4; + }; + 9CB44B761B84D8C1007FA7B6 = { + CreatedOnToolsVersion = 6.4; + }; + 9CB44C301B84DCCC007FA7B6 = { + CreatedOnToolsVersion = 6.4; + }; + 9CB44C421B84DCCC007FA7B6 = { + CreatedOnToolsVersion = 6.4; + }; + }; + }; + buildConfigurationList = CD70405E4519D77E9FDF252033749EF6 /* Build configuration list for PBXProject "objcTox" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 5A2B53F1E418A85F8FF2B2AE2AD91B94; + productRefGroup = DB96D4CB88F5590E960C0A563BE0D34C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 9CB44B5E1B84D8C1007FA7B6 /* iOSDemo */, + 9CB44B761B84D8C1007FA7B6 /* iOSDemoTests */, + 9CB44C301B84DCCC007FA7B6 /* OSXDemo */, + 9CB44C421B84DCCC007FA7B6 /* OSXDemoTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 9CB44B5D1B84D8C1007FA7B6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 116634F91C80C7280072C980 /* nodes.json in Resources */, + 9CB44B721B84D8C1007FA7B6 /* LaunchScreen.xib in Resources */, + 9CB44B6F1B84D8C1007FA7B6 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9CB44B751B84D8C1007FA7B6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9CF2F4091D5363420002E175 /* unencrypted-database.realm in Resources */, + 116634FA1C80C7280072C980 /* nodes.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9CB44C2F1B84DCCC007FA7B6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F0EAD8431C1A38DA0056587D /* MainWindow.xib in Resources */, + F02C7EB41C1CCF1200D144BD /* OCTFriendsViewController.xib in Resources */, + F019291C1C2797D2001A5F45 /* OCTConversationViewController.xib in Resources */, + 116634FB1C80C7280072C980 /* nodes.json in Resources */, + F09D0A471C1B9877008E74AF /* OCTUserViewController.xib in Resources */, + 9CB44C3B1B84DCCC007FA7B6 /* Images.xcassets in Resources */, + 9CB44C3E1B84DCCC007FA7B6 /* MainMenu.xib in Resources */, + 11AF89121C9D570000AD1D9F /* OCTFilesViewController.xib in Resources */, + F0EAD8451C1A58B20056587D /* OCTBootStrap.xib in Resources */, + F07992DB1C2E5B1900B0D267 /* OCTCallsViewController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9CB44C411B84DCCC007FA7B6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9CF2F40A1D5363420002E175 /* unencrypted-database.realm in Resources */, + 116634FC1C80C7280072C980 /* nodes.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1A0DE562B641CFDFBE0AFFEB /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + 1AE3FA2EF5896FAB63CEED26 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-iOSDemo/Pods-iOSDemo-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 27FAD714500DBB214100D6A9 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-iOSDemo/Pods-iOSDemo-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 34BD33C231B0DC4C812E6CD4 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-iOSDemoTests/Pods-iOSDemoTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 49814162CF2B25A0EF83F743 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-OSXDemoTests/Pods-OSXDemoTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 49819FDE533D3A05C392778B /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-OSXDemo/Pods-OSXDemo-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 5939D0C49BC321A42C08BA49 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + 7BC9F17BA589F0241ED5F6C8 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-OSXDemoTests/Pods-OSXDemoTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 8DACE7DE62FF348B30F0696F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-OSXDemo/Pods-OSXDemo-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B699B233074D6E44E9F6DD10 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + E94B5F357EC3125CEB95827F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F238569579B9956880F66E28 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-iOSDemoTests/Pods-iOSDemoTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 9CB44B5B1B84D8C1007FA7B6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9CB44BE61B84D9E1007FA7B6 /* OCTChat.m in Sources */, + 11D6511E1B89233A00C3DD23 /* OCTTabBarControllerViewController.m in Sources */, + 11D650EC1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m in Sources */, + 9CB44B671B84D8C1007FA7B6 /* AppDelegate.m in Sources */, + 11D650D91B89226800C3DD23 /* OCTCall.m in Sources */, + 9CB44B921B84D91C007FA7B6 /* OCTStartDemoViewController.m in Sources */, + 1183BC831CA02AA9000CD310 /* OCTFilePathOutput.m in Sources */, + 9CB44B8F1B84D91C007FA7B6 /* OCTChatsViewController.m in Sources */, + 11D651211B89234300C3DD23 /* OCTVideoViewController.m in Sources */, + 9CB44BEB1B84D9E1007FA7B6 /* OCTMessageText.m in Sources */, + 1183BC891CA035CD000CD310 /* OCTFileDataInput.m in Sources */, + 1183BC7C1CA02755000CD310 /* OCTFilePathInput.m in Sources */, + 9CB44BF71B84D9E1007FA7B6 /* OCTTox.m in Sources */, + 9CB44BF51B84D9E1007FA7B6 /* OCTManagerImpl.m in Sources */, + 9CB44BE81B84D9E1007FA7B6 /* OCTFriendRequest.m in Sources */, + 9CB44B931B84D91C007FA7B6 /* OCTTableViewController.m in Sources */, + 9CB44B901B84D91C007FA7B6 /* OCTConversationViewController.m in Sources */, + 9CB44BF81B84D9E1007FA7B6 /* OCTToxConstants.m in Sources */, + 119B13E51B9AF5A2006DA6FF /* OCTToxEncryptSave.m in Sources */, + 9CB44BF41B84D9E1007FA7B6 /* OCTSubmanagerUserImpl.m in Sources */, + 11D651061B8922AF00C3DD23 /* OCTToxAV.m in Sources */, + 9CB44B641B84D8C1007FA7B6 /* main.m in Sources */, + 1183BC8F1CA036AA000CD310 /* OCTFileDataOutput.m in Sources */, + 9CB44BE31B84D9E1007FA7B6 /* OCTManagerConfiguration.m in Sources */, + 9CB44BF61B84D9E1007FA7B6 /* OCTManagerConstants.m in Sources */, + F5F6FA1F1C268B5000607306 /* OCTAudioQueue.m in Sources */, + 9CB44BF91B84D9E1007FA7B6 /* OCTToxOptions.m in Sources */, + 1183BC951CA076BC000CD310 /* NSError+OCTFile.m in Sources */, + 9CB44BEA1B84D9E1007FA7B6 /* OCTMessageFile.m in Sources */, + 11D651181B89232200C3DD23 /* OCTCallsViewController.m in Sources */, + 9CB44B941B84D91C007FA7B6 /* OCTUserViewController.m in Sources */, + 11D650E11B89226800C3DD23 /* OCTCallTimer.m in Sources */, + 9CB44BF01B84D9E1007FA7B6 /* OCTSubmanagerChatsImpl.m in Sources */, + 9CB44BE91B84D9E1007FA7B6 /* OCTMessageAbstract.m in Sources */, + 11D650DD1B89226800C3DD23 /* OCTCall+Utilities.m in Sources */, + 11D6510A1B8922AF00C3DD23 /* OCTToxAVConstants.m in Sources */, + 11E80D831B98C647008DFC47 /* OCTSettingsStorageObject.m in Sources */, + 9CB44B911B84D91C007FA7B6 /* OCTFriendsViewController.m in Sources */, + 11AF88FB1C98976F00AD1D9F /* OCTFileDownloadOperation.m in Sources */, + 11D650FB1B89229B00C3DD23 /* OCTVideoEngine.m in Sources */, + 9CB44BED1B84D9E1007FA7B6 /* OCTObject.m in Sources */, + 9CB44BEF1B84D9E1007FA7B6 /* OCTSubmanagerBootstrapImpl.m in Sources */, + 9CB44BF11B84D9E1007FA7B6 /* OCTSubmanagerFilesImpl.m in Sources */, + 11D650F71B89229B00C3DD23 /* OCTPixelBufferPool.m in Sources */, + 11BB6EAE1CC3930A00A531A8 /* OCTFileTools.m in Sources */, + 9CB1F9591D5B671E00105858 /* OCTManagerFactory.m in Sources */, + 11D650E61B89227300C3DD23 /* OCTMessageCall.m in Sources */, + 113187611DD683E400E6FAA2 /* OCTSendMessageOperation.m in Sources */, + 9CB44BEC1B84D9E1007FA7B6 /* OCTNode.m in Sources */, + 9CB44BE41B84D9E1007FA7B6 /* OCTRealmManager.m in Sources */, + 9CB44BF21B84D9E1007FA7B6 /* OCTSubmanagerFriendsImpl.m in Sources */, + 1183BC691C9EBBE5000CD310 /* OCTFileUploadOperation.m in Sources */, + 11D650FF1B89229B00C3DD23 /* OCTVideoView.m in Sources */, + 11AF89011C98C25A00AD1D9F /* OCTFileBaseOperation.m in Sources */, + 9CB44BE71B84D9E1007FA7B6 /* OCTFriend.m in Sources */, + 11D650D01B89225400C3DD23 /* OCTAudioEngine.m in Sources */, + 9CB44BE11B84D9E1007FA7B6 /* OCTDefaultFileStorage.m in Sources */, + 9CB44BF31B84D9E1007FA7B6 /* OCTSubmanagerObjectsImpl.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9CB44B731B84D8C1007FA7B6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F5BF42781C2D1F75008283E0 /* OCTAudioQueue.m in Sources */, + 11D6510B1B8922AF00C3DD23 /* OCTToxAVConstants.m in Sources */, + 9CB44C181B84DBA3007FA7B6 /* OCTSubmanagerUserImpl.m in Sources */, + 11D650F81B89229B00C3DD23 /* OCTPixelBufferPool.m in Sources */, + 9CB44C1A1B84DBA3007FA7B6 /* OCTSubmanagerFilesImpl.m in Sources */, + 9CB44C0F1B84DBA3007FA7B6 /* OCTMessageFile.m in Sources */, + 9CB44CD21B84DF46007FA7B6 /* OCTToxTests.m in Sources */, + 11D650E71B89227300C3DD23 /* OCTMessageCall.m in Sources */, + 112779D41B9B6F0700E475B4 /* OCTToxEncryptSaveTests.m in Sources */, + 9CB44CD01B84DF46007FA7B6 /* OCTSubmanagerUserImplTests.m in Sources */, + 11D651241B89236A00C3DD23 /* OCTAudioEngineTests.m in Sources */, + 9CB44CC21B84DF46007FA7B6 /* OCTRealmTests.m in Sources */, + 9CB44CC01B84DF46007FA7B6 /* OCTObjectTests.m in Sources */, + 11BB6EAF1CC3930A00A531A8 /* OCTFileTools.m in Sources */, + 9CB44CBC1B84DF46007FA7B6 /* OCTManagerImplTests.m in Sources */, + 9CB44C131B84DBA3007FA7B6 /* OCTRealmManager.m in Sources */, + 9CB44C171B84DBA3007FA7B6 /* OCTSubmanagerBootstrapImpl.m in Sources */, + 1183BC7D1CA02755000CD310 /* OCTFilePathInput.m in Sources */, + 11D650E21B89226800C3DD23 /* OCTCallTimer.m in Sources */, + 9CB44C151B84DBA3007FA7B6 /* OCTSubmanagerObjectsImpl.m in Sources */, + 9CB44C071B84DBA3007FA7B6 /* OCTChat.m in Sources */, + 1183BC961CA076BC000CD310 /* NSError+OCTFile.m in Sources */, + 9CB1F95A1D5B671E00105858 /* OCTManagerFactory.m in Sources */, + 9CB44C191B84DBA3007FA7B6 /* OCTSubmanagerFriendsImpl.m in Sources */, + 9CB44C1E1B84DBA3007FA7B6 /* OCTToxOptions.m in Sources */, + 11D650DE1B89226800C3DD23 /* OCTCall+Utilities.m in Sources */, + 11D651071B8922AF00C3DD23 /* OCTToxAV.m in Sources */, + 9CB44C1F1B84DBA3007FA7B6 /* OCTTox.m in Sources */, + F50269671C31CB0700E05351 /* OCTAudioQueueTests.m in Sources */, + 11D650D11B89225400C3DD23 /* OCTAudioEngine.m in Sources */, + 9CB44C121B84DBA3007FA7B6 /* OCTDefaultFileStorage.m in Sources */, + 9CB44C0B1B84DBA3007FA7B6 /* OCTNode.m in Sources */, + 9CB44C081B84DBA3007FA7B6 /* OCTFriend.m in Sources */, + 11BB6EB31CC3932B00A531A8 /* OCTFileToolsTests.m in Sources */, + 11AF89021C98C25A00AD1D9F /* OCTFileBaseOperation.m in Sources */, + 9CB44CCC1B84DF46007FA7B6 /* OCTSubmanagerFriendsImplTests.m in Sources */, + 11D650FC1B89229B00C3DD23 /* OCTVideoEngine.m in Sources */, + 9CB44CB21B84DF46007FA7B6 /* OCTChatTests.m in Sources */, + 9CB44CC61B84DF46007FA7B6 /* OCTSubmanagerBootstrapImplTests.m in Sources */, + 9CB44C1D1B84DBA3007FA7B6 /* OCTToxConstants.m in Sources */, + 9CB44C141B84DBA3007FA7B6 /* OCTSubmanagerChatsImpl.m in Sources */, + 11D651261B89236A00C3DD23 /* OCTCallTimerTests.m in Sources */, + 9CB44C1B1B84DBA3007FA7B6 /* OCTManagerImpl.m in Sources */, + 11D6512C1B89238900C3DD23 /* OCTToxAVTests.m in Sources */, + 11D6512F1B89238F00C3DD23 /* OCTVideoEngineTests.m in Sources */, + 11D650ED1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m in Sources */, + 1183BC6A1C9EBBE5000CD310 /* OCTFileUploadOperation.m in Sources */, + 9CB44CC81B84DF46007FA7B6 /* OCTSubmanagerChatsImplTests.m in Sources */, + 9CB44C1C1B84DBA3007FA7B6 /* OCTManagerConstants.m in Sources */, + 9CB44C101B84DBA3007FA7B6 /* OCTManagerConfiguration.m in Sources */, + 1183BC901CA036AA000CD310 /* OCTFileDataOutput.m in Sources */, + 9CB44CBA1B84DF46007FA7B6 /* OCTManagerConfigurationTests.m in Sources */, + 11D650DA1B89226800C3DD23 /* OCTCall.m in Sources */, + 9CB44CCA1B84DF46007FA7B6 /* OCTSubmanagerFilesImplTests.m in Sources */, + 1183BC8A1CA035CD000CD310 /* OCTFileDataInput.m in Sources */, + 9CB44C091B84DBA3007FA7B6 /* OCTMessageAbstract.m in Sources */, + 9CB44CBE1B84DF46007FA7B6 /* OCTMessageAbstractTests.m in Sources */, + 11D651001B89229B00C3DD23 /* OCTVideoView.m in Sources */, + 9CB44CB41B84DF46007FA7B6 /* OCTDefaultFileStorageTests.m in Sources */, + 11AF88FC1C98976F00AD1D9F /* OCTFileDownloadOperation.m in Sources */, + 9CB44CCE1B84DF46007FA7B6 /* OCTSubmanagerObjectsImplTests.m in Sources */, + 11E80D841B98C647008DFC47 /* OCTSettingsStorageObject.m in Sources */, + 9CB44C0C1B84DBA3007FA7B6 /* OCTObject.m in Sources */, + 9CB44C0E1B84DBA3007FA7B6 /* OCTMessageText.m in Sources */, + 113187621DD683E400E6FAA2 /* OCTSendMessageOperation.m in Sources */, + 9CB44C0A1B84DBA3007FA7B6 /* OCTFriendRequest.m in Sources */, + 1183BC841CA02AA9000CD310 /* OCTFilePathOutput.m in Sources */, + 9CB44CB81B84DF46007FA7B6 /* OCTFriendRequestTests.m in Sources */, + 11D651291B89237D00C3DD23 /* OCTSubmanagerCallsImplTests.m in Sources */, + 119B13E61B9AF5A2006DA6FF /* OCTToxEncryptSave.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9CB44C2D1B84DCCC007FA7B6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 11AF89031C98C25A00AD1D9F /* OCTFileBaseOperation.m in Sources */, + 1183BC6B1C9EBBE5000CD310 /* OCTFileUploadOperation.m in Sources */, + 11E80D851B98C647008DFC47 /* OCTSettingsStorageObject.m in Sources */, + 1183BC911CA036AA000CD310 /* OCTFileDataOutput.m in Sources */, + 9CB44C391B84DCCC007FA7B6 /* main.m in Sources */, + F5F6FA201C268B5000607306 /* OCTAudioQueue.m in Sources */, + 9CB1F95B1D5B671E00105858 /* OCTManagerFactory.m in Sources */, + 119B13E71B9AF5A2006DA6FF /* OCTToxEncryptSave.m in Sources */, + 1183BC971CA076BC000CD310 /* NSError+OCTFile.m in Sources */, + 9CB44C5F1B84DCFB007FA7B6 /* OCTSubmanagerFilesImpl.m in Sources */, + F009D9AE1C195DBA008243EF /* OCTBootStrapViewController.m in Sources */, + 9CB44C611B84DCFB007FA7B6 /* OCTSubmanagerObjectsImpl.m in Sources */, + F009D9AA1C195D77008243EF /* OCTMainWindowController.m in Sources */, + F09D0A461C1B9877008E74AF /* OCTUserViewController.m in Sources */, + 1183BC851CA02AA9000CD310 /* OCTFilePathOutput.m in Sources */, + 9CB44C541B84DCFB007FA7B6 /* OCTChat.m in Sources */, + 9CB44C371B84DCCC007FA7B6 /* AppDelegate.m in Sources */, + F07992DA1C2E5B1900B0D267 /* OCTCallsViewController.m in Sources */, + 11D651081B8922AF00C3DD23 /* OCTToxAV.m in Sources */, + 11D650FD1B89229B00C3DD23 /* OCTVideoEngine.m in Sources */, + 9CB44C661B84DCFB007FA7B6 /* OCTToxConstants.m in Sources */, + 9CB44C671B84DCFB007FA7B6 /* OCTToxOptions.m in Sources */, + 9CB44C641B84DCFB007FA7B6 /* OCTManagerConstants.m in Sources */, + 9CB44C621B84DCFB007FA7B6 /* OCTSubmanagerUserImpl.m in Sources */, + 1183BC8B1CA035CD000CD310 /* OCTFileDataInput.m in Sources */, + 9C3F49F71CDCE81B005E2A61 /* RLMCollectionChange+IndexSet.m in Sources */, + 11D650E81B89227300C3DD23 /* OCTMessageCall.m in Sources */, + 9CB44C5B1B84DCFB007FA7B6 /* OCTObject.m in Sources */, + 9CB44C5A1B84DCFB007FA7B6 /* OCTNode.m in Sources */, + 9CB44C551B84DCFB007FA7B6 /* OCTFriend.m in Sources */, + 9CB44C5E1B84DCFB007FA7B6 /* OCTSubmanagerChatsImpl.m in Sources */, + 9CB44C571B84DCFB007FA7B6 /* OCTMessageAbstract.m in Sources */, + 9CB44C4F1B84DCFB007FA7B6 /* OCTDefaultFileStorage.m in Sources */, + 9CB44C631B84DCFB007FA7B6 /* OCTManagerImpl.m in Sources */, + 9CB44C591B84DCFB007FA7B6 /* OCTMessageText.m in Sources */, + 11D650D21B89225400C3DD23 /* OCTAudioEngine.m in Sources */, + 1183BC7E1CA02755000CD310 /* OCTFilePathInput.m in Sources */, + 11D650F91B89229B00C3DD23 /* OCTPixelBufferPool.m in Sources */, + 11D650DB1B89226800C3DD23 /* OCTCall.m in Sources */, + 113187631DD683E400E6FAA2 /* OCTSendMessageOperation.m in Sources */, + 11D650DF1B89226800C3DD23 /* OCTCall+Utilities.m in Sources */, + 9CB44C651B84DCFB007FA7B6 /* OCTTox.m in Sources */, + F02C7EB31C1CCF1200D144BD /* OCTFriendsViewController.m in Sources */, + 9CB44C521B84DCFB007FA7B6 /* OCTRealmManager.m in Sources */, + 11D650E31B89226800C3DD23 /* OCTCallTimer.m in Sources */, + 11BB6EB01CC3930A00A531A8 /* OCTFileTools.m in Sources */, + 9CB44C561B84DCFB007FA7B6 /* OCTFriendRequest.m in Sources */, + 11D650EE1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m in Sources */, + 9CB44C581B84DCFB007FA7B6 /* OCTMessageFile.m in Sources */, + 9CB44C5D1B84DCFB007FA7B6 /* OCTSubmanagerBootstrapImpl.m in Sources */, + 9CB44C511B84DCFB007FA7B6 /* OCTManagerConfiguration.m in Sources */, + 11D651011B89229B00C3DD23 /* OCTVideoView.m in Sources */, + 11D6510C1B8922AF00C3DD23 /* OCTToxAVConstants.m in Sources */, + 11AF89111C9D570000AD1D9F /* OCTFilesViewController.m in Sources */, + F019291B1C2797D2001A5F45 /* OCTConversationViewController.m in Sources */, + 11AF88FD1C98976F00AD1D9F /* OCTFileDownloadOperation.m in Sources */, + 9CB44C601B84DCFB007FA7B6 /* OCTSubmanagerFriendsImpl.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9CB44C3F1B84DCCC007FA7B6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F5BF42791C2D1F7D008283E0 /* OCTAudioQueue.m in Sources */, + 11D6510D1B8922AF00C3DD23 /* OCTToxAVConstants.m in Sources */, + 11D650FA1B89229B00C3DD23 /* OCTPixelBufferPool.m in Sources */, + 9CB44C801B84DCFB007FA7B6 /* OCTNode.m in Sources */, + 11D650E91B89227300C3DD23 /* OCTMessageCall.m in Sources */, + 112779D51B9B6F0B00E475B4 /* OCTToxEncryptSaveTests.m in Sources */, + 9CB44C7A1B84DCFB007FA7B6 /* OCTChat.m in Sources */, + 11D651251B89236A00C3DD23 /* OCTAudioEngineTests.m in Sources */, + 9CB44CD31B84DF46007FA7B6 /* OCTToxTests.m in Sources */, + 9CB44CD11B84DF46007FA7B6 /* OCTSubmanagerUserImplTests.m in Sources */, + 11BB6EB11CC3930A00A531A8 /* OCTFileTools.m in Sources */, + 9CB44CC31B84DF46007FA7B6 /* OCTRealmTests.m in Sources */, + 9CB44CC11B84DF46007FA7B6 /* OCTObjectTests.m in Sources */, + 9CB44CBD1B84DF46007FA7B6 /* OCTManagerImplTests.m in Sources */, + 1183BC7F1CA02755000CD310 /* OCTFilePathInput.m in Sources */, + 9CB44C7B1B84DCFB007FA7B6 /* OCTFriend.m in Sources */, + 11D650E41B89226800C3DD23 /* OCTCallTimer.m in Sources */, + 9CB44C811B84DCFB007FA7B6 /* OCTObject.m in Sources */, + 9CB44C751B84DCFB007FA7B6 /* OCTDefaultFileStorage.m in Sources */, + 1183BC981CA076BC000CD310 /* NSError+OCTFile.m in Sources */, + 9CB1F95C1D5B671E00105858 /* OCTManagerFactory.m in Sources */, + 11D650E01B89226800C3DD23 /* OCTCall+Utilities.m in Sources */, + 11D651091B8922AF00C3DD23 /* OCTToxAV.m in Sources */, + F50269681C31CB0700E05351 /* OCTAudioQueueTests.m in Sources */, + 11D650D31B89225400C3DD23 /* OCTAudioEngine.m in Sources */, + 9CB44C7E1B84DCFB007FA7B6 /* OCTMessageFile.m in Sources */, + 9CB44C7F1B84DCFB007FA7B6 /* OCTMessageText.m in Sources */, + 9CB44C861B84DCFB007FA7B6 /* OCTSubmanagerFriendsImpl.m in Sources */, + 9CB44C871B84DCFB007FA7B6 /* OCTSubmanagerObjectsImpl.m in Sources */, + 11AF89041C98C25A00AD1D9F /* OCTFileBaseOperation.m in Sources */, + 11BB6EB41CC3932B00A531A8 /* OCTFileToolsTests.m in Sources */, + 9CB44C831B84DCFB007FA7B6 /* OCTSubmanagerBootstrapImpl.m in Sources */, + 11D650FE1B89229B00C3DD23 /* OCTVideoEngine.m in Sources */, + 9CB44CCD1B84DF46007FA7B6 /* OCTSubmanagerFriendsImplTests.m in Sources */, + 9CB44CB31B84DF46007FA7B6 /* OCTChatTests.m in Sources */, + 9CB44CC71B84DF46007FA7B6 /* OCTSubmanagerBootstrapImplTests.m in Sources */, + 11D651271B89236A00C3DD23 /* OCTCallTimerTests.m in Sources */, + 11D6512D1B89238900C3DD23 /* OCTToxAVTests.m in Sources */, + 11D651301B89238F00C3DD23 /* OCTVideoEngineTests.m in Sources */, + 11D650EF1B89228C00C3DD23 /* OCTSubmanagerCallsImpl.m in Sources */, + 1183BC6C1C9EBBE5000CD310 /* OCTFileUploadOperation.m in Sources */, + 9CB44C7C1B84DCFB007FA7B6 /* OCTFriendRequest.m in Sources */, + 9CB44CC91B84DF46007FA7B6 /* OCTSubmanagerChatsImplTests.m in Sources */, + 9CB44C851B84DCFB007FA7B6 /* OCTSubmanagerFilesImpl.m in Sources */, + 9CB44C8D1B84DCFB007FA7B6 /* OCTToxOptions.m in Sources */, + 9CB44C7D1B84DCFB007FA7B6 /* OCTMessageAbstract.m in Sources */, + 9CB44C891B84DCFB007FA7B6 /* OCTManagerImpl.m in Sources */, + 1183BC921CA036AA000CD310 /* OCTFileDataOutput.m in Sources */, + 9CB44CBB1B84DF46007FA7B6 /* OCTManagerConfigurationTests.m in Sources */, + 11D650DC1B89226800C3DD23 /* OCTCall.m in Sources */, + 9CB44CCB1B84DF46007FA7B6 /* OCTSubmanagerFilesImplTests.m in Sources */, + 1183BC8C1CA035CD000CD310 /* OCTFileDataInput.m in Sources */, + 9CB44C8B1B84DCFB007FA7B6 /* OCTTox.m in Sources */, + 9CB44C8C1B84DCFB007FA7B6 /* OCTToxConstants.m in Sources */, + 9CB44CBF1B84DF46007FA7B6 /* OCTMessageAbstractTests.m in Sources */, + 9CB44C881B84DCFB007FA7B6 /* OCTSubmanagerUserImpl.m in Sources */, + 11D651021B89229B00C3DD23 /* OCTVideoView.m in Sources */, + 9CB44CB51B84DF46007FA7B6 /* OCTDefaultFileStorageTests.m in Sources */, + 11AF88FE1C98976F00AD1D9F /* OCTFileDownloadOperation.m in Sources */, + 9CB44CCF1B84DF46007FA7B6 /* OCTSubmanagerObjectsImplTests.m in Sources */, + 11E80D861B98C647008DFC47 /* OCTSettingsStorageObject.m in Sources */, + 9CB44C841B84DCFB007FA7B6 /* OCTSubmanagerChatsImpl.m in Sources */, + 9CB44C781B84DCFB007FA7B6 /* OCTRealmManager.m in Sources */, + 9CB44C8A1B84DCFB007FA7B6 /* OCTManagerConstants.m in Sources */, + 113187641DD683E400E6FAA2 /* OCTSendMessageOperation.m in Sources */, + 9CB44C771B84DCFB007FA7B6 /* OCTManagerConfiguration.m in Sources */, + 1183BC861CA02AA9000CD310 /* OCTFilePathOutput.m in Sources */, + 9CB44CB91B84DF46007FA7B6 /* OCTFriendRequestTests.m in Sources */, + 11D6512A1B89237D00C3DD23 /* OCTSubmanagerCallsImplTests.m in Sources */, + 119B13E81B9AF5A2006DA6FF /* OCTToxEncryptSave.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 9CB44B791B84D8C1007FA7B6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9CB44B5E1B84D8C1007FA7B6 /* iOSDemo */; + targetProxy = 9CB44B781B84D8C1007FA7B6 /* PBXContainerItemProxy */; + }; + 9CB44C451B84DCCC007FA7B6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9CB44C301B84DCCC007FA7B6 /* OSXDemo */; + targetProxy = 9CB44C441B84DCCC007FA7B6 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 9CB44B701B84D8C1007FA7B6 /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 9CB44B711B84D8C1007FA7B6 /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; + 9CB44C3C1B84DCCC007FA7B6 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 9CB44C3D1B84DCCC007FA7B6 /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 070BBB548773794BC7DBE0A6290FD8A6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_BITCODE = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 9CB44B801B84D8C1007FA7B6 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6F97E8DCCDE81B906C0CCAFE /* Pods-iOSDemo.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_BITCODE = NO; + ENABLE_NS_ASSERTIONS = YES; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = iOSDemo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_NO_PIE = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-l\"Pods-iOSDemo-BlocksKit\"", + "-l\"Pods-iOSDemo-CocoaLumberjack\"", + "-l\"Pods-iOSDemo-Masonry\"", + "-l\"Pods-iOSDemo-Realm\"", + "-l\"Pods-iOSDemo-TPCircularBuffer\"", + "-l\"Pods-iOSDemo-libopus-patched-config\"", + "-l\"Pods-iOSDemo-libsodium\"", + "-l\"Pods-iOSDemo-toxcore\"", + "-l\"c++\"", + "-l\"realm-ios\"", + "-framework", + "\"AudioToolbox\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"MessageUI\"", + "-framework", + "\"UIKit\"", + "-framework", + "\"vpx\"", + "-read_only_relocs", + suppress, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 9CB44B821B84D8C1007FA7B6 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 54FC7A218FD35DB03A172079 /* Pods-iOSDemoTests.release.xcconfig */; + buildSettings = { + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + INFOPLIST_FILE = iOSDemoTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-l\"Pods-iOSDemoTests-CocoaLumberjack\"", + "-l\"Pods-iOSDemoTests-OCMock\"", + "-l\"Pods-iOSDemoTests-Realm\"", + "-l\"Pods-iOSDemoTests-TPCircularBuffer\"", + "-l\"Pods-iOSDemoTests-libopus-patched-config\"", + "-l\"Pods-iOSDemoTests-libsodium\"", + "-l\"Pods-iOSDemoTests-toxcore\"", + "-l\"c++\"", + "-l\"realm-ios\"", + "-framework", + "\"AudioToolbox\"", + "-framework", + "\"vpx\"", + "-read_only_relocs", + suppress, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 9CB44C4C1B84DCCC007FA7B6 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 80EDC251089F9EE3EA655215 /* Pods-OSXDemo.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + INFOPLIST_FILE = OSXDemo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + }; + name = Release; + }; + 9CB44C4E1B84DCCC007FA7B6 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9BB556421978E2471480B277 /* Pods-OSXDemoTests.release.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + INFOPLIST_FILE = OSXDemoTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 9CB44B7F1B84D8C1007FA7B6 /* Build configuration list for PBXNativeTarget "iOSDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9CB44B801B84D8C1007FA7B6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9CB44B811B84D8C1007FA7B6 /* Build configuration list for PBXNativeTarget "iOSDemoTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9CB44B821B84D8C1007FA7B6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9CB44C4B1B84DCCC007FA7B6 /* Build configuration list for PBXNativeTarget "OSXDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9CB44C4C1B84DCCC007FA7B6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9CB44C4D1B84DCCC007FA7B6 /* Build configuration list for PBXNativeTarget "OSXDemoTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9CB44C4E1B84DCCC007FA7B6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CD70405E4519D77E9FDF252033749EF6 /* Build configuration list for PBXProject "objcTox" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 070BBB548773794BC7DBE0A6290FD8A6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 1A384FEB508C718A4A7522BB0A835B5F /* Project object */; +} diff --git a/local_pod_repo/objcTox/objective-c-style-guide.md b/local_pod_repo/objcTox/objective-c-style-guide.md new file mode 100644 index 0000000..c779266 --- /dev/null +++ b/local_pod_repo/objcTox/objective-c-style-guide.md @@ -0,0 +1,455 @@ +Forked from [NYTimes](https://github.com/NYTimes/objective-c-style-guide). + +## Introduction + +Here are some of the documents from Apple that informed the style guide. If something isn’t mentioned here, it’s probably covered in great detail in one of these: + +* [The Objective-C Programming Language](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html) +* [Cocoa Fundamentals Guide](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/Introduction/Introduction.html) +* [Coding Guidelines for Cocoa](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html) +* [iOS App Programming Guide](http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/Introduction/Introduction.html) + +## Table of Contents + +* [Dot Notation Syntax](#dot-notation-syntax) +* [Spacing](#spacing) +* [Conditionals](#conditionals) + * [Ternary Operator](#ternary-operator) +* [Error handling](#error-handling) +* [Methods](#methods) +* [Variables](#variables) +* [Naming](#naming) +* [Comments](#comments) +* [Init & Dealloc](#init-and-dealloc) +* [Literals](#literals) +* [CGRect Functions](#cgrect-functions) +* [Constants](#constants) +* [Enumerated Types](#enumerated-types) +* [Bitmasks](#bitmasks) +* [Private Properties](#private-properties) +* [Booleans](#booleans) +* [Singletons](#singletons) +* [Imports](#imports) +* [Xcode Project](#xcode-project) + +## Dot Notation Syntax + +Dot notation should **always** be used for accessing and mutating properties. Bracket notation is preferred in all other instances. + +**For example:** +```objc +view.backgroundColor = [UIColor orangeColor]; +[UIApplication sharedApplication].delegate; +``` + +**Not:** +```objc +[view setBackgroundColor:[UIColor orangeColor]]; +UIApplication.sharedApplication.delegate; +``` + +## Spacing + +* Indent using 4 spaces. Never indent with tabs. Be sure to set this preference in Xcode. +* Method braces and other braces (`if`/`else`/`switch`/`while` etc.) always open on the same line as the statement but close on a new line. + +**For example:** +```objc +if (user.isHappy) { + // Do something +} +else { + // Do something else +} +``` +* There should be exactly one blank line between methods to aid in visual clarity and organization. +* Whitespace within methods should be used to separate functionality (though often this can indicate an opportunity to split the method into several, smaller methods). In methods with long or verbose names, a single line of whitespace may be used to provide visual separation before the method’s body. +* `@synthesize` and `@dynamic` should each be declared on new lines in the implementation. + +## Conditionals + +Conditional bodies should always use braces even when a conditional body could be written without braces (e.g., it is one line only) to prevent errors. These errors include adding a second line and expecting it to be part of the if-statement. Another, [even more dangerous defect](http://programmers.stackexchange.com/a/16530) may happen where the line “inside” the if-statement is commented out, and the next line unwittingly becomes part of the if-statement. In addition, this style is more consistent with all other conditionals, and therefore more easily scannable. + +**For example:** +```objc +if (!error) { + return success; +} +``` + +**Not:** +```objc +if (!error) + return success; +``` + +or + +```objc +if (!error) return success; +``` + +### Ternary Operator + +The ternary operator, `?` , should only be used when it increases clarity or code neatness. A single condition is usually all that should be evaluated. Evaluating multiple conditions is usually more understandable as an if statement, or refactored into named variables. + +**For example:** +```objc +result = a > b ? x : y; +``` + +**Not:** +```objc +result = a > b ? x = c > d ? c : d : y; +``` + +## Error Handling + +When methods return an error parameter by reference, switch on the returned value, not the error variable. + +**For example:** +```objc +NSError *error; +if (![self trySomethingWithError:&error]) { + // Handle Error +} +``` + +**Not:** +```objc +NSError *error; +[self trySomethingWithError:&error]; +if (error) { + // Handle Error +} +``` + +Some of Apple’s APIs write garbage values to the error parameter (if non-NULL) in successful cases, so switching on the error can cause false negatives (and subsequently crash). + +## Methods + +In method signatures, there should be a space after the scope (`-` or `+` symbol). There should be a space between the method segments. + +**For example:** +```objc +- (void)setExampleText:(NSString *)text image:(UIImage *)image; +``` + +## Variables + +Variables should be named descriptively, with the variable’s name clearly communicating what the variable _is_ and pertinent information a programmer needs to use that value properly. + +**For example:** + +* `NSString *title`: It is reasonable to assume a “title” is a string. +* `NSString *titleHTML`: This indicates a title that may contain HTML which needs parsing for display. _“HTML” is needed for a programmer to use this variable effectively._ +* `NSAttributedString *titleAttributedString`: A title, already formatted for display. _`AttributedString` hints that this value is not just a vanilla title, and adding it could be a reasonable choice depending on context._ +* `NSDate *now`: _No further clarification is needed._ +* `NSDate *lastModifiedDate`: Simply `lastModified` can be ambiguous; depending on context, one could reasonably assume it is one of a few different types. +* `NSURL *URL` vs. `NSString *URLString`: In situations when a value can reasonably be represented by different classes, it is often useful to disambiguate in the variable’s name. +* `NSString *releaseDateString`: Another example where a value could be represented by another class, and the name can help disambiguate. + +Single letter variable names should be avoided except as simple counter variables in loops. + +Asterisks indicating a type is a pointer should be “attached to” the variable name. **For example,** `NSString *text` **not** `NSString* text` or `NSString * text`. + +Property definitions should be used in place of naked instance variables whenever possible. Direct instance variable access should be avoided except in initializer methods (`init`, `initWithCoder:`, etc…), `dealloc` methods and within custom setters and getters. For more information, see [Apple’s docs on using accessor methods in initializer methods and `dealloc`](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW6). + +**For example:** + +```objc +@interface OCTSection: NSObject + +@property (nonatomic) NSString *headline; + +@end +``` + +**Not:** + +```objc +@interface OCTSection : NSObject { + NSString *headline; +} +``` + +## Naming + +Apple naming conventions should be adhered to wherever possible, especially those related to [memory management rules](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html) ([NARC](http://stackoverflow.com/a/2865194/340508)). + +Long, descriptive method and variable names are good. + +**For example:** + +```objc +UIButton *settingsButton; +``` + +**Not** + +```objc +UIButton *setBut; +``` + +A three letter prefix (e.g., `OCT`) should always be used for class names and constants. Constants should be camel-case with all words capitalized and prefixed by the related class name for clarity. A two letter prefix (e.g., `NS`) is [reserved for use by Apple](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/DefiningClasses/DefiningClasses.html#//apple_ref/doc/uid/TP40011210-CH3-SW12). + +**For example:** + +```objc +static const NSTimeInterval OCTArticleViewControllerNavigationFadeAnimationDuration = 0.3; +``` + +**Not:** + +```objc +static const NSTimeInterval fadetime = 1.7; +``` + +Properties and local variables should be camel-case with the leading word being lowercase. + +Instance variables should be camel-case with the leading word being lowercase, and should be prefixed with an underscore. This is consistent with instance variables synthesized automatically by LLVM. **If LLVM can synthesize the variable automatically, then let it.** + +**For example:** + +```objc +@synthesize descriptiveVariableName = _descriptiveVariableName; +``` + +**Not:** + +```objc +id varnm; +``` + +## Comments + +When they are needed, comments should be used to explain **why** a particular piece of code does something. Any comments that are used must be kept up-to-date or deleted. + +Block comments should generally be avoided, as code should be as self-documenting as possible, with only the need for intermittent, few-line explanations. This does not apply to those comments used to generate documentation. + +## init and dealloc + +`dealloc` methods should be placed directly after the `init` method. + +`init` methods should be structured like this: + +```objc +- (instancetype)init +{ + self = [super init]; // or call the designated initializer + if (! self) { + return nil; + } + + // Custom initialization + + return self; +} +``` + +## Literals + +`NSString`, `NSDictionary`, `NSArray`, and `NSNumber` literals should be used whenever creating immutable instances of those objects. Pay special care that `nil` values not be passed into `NSArray` and `NSDictionary` literals, as this will cause a crash. + +**For example:** + +```objc +NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"]; +NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"}; +NSNumber *shouldUseLiterals = @YES; +NSNumber *buildingZIPCode = @10018; +``` + +**Not:** + +```objc +NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil]; +NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil]; +NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES]; +NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018]; +``` + +## `CGRect` Functions + +When accessing the `x`, `y`, `width`, or `height` of a `CGRect`, always use the [`CGGeometry` functions](http://developer.apple.com/library/ios/#documentation/graphicsimaging/reference/CGGeometry/Reference/reference.html) instead of direct struct member access. From Apple's `CGGeometry` reference: + +> All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics. + +**For example:** + +```objc +CGRect frame = self.view.frame; + +CGFloat x = CGRectGetMinX(frame); +CGFloat y = CGRectGetMinY(frame); +CGFloat width = CGRectGetWidth(frame); +CGFloat height = CGRectGetHeight(frame); +``` + +**Not:** + +```objc +CGRect frame = self.view.frame; + +CGFloat x = frame.origin.x; +CGFloat y = frame.origin.y; +CGFloat width = frame.size.width; +CGFloat height = frame.size.height; +``` + +## Constants + +Constants are preferred over in-line string literals or numbers, as they allow for easy reproduction of commonly used variables and can be quickly changed without the need for find and replace. Constants should be declared as `static` constants and not `#define`s unless explicitly being used as a macro. + +**For example:** + +```objc +static NSString *const OCTAboutViewControllerName = @"Name"; + +static const CGFloat COTImageThumbnailHeight = 50.0; +``` + +**Not:** + +```objc +#define CompanyName @"Name" + +#define thumbnailHeight 2 +``` + +## Enumerated Types + +When using `enum`s, use the new fixed underlying type specification, which provides stronger type checking and code completion. The SDK includes a macro to facilitate and encourage use of fixed underlying types: `NS_ENUM()`. + +**Example:** + +```objc +typedef NS_ENUM(NSInteger, OCTAdRequestState) { + OCTAdRequestStateInactive, + OCTAdRequestStateLoading +}; +``` + +## Bitmasks + +When working with bitmasks, use the `NS_OPTIONS` macro. + +**Example:** + +```objc +typedef NS_OPTIONS(NSUInteger, OCTAdCategory) { + OCTAdCategoryAutos = 1 << 0, + OCTAdCategoryJobs = 1 << 1, + OCTAdCategoryRealState = 1 << 2, + OCTAdCategoryTechnology = 1 << 3 +}; +``` + +## Private Properties + +Private properties should be declared in class extensions (anonymous categories) in the implementation file of a class. + +**For example:** + +```objc +@interface OCTAdvertisement () + +@property (nonatomic, strong) GADBannerView *googleAdView; +@property (nonatomic, strong) ADBannerView *iAdView; +@property (nonatomic, strong) UIWebView *adXWebView; + +@end +``` + +## Booleans + +Never compare something directly to `YES`, because `YES` is defined as `1`, and a `BOOL` in Objective-C is a `CHAR` type that is 8 bits long (so a value of `11111110` will return `NO` if compared to `YES`). + +**For an object pointer:** + +```objc +if (! someObject) { +} + +if (someObject) { +} +``` + +**For a `BOOL` value:** + +```objc +if (isAwesome) +if (someNumber.boolValue) +if (! someNumber.boolValue) +``` + +**Not:** + +```objc +if (isAwesome == YES) // Never do this. +``` + +If the name of a `BOOL` property is expressed as an adjective, the property’s name can omit the `is` prefix and specify the conventional name for the getter. + +**For example:** + +```objc +@property (assign, getter=isEditable) BOOL editable; +``` + +_Text and example taken from the [Cocoa Naming Guidelines](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingIvarsAndTypes.html#//apple_ref/doc/uid/20001284-BAJGIIJE)._ + +## Singletons + +Generally you don't want to use singletons. Don't use them. + +Singleton objects should use a thread-safe pattern for creating their shared instance. +```objc ++ (instancetype)sharedInstance { + static id sharedInstance = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[[self class] alloc] init]; + }); + + return sharedInstance; +} +``` +This will prevent [possible and sometimes frequent crashes](http://cocoasamurai.blogspot.com/2011/04/singletons-your-doing-them-wrong.html). + +## Imports + +If there is more than one import statement, group the statements [together](http://ashfurrow.com/blog/structuring-modern-objective-c). Commenting each group is optional. + +Note: For modules use the [@import](http://clang.llvm.org/docs/Modules.html#using-modules) syntax. + +```objc +// Frameworks +@import QuartzCore; + +// Models +#import "NYTUser.h" + +// Views +#import "NYTButton.h" +#import "NYTUserView.h" +``` + +## Xcode project + +The physical files should be kept in sync with the Xcode project files in order to avoid file sprawl. Any Xcode groups created should be reflected by folders in the filesystem. Code should be grouped not only by type, but also by feature for greater clarity. + +# Other Objective-C Style Guides + +If ours doesn’t fit your tastes, have a look at some other style guides: + +* [NYTimes](https://github.com/NYTimes/objective-c-style-guide) +* [Google](http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml) +* [GitHub](https://github.com/github/objective-c-conventions) +* [Adium](https://trac.adium.im/wiki/CodingStyle) +* [Sam Soffes](https://gist.github.com/soffes/812796) +* [CocoaDevCentral](http://cocoadevcentral.com/articles/000082.php) +* [Luke Redpath](http://lukeredpath.co.uk/blog/2011/06/28/my-objective-c-style-guide/) +* [Marcus Zarra](http://www.cimgf.com/zds-code-style-guide/) +* [Wikimedia](https://www.mediawiki.org/wiki/Wikimedia_Apps/Team/iOS/ObjectiveCStyleGuide) diff --git a/local_pod_repo/objcTox/pre-commit.sh b/local_pod_repo/objcTox/pre-commit.sh new file mode 100755 index 0000000..cb19aa1 --- /dev/null +++ b/local_pod_repo/objcTox/pre-commit.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +# git pre-commit hook that runs an Uncrustify stylecheck. +# Features: +# - abort commit when commit does not comply with the style guidelines +# - create a patch of the proposed style changes +# +# More info on Uncrustify: http://uncrustify.sourceforge.net/ + +# exit on error +set -e + +# necessary check for initial commit +if git rev-parse --verify HEAD >/dev/null 2>&1 ; then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + + +patch=$(./run-uncrustify.sh --create-patch) +if [ $? != 0 ]; then + exit 1 +fi + +# if no patch has been generated all is ok, clean up the file stub and exit +if [ ! -s "$patch" ] ; then + echo "Files in this commit comply with the uncrustify rules." + rm -f "$patch" + exit 0 +fi + +# a patch has been created, notify the user and exit +echo "\nUncrustify check failed! You can see a diff file\n$patch" + +echo "\nYou can apply these changes with:\ngit apply $patch\n" +echo "Aborting commit. Apply changes and commit again or skip checking with --no-verify (not recommended)." + +if [ ! -t 1 ] ; then + exit 1 +fi + +exec < /dev/tty + +while true; do + echo "Do you wish to open patch file? y/n" + read -p "" yn + case $yn in + [Yy]* ) open "$patch"; break;; + [Nn]* ) break;; + * ) echo "Please answer yes or no.";; + esac +done + +while true; do + echo "Apply patch file? y/n" + read -p "" yn + case $yn in + [Yy]* ) git apply "$patch"; break;; + [Nn]* ) break;; + * ) echo "Please answer yes or no.";; + esac +done + +exit 1 diff --git a/local_pod_repo/objcTox/run-uncrustify.sh b/local_pod_repo/objcTox/run-uncrustify.sh new file mode 100755 index 0000000..b20dc69 --- /dev/null +++ b/local_pod_repo/objcTox/run-uncrustify.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +# set uncrustify path or executable +# UNCRUSTIFY="/usr/bin/uncrustify" +UNCRUSTIFY="uncrustify" + +while [[ $# > 0 ]] +do + key="$1" + + case $key in + --check) + CHECK=true + ;; + --apply) + APPLY=true + ;; + --create-patch) + CREATEPATCH=true + ;; + *) + # unknown option + ;; + esac + shift # past argument or value +done + + +if [ "$CHECK" = true ] ; then + OPTIONS="--check" +elif [ "$APPLY" = true ] ; then + OPTIONS="--no-backup" +elif [ "$CREATEPATCH" = true ] ; then + OPTIONS="" +else + >&2 echo "Please specify either --check, --apply or --create-patch" + exit 1 +fi + + +if ! command -v "$UNCRUSTIFY" > /dev/null ; then + >&2 echo "Error: uncrustify executable not found.\n" + >&2 echo "Set the correct path in $UNCRUSTIFY.\n" + exit 1 +fi + +FILES="$(find {Classes,Tests,iOSDemo,iOSDemoTests,OSXDemo,OSXDemoTests} -name '*.h' -or -name '*.m')" + +if [ "$CHECK" = true ] || [ "$APPLY" = true ]; then + $UNCRUSTIFY -c uncrustify.cfg -l OC $OPTIONS $FILES + exit $? +fi + +# Create patch + +prefix="run-uncrustify" +suffix="$(date +%C%y-%m-%d_%Hh%Mm%Ss)" +patch="/tmp/$prefix-$suffix.patch" + +for file in $FILES; do + # escape special characters in the source filename: + # - '\': backslash needs to be escaped + # - '*': used as matching string => '*' would mean expansion + # (curiously, '?' must not be escaped) + # - '[': used as matching string => '[' would mean start of set + # - '|': used as sed split char instead of '/', so it needs to be escaped + # in the filename + # printf %s particularly important if the filename contains the % character + file_escaped_source=$(printf "%s" "$file" | sed -e 's/[\*[|]/\\&/g') + + # escape special characters in the target filename: + # phase 1 (characters escaped in the output diff): + # - '\': backslash needs to be escaped in the output diff + # - '"': quote needs to be escaped in the output diff if present inside + # of the filename, as it used to bracket the entire filename part + # phase 2 (characters escaped in the match replacement): + # - '\': backslash needs to be escaped again for sed itself + # (i.e. double escaping after phase 1) + # - '&': would expand to matched string + # - '|': used as sed split char instead of '/' + # printf %s particularly important if the filename contains the % character + file_escaped_target=$(printf "%s" "$file" | sed -e 's/[\"]/\\&/g' -e 's/[\&|]/\\&/g') + + $UNCRUSTIFY -q -c uncrustify.cfg -l OC $OPTIONS -f "$file" | \ + diff -u -- "$file" - | \ + sed -e "1s|--- $file_escaped_source|--- \"a/$file_escaped_target\"|" -e "2s|+++ -|+++ \"b/$file_escaped_target\"|" >> "$patch" +done + +echo "$patch" diff --git a/local_pod_repo/objcTox/uncrustify.cfg b/local_pod_repo/objcTox/uncrustify.cfg new file mode 100644 index 0000000..5d59a06 --- /dev/null +++ b/local_pod_repo/objcTox/uncrustify.cfg @@ -0,0 +1,1687 @@ +# Uncrustify 0.61 + +# +# General options +# + +# The type of line endings +newlines = lf # auto/lf/crlf/cr + +# The original size of tabs in the input +input_tab_size = 4 # number + +# The size of tabs in the output (only used if align_with_tabs=true) +output_tab_size = 4 # number + +# The ASCII value of the string escape char, usually 92 (\) or 94 (^). (Pawn) +string_escape_char = 92 # number + +# Alternate string escape char for Pawn. Only works right before the quote char. +string_escape_char2 = 0 # number + +# Allow interpreting '>=' and '>>=' as part of a template in 'void f(list>=val);'. +# If true (default), 'assert(x<0 && y>=3)' will be broken. +# Improvements to template detection may make this option obsolete. +tok_split_gte = false # false/true + +# Control what to do with the UTF-8 BOM (recommend 'remove') +utf8_bom = ignore # ignore/add/remove/force + +# If the file contains bytes with values between 128 and 255, but is not UTF-8, then output as UTF-8 +utf8_byte = false # false/true + +# Force the output encoding to UTF-8 +utf8_force = false # false/true + +# +# Indenting +# + +# The number of columns to indent per level. +# Usually 2, 3, 4, or 8. +indent_columns = 4 # number + +# The continuation indent. If non-zero, this overrides the indent of '(' and '=' continuation indents. +# For FreeBSD, this is set to 4. Negative value is absolute and not increased for each ( level +indent_continue = 0 # number + +# How to use tabs when indenting code +# 0=spaces only +# 1=indent with tabs to brace level, align with spaces +# 2=indent and align with tabs, using spaces when not on a tabstop +indent_with_tabs = 0 # number + +# Comments that are not a brace level are indented with tabs on a tabstop. +# Requires indent_with_tabs=2. If false, will use spaces. +indent_cmt_with_tabs = false # false/true + +# Whether to indent strings broken by '\' so that they line up +indent_align_string = false # false/true + +# The number of spaces to indent multi-line XML strings. +# Requires indent_align_string=True +indent_xml_string = 0 # number + +# Spaces to indent '{' from level +indent_brace = 0 # number + +# Whether braces are indented to the body level +indent_braces = false # false/true + +# Disabled indenting function braces if indent_braces is true +indent_braces_no_func = false # false/true + +# Disabled indenting class braces if indent_braces is true +indent_braces_no_class = false # false/true + +# Disabled indenting struct braces if indent_braces is true +indent_braces_no_struct = false # false/true + +# Indent based on the size of the brace parent, i.e. 'if' => 3 spaces, 'for' => 4 spaces, etc. +indent_brace_parent = false # false/true + +# Indent based on the paren open instead of the brace open in '({\n', default is to indent by brace. +indent_paren_open_brace = false # false/true + +# Whether the 'namespace' body is indented +indent_namespace = true # false/true + +# Only indent one namespace and no sub-namepaces. +# Requires indent_namespace=true. +indent_namespace_single_indent = false # false/true + +# The number of spaces to indent a namespace block +indent_namespace_level = 0 # number + +# If the body of the namespace is longer than this number, it won't be indented. +# Requires indent_namespace=true. Default=0 (no limit) +indent_namespace_limit = 0 # number + +# Whether the 'extern "C"' body is indented +indent_extern = false # false/true + +# Whether the 'class' body is indented +indent_class = true # false/true + +# Whether to indent the stuff after a leading base class colon +indent_class_colon = false # false/true + +# Whether to indent the stuff after a leading class initializer colon +indent_constr_colon = false # false/true + +# Virtual indent from the ':' for member initializers. Default is 2 +indent_ctor_init_leading = 2 # number + +# Additional indenting for constructor initializer list +indent_ctor_init = 0 # number + +# False=treat 'else\nif' as 'else if' for indenting purposes +# True=indent the 'if' one level +indent_else_if = false # false/true + +# Amount to indent variable declarations after a open brace. neg=relative, pos=absolute +indent_var_def_blk = 0 # number + +# Indent continued variable declarations instead of aligning. +indent_var_def_cont = false # false/true + +# True: force indentation of function definition to start in column 1 +# False: use the default behavior +indent_func_def_force_col1 = false # false/true + +# True: indent continued function call parameters one indent level +# False: align parameters under the open paren +indent_func_call_param = false # false/true + +# Same as indent_func_call_param, but for function defs +indent_func_def_param = false # false/true + +# Same as indent_func_call_param, but for function protos +indent_func_proto_param = false # false/true + +# Same as indent_func_call_param, but for class declarations +indent_func_class_param = false # false/true + +# Same as indent_func_call_param, but for class variable constructors +indent_func_ctor_var_param = false # false/true + +# Same as indent_func_call_param, but for templates +indent_template_param = false # false/true + +# Double the indent for indent_func_xxx_param options +indent_func_param_double = false # false/true + +# Indentation column for standalone 'const' function decl/proto qualifier +indent_func_const = 0 # number + +# Indentation column for standalone 'throw' function decl/proto qualifier +indent_func_throw = 0 # number + +# The number of spaces to indent a continued '->' or '.' +# Usually set to 0, 1, or indent_columns. +indent_member = indent_columns # number + +# Spaces to indent single line ('//') comments on lines before code +indent_sing_line_comments = 0 # number + +# If set, will indent trailing single line ('//') comments relative +# to the code instead of trying to keep the same absolute column +indent_relative_single_line_comments = false # false/true + +# Spaces to indent 'case' from 'switch' +# Usually 0 or indent_columns. +indent_switch_case = 4 # number + +# Spaces to shift the 'case' line, without affecting any other lines +# Usually 0. +indent_case_shift = 0 # number + +# Spaces to indent '{' from 'case'. +# By default, the brace will appear under the 'c' in case. +# Usually set to 0 or indent_columns. +indent_case_brace = 0 # number + +# Whether to indent comments found in first column +indent_col1_comment = true # false/true + +# How to indent goto labels +# >0 : absolute column where 1 is the leftmost column +# <=0 : subtract from brace indent +indent_label = 0 # number + +# Same as indent_label, but for access specifiers that are followed by a colon +indent_access_spec = 1 # number + +# Indent the code after an access specifier by one level. +# If set, this option forces 'indent_access_spec=0' +indent_access_spec_body = false # false/true + +# If an open paren is followed by a newline, indent the next line so that it lines up after the open paren (not recommended) +indent_paren_nl = false # false/true + +# Controls the indent of a close paren after a newline. +# 0: Indent to body level +# 1: Align under the open paren +# 2: Indent to the brace level +indent_paren_close = 2 # number + +# Controls the indent of a comma when inside a paren.If TRUE, aligns under the open paren +indent_comma_paren = false # false/true + +# Controls the indent of a BOOL operator when inside a paren.If TRUE, aligns under the open paren +indent_bool_paren = false # false/true + +# If 'indent_bool_paren' is true, controls the indent of the first expression. If TRUE, aligns the first expression to the following ones +indent_first_bool_expr = false # false/true + +# If an open square is followed by a newline, indent the next line so that it lines up after the open square (not recommended) +indent_square_nl = false # false/true + +# Don't change the relative indent of ESQL/C 'EXEC SQL' bodies +indent_preserve_sql = false # false/true + +# Align continued statements at the '='. Default=True +# If FALSE or the '=' is followed by a newline, the next line is indent one tab. +indent_align_assign = true # false/true + +# Indent OC blocks at brace level instead of usual rules. +indent_oc_block = true # false/true + +# Indent OC blocks in a message relative to the parameter name. +# 0=use indent_oc_block rules, 1+=spaces to indent +indent_oc_block_msg = 0 # number + +# Minimum indent for subsequent parameters +indent_oc_msg_colon = 0 # number + +# If true, prioritize aligning with initial colon (and stripping spaces from lines, if necessary). +# Default is true. +indent_oc_msg_prioritize_first_colon = true # false/true + +# If indent_oc_block_msg and this option are on, blocks will be indented the way that Xcode does by default (from keyword if the parameter is on its own line; otherwise, from the previous indentation level). +indent_oc_block_msg_xcode_style = false # false/true + +# If indent_oc_block_msg and this option are on, blocks will be indented from where the brace is relative to a msg keyword. +indent_oc_block_msg_from_keyword = false # false/true + +# If indent_oc_block_msg and this option are on, blocks will be indented from where the brace is relative to a msg colon. +indent_oc_block_msg_from_colon = false # false/true + +# If indent_oc_block_msg and this option are on, blocks will be indented from where the block caret is. +indent_oc_block_msg_from_caret = false # false/true + +# If indent_oc_block_msg and this option are on, blocks will be indented from where the brace is. +indent_oc_block_msg_from_brace = false # false/true + +# +# Spacing options +# + +# Add or remove space around arithmetic operator '+', '-', '/', '*', etc +sp_arith = ignore # ignore/add/remove/force + +# Add or remove space around assignment operator '=', '+=', etc +sp_assign = add # ignore/add/remove/force + +# Add or remove space around '=' in C++11 lambda capture specifications. Overrides sp_assign +sp_cpp_lambda_assign = ignore # ignore/add/remove/force + +# Add or remove space after the capture specification in C++11 lambda. +sp_cpp_lambda_paren = ignore # ignore/add/remove/force + +# Add or remove space around assignment operator '=' in a prototype +sp_assign_default = ignore # ignore/add/remove/force + +# Add or remove space before assignment operator '=', '+=', etc. Overrides sp_assign. +sp_before_assign = ignore # ignore/add/remove/force + +# Add or remove space after assignment operator '=', '+=', etc. Overrides sp_assign. +sp_after_assign = ignore # ignore/add/remove/force + +# Add or remove space in 'NS_ENUM (' +sp_enum_paren = remove # ignore/add/remove/force + +# Add or remove space around assignment '=' in enum +sp_enum_assign = add # ignore/add/remove/force + +# Add or remove space around preprocessor '##' concatenation operator. Default=Add +sp_pp_concat = add # ignore/add/remove/force + +# Add or remove space after preprocessor '#' stringify operator. Also affects the '#@' charizing operator. +sp_pp_stringify = ignore # ignore/add/remove/force + +# Add or remove space before preprocessor '#' stringify operator as in '#define x(y) L#y'. +sp_before_pp_stringify = ignore # ignore/add/remove/force + +# Add or remove space around boolean operators '&&' and '||' +sp_bool = add # ignore/add/remove/force + +# Add or remove space around compare operator '<', '>', '==', etc +sp_compare = add # ignore/add/remove/force + +# Add or remove space inside '(' and ')' +sp_inside_paren = remove # ignore/add/remove/force + +# Add or remove space between nested parens: '((' vs ') )' +sp_paren_paren = ignore # ignore/add/remove/force + +# Add or remove space between back-to-back parens: ')(' vs ') (' +sp_cparen_oparen = ignore # ignore/add/remove/force + +# Whether to balance spaces inside nested parens +sp_balance_nested_parens = false # false/true + +# Add or remove space between ')' and '{' +sp_paren_brace = add # ignore/add/remove/force + +# Add or remove space before pointer star '*' +sp_before_ptr_star = add # ignore/add/remove/force + +# Add or remove space before pointer star '*' that isn't followed by a variable name +# If set to 'ignore', sp_before_ptr_star is used instead. +sp_before_unnamed_ptr_star = ignore # ignore/add/remove/force + +# Add or remove space between pointer stars '*' +sp_between_ptr_star = remove # ignore/add/remove/force + +# Add or remove space after pointer star '*', if followed by a word. +sp_after_ptr_star = remove # ignore/add/remove/force + +# Add or remove space after pointer star '*', if followed by a qualifier. +sp_after_ptr_star_qualifier = ignore # ignore/add/remove/force + +# Add or remove space after a pointer star '*', if followed by a func proto/def. +sp_after_ptr_star_func = ignore # ignore/add/remove/force + +# Add or remove space after a pointer star '*', if followed by an open paren (function types). +sp_ptr_star_paren = ignore # ignore/add/remove/force + +# Add or remove space before a pointer star '*', if followed by a func proto/def. +sp_before_ptr_star_func = ignore # ignore/add/remove/force + +# Add or remove space before a reference sign '&' +sp_before_byref = add # ignore/add/remove/force + +# Add or remove space before a reference sign '&' that isn't followed by a variable name +# If set to 'ignore', sp_before_byref is used instead. +sp_before_unnamed_byref = ignore # ignore/add/remove/force + +# Add or remove space after reference sign '&', if followed by a word. +sp_after_byref = remove # ignore/add/remove/force + +# Add or remove space after a reference sign '&', if followed by a func proto/def. +sp_after_byref_func = ignore # ignore/add/remove/force + +# Add or remove space before a reference sign '&', if followed by a func proto/def. +sp_before_byref_func = ignore # ignore/add/remove/force + +# Add or remove space between type and word. Default=Force +sp_after_type = force # ignore/add/remove/force + +# Add or remove space before the paren in the D constructs 'template Foo(' and 'class Foo('. +sp_before_template_paren = ignore # ignore/add/remove/force + +# Add or remove space in 'template <' vs 'template<'. +# If set to ignore, sp_before_angle is used. +sp_template_angle = ignore # ignore/add/remove/force + +# Add or remove space before '<>' +sp_before_angle = ignore # ignore/add/remove/force + +# Add or remove space inside '<' and '>' +sp_inside_angle = ignore # ignore/add/remove/force + +# Add or remove space after '<>' +sp_after_angle = ignore # ignore/add/remove/force + +# Add or remove space between '<>' and '(' as found in 'new List();' +sp_angle_paren = ignore # ignore/add/remove/force + +# Add or remove space between '<>' and a word as in 'List m;' +sp_angle_word = ignore # ignore/add/remove/force + +# Add or remove space between '>' and '>' in '>>' (template stuff C++/C# only). Default=Add +sp_angle_shift = remove # ignore/add/remove/force + +# Permit removal of the space between '>>' in 'foo >' (C++11 only). Default=False +# sp_angle_shift cannot remove the space without this option. +sp_permit_cpp11_shift = true # false/true + +# Add or remove space before '(' of 'if', 'for', 'switch', and 'while' +sp_before_sparen = add # ignore/add/remove/force + +# Add or remove space inside if-condition '(' and ')' +sp_inside_sparen = remove # ignore/add/remove/force + +# Add or remove space before if-condition ')'. Overrides sp_inside_sparen. +sp_inside_sparen_close = ignore # ignore/add/remove/force + +# Add or remove space before if-condition '('. Overrides sp_inside_sparen. +sp_inside_sparen_open = ignore # ignore/add/remove/force + +# Add or remove space after ')' of 'if', 'for', 'switch', and 'while' +sp_after_sparen = ignore # ignore/add/remove/force + +# Add or remove space between ')' and '{' of 'if', 'for', 'switch', and 'while' +sp_sparen_brace = ignore # ignore/add/remove/force + +# Add or remove space between 'invariant' and '(' in the D language. +sp_invariant_paren = ignore # ignore/add/remove/force + +# Add or remove space after the ')' in 'invariant (C) c' in the D language. +sp_after_invariant_paren = ignore # ignore/add/remove/force + +# Add or remove space before empty statement ';' on 'if', 'for' and 'while' +sp_special_semi = ignore # ignore/add/remove/force + +# Add or remove space before ';'. Default=Remove +sp_before_semi = remove # ignore/add/remove/force + +# Add or remove space before ';' in non-empty 'for' statements +sp_before_semi_for = ignore # ignore/add/remove/force + +# Add or remove space before a semicolon of an empty part of a for statement. +sp_before_semi_for_empty = ignore # ignore/add/remove/force + +# Add or remove space after ';', except when followed by a comment. Default=Add +sp_after_semi = add # ignore/add/remove/force + +# Add or remove space after ';' in non-empty 'for' statements. Default=Force +sp_after_semi_for = force # ignore/add/remove/force + +# Add or remove space after the final semicolon of an empty part of a for statement: for ( ; ; ). +sp_after_semi_for_empty = ignore # ignore/add/remove/force + +# Add or remove space before '[' (except '[]') +sp_before_square = ignore # ignore/add/remove/force + +# Add or remove space before '[]' +sp_before_squares = ignore # ignore/add/remove/force + +# Add or remove space inside a non-empty '[' and ']' +sp_inside_square = ignore # ignore/add/remove/force + +# Add or remove space after ',' +sp_after_comma = add # ignore/add/remove/force + +# Add or remove space before ',' +sp_before_comma = remove # ignore/add/remove/force + +# Add or remove space between an open paren and comma: '(,' vs '( ,' +sp_paren_comma = force # ignore/add/remove/force + +# Add or remove space before the variadic '...' when preceded by a non-punctuator +sp_before_ellipsis = ignore # ignore/add/remove/force + +# Add or remove space after class ':' +sp_after_class_colon = ignore # ignore/add/remove/force + +# Add or remove space before class ':' +sp_before_class_colon = ignore # ignore/add/remove/force + +# Add or remove space after class constructor ':' +sp_after_constr_colon = ignore # ignore/add/remove/force + +# Add or remove space before class constructor ':' +sp_before_constr_colon = ignore # ignore/add/remove/force + +# Add or remove space before case ':'. Default=Remove +sp_before_case_colon = remove # ignore/add/remove/force + +# Add or remove space between 'operator' and operator sign +sp_after_operator = ignore # ignore/add/remove/force + +# Add or remove space between the operator symbol and the open paren, as in 'operator ++(' +sp_after_operator_sym = ignore # ignore/add/remove/force + +# Add or remove space after C/D cast, i.e. 'cast(int)a' vs 'cast(int) a' or '(int)a' vs '(int) a' +sp_after_cast = ignore # ignore/add/remove/force + +# Add or remove spaces inside cast parens +sp_inside_paren_cast = ignore # ignore/add/remove/force + +# Add or remove space between the type and open paren in a C++ cast, i.e. 'int(exp)' vs 'int (exp)' +sp_cpp_cast_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'sizeof' and '(' +sp_sizeof_paren = remove # ignore/add/remove/force + +# Add or remove space after the tag keyword (Pawn) +sp_after_tag = ignore # ignore/add/remove/force + +# Add or remove space inside enum '{' and '}' +sp_inside_braces_enum = ignore # ignore/add/remove/force + +# Add or remove space inside struct/union '{' and '}' +sp_inside_braces_struct = ignore # ignore/add/remove/force + +# Add or remove space inside '{' and '}' +sp_inside_braces = ignore # ignore/add/remove/force + +# Add or remove space inside '{}' +sp_inside_braces_empty = ignore # ignore/add/remove/force + +# Add or remove space between return type and function name +# A minimum of 1 is forced except for pointer return types. +sp_type_func = ignore # ignore/add/remove/force + +# Add or remove space between function name and '(' on function declaration +sp_func_proto_paren = ignore # ignore/add/remove/force + +# Add or remove space between function name and '(' on function definition +sp_func_def_paren = ignore # ignore/add/remove/force + +# Add or remove space inside empty function '()' +sp_inside_fparens = ignore # ignore/add/remove/force + +# Add or remove space inside function '(' and ')' +sp_inside_fparen = ignore # ignore/add/remove/force + +# Add or remove space inside the first parens in the function type: 'void (*x)(...)' +sp_inside_tparen = remove # ignore/add/remove/force + +# Add or remove between the parens in the function type: 'void (*x)(...)' +sp_after_tparen_close = remove # ignore/add/remove/force + +# Add or remove space between ']' and '(' when part of a function call. +sp_square_fparen = add # ignore/add/remove/force + +# Add or remove space between ')' and '{' of function +sp_fparen_brace = add # ignore/add/remove/force + +# Java: Add or remove space between ')' and '{{' of double brace initializer. +sp_fparen_dbrace = ignore # ignore/add/remove/force + +# Add or remove space between function name and '(' on function calls +sp_func_call_paren = remove # ignore/add/remove/force + +# Add or remove space between function name and '()' on function calls without parameters. +# If set to 'ignore' (the default), sp_func_call_paren is used. +sp_func_call_paren_empty = ignore # ignore/add/remove/force + +# Add or remove space between the user function name and '(' on function calls +# You need to set a keyword to be a user function, like this: 'set func_call_user _' in the config file. +sp_func_call_user_paren = ignore # ignore/add/remove/force + +# Add or remove space between a constructor/destructor and the open paren +sp_func_class_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'return' and '(' +sp_return_paren = add # ignore/add/remove/force + +# Add or remove space between '__attribute__' and '(' +sp_attribute_paren = remove # ignore/add/remove/force + +# Add or remove space between 'defined' and '(' in '#if defined (FOO)' +sp_defined_paren = add # ignore/add/remove/force + +# Add or remove space between 'throw' and '(' in 'throw (something)' +sp_throw_paren = remove # ignore/add/remove/force + +# Add or remove space between 'throw' and anything other than '(' as in '@throw [...];' +sp_after_throw = add # ignore/add/remove/force + +# Add or remove space between 'catch' and '(' in 'catch (something) { }' +# If set to ignore, sp_before_sparen is used. +sp_catch_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'version' and '(' in 'version (something) { }' (D language) +# If set to ignore, sp_before_sparen is used. +sp_version_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'scope' and '(' in 'scope (something) { }' (D language) +# If set to ignore, sp_before_sparen is used. +sp_scope_paren = ignore # ignore/add/remove/force + +# Add or remove space between macro and value +sp_macro = add # ignore/add/remove/force + +# Add or remove space between macro function ')' and value +sp_macro_func = add # ignore/add/remove/force + +# Add or remove space between 'else' and '{' if on the same line +sp_else_brace = add # ignore/add/remove/force + +# Add or remove space between '}' and 'else' if on the same line +sp_brace_else = ignore # ignore/add/remove/force + +# Add or remove space between '}' and the name of a typedef on the same line +sp_brace_typedef = add # ignore/add/remove/force + +# Add or remove space between 'catch' and '{' if on the same line +sp_catch_brace = add # ignore/add/remove/force + +# Add or remove space between '}' and 'catch' if on the same line +sp_brace_catch = add # ignore/add/remove/force + +# Add or remove space between 'finally' and '{' if on the same line +sp_finally_brace = add # ignore/add/remove/force + +# Add or remove space between '}' and 'finally' if on the same line +sp_brace_finally = add # ignore/add/remove/force + +# Add or remove space between 'try' and '{' if on the same line +sp_try_brace = add # ignore/add/remove/force + +# Add or remove space between get/set and '{' if on the same line +sp_getset_brace = add # ignore/add/remove/force + +# Add or remove space between a variable and '{' for C++ uniform initialization +sp_word_brace = add # ignore/add/remove/force + +# Add or remove space between a variable and '{' for a namespace +sp_word_brace_ns = add # ignore/add/remove/force + +# Add or remove space before the '::' operator +sp_before_dc = ignore # ignore/add/remove/force + +# Add or remove space after the '::' operator +sp_after_dc = ignore # ignore/add/remove/force + +# Add or remove around the D named array initializer ':' operator +sp_d_array_colon = ignore # ignore/add/remove/force + +# Add or remove space after the '!' (not) operator. Default=Remove +sp_not = add # ignore/add/remove/force + +# Add or remove space after the '~' (invert) operator. Default=Remove +sp_inv = remove # ignore/add/remove/force + +# Add or remove space after the '&' (address-of) operator. Default=Remove +# This does not affect the spacing after a '&' that is part of a type. +sp_addr = remove # ignore/add/remove/force + +# Add or remove space around the '.' or '->' operators. Default=Remove +sp_member = remove # ignore/add/remove/force + +# Add or remove space after the '*' (dereference) operator. Default=Remove +# This does not affect the spacing after a '*' that is part of a type. +sp_deref = remove # ignore/add/remove/force + +# Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'. Default=Remove +sp_sign = remove # ignore/add/remove/force + +# Add or remove space before or after '++' and '--', as in '(--x)' or 'y++;'. Default=Remove +sp_incdec = remove # ignore/add/remove/force + +# Add or remove space before a backslash-newline at the end of a line. Default=Add +sp_before_nl_cont = add # ignore/add/remove/force + +# Add or remove space after the scope '+' or '-', as in '-(void) foo;' or '+(int) bar;' +sp_after_oc_scope = add # ignore/add/remove/force + +# Add or remove space after the colon in message specs +# '-(int) f:(int) x;' vs '-(int) f: (int) x;' +sp_after_oc_colon = remove # ignore/add/remove/force + +# Add or remove space before the colon in message specs +# '-(int) f: (int) x;' vs '-(int) f : (int) x;' +sp_before_oc_colon = remove # ignore/add/remove/force + +# Add or remove space after the colon in immutable dictionary expression +# 'NSDictionary *test = @{@"foo": @"bar"};' +sp_after_oc_dict_colon = add # ignore/add/remove/force + +# Add or remove space before the colon in immutable dictionary expression +# 'NSDictionary *test = @{@"foo" :@"bar"};' +sp_before_oc_dict_colon = add # ignore/add/remove/force + +# Add or remove space after the colon in message specs +# '[object setValue:1];' vs '[object setValue: 1];' +sp_after_send_oc_colon = remove # ignore/add/remove/force + +# Add or remove space before the colon in message specs +# '[object setValue:1];' vs '[object setValue :1];' +sp_before_send_oc_colon = remove # ignore/add/remove/force + +# Add or remove space after the (type) in message specs +# '-(int)f: (int) x;' vs '-(int)f: (int)x;' +sp_after_oc_type = remove # ignore/add/remove/force + +# Add or remove space after the first (type) in message specs +# '-(int) f:(int)x;' vs '-(int)f:(int)x;' +sp_after_oc_return_type = remove # ignore/add/remove/force + +# Add or remove space between '@selector' and '(' +# '@selector(msgName)' vs '@selector (msgName)' +# Also applies to @protocol() constructs +sp_after_oc_at_sel = remove # ignore/add/remove/force + +# Add or remove space between '@selector(x)' and the following word +# '@selector(foo) a:' vs '@selector(foo)a:' +sp_after_oc_at_sel_parens = add # ignore/add/remove/force + +# Add or remove space inside '@selector' parens +# '@selector(foo)' vs '@selector( foo )' +# Also applies to @protocol() constructs +sp_inside_oc_at_sel_parens = remove # ignore/add/remove/force + +# Add or remove space before a block pointer caret +# '^int (int arg){...}' vs. ' ^int (int arg){...}' +sp_before_oc_block_caret = remove # ignore/add/remove/force + +# Add or remove space after a block pointer caret +# '^int (int arg){...}' vs. '^ int (int arg){...}' +sp_after_oc_block_caret = remove # ignore/add/remove/force + +# Add or remove space between the receiver and selector in a message. +# '[receiver selector ...]' +sp_after_oc_msg_receiver = ignore # ignore/add/remove/force + +# Add or remove space after @property. +sp_after_oc_property = add # ignore/add/remove/force + +# Add or remove space around the ':' in 'b ? t : f' +sp_cond_colon = add # ignore/add/remove/force + +# Add or remove space before the ':' in 'b ? t : f'. Overrides sp_cond_colon. +sp_cond_colon_before = ignore # ignore/add/remove/force + +# Add or remove space after the ':' in 'b ? t : f'. Overrides sp_cond_colon. +sp_cond_colon_after = ignore # ignore/add/remove/force + +# Add or remove space around the '?' in 'b ? t : f' +sp_cond_question = add # ignore/add/remove/force + +# Add or remove space before the '?' in 'b ? t : f'. Overrides sp_cond_question. +sp_cond_question_before = ignore # ignore/add/remove/force + +# Add or remove space after the '?' in 'b ? t : f'. Overrides sp_cond_question. +sp_cond_question_after = ignore # ignore/add/remove/force + +# In the abbreviated ternary form (a ?: b), add/remove space between ? and :.'. Overrides all other sp_cond_* options. +sp_cond_ternary_short = remove # ignore/add/remove/force + +# Fix the spacing between 'case' and the label. Only 'ignore' and 'force' make sense here. +sp_case_label = ignore # ignore/add/remove/force + +# Control the space around the D '..' operator. +sp_range = ignore # ignore/add/remove/force + +# Control the spacing after ':' in 'for (TYPE VAR : EXPR)' (Java) +sp_after_for_colon = ignore # ignore/add/remove/force + +# Control the spacing before ':' in 'for (TYPE VAR : EXPR)' (Java) +sp_before_for_colon = ignore # ignore/add/remove/force + +# Control the spacing in 'extern (C)' (D) +sp_extern_paren = ignore # ignore/add/remove/force + +# Control the space after the opening of a C++ comment '// A' vs '//A' +sp_cmt_cpp_start = add # ignore/add/remove/force + +# Controls the spaces between #else or #endif and a trailing comment +sp_endif_cmt = add # ignore/add/remove/force + +# Controls the spaces after 'new', 'delete', and 'delete[]' +sp_after_new = ignore # ignore/add/remove/force + +# Controls the spaces before a trailing or embedded comment +sp_before_tr_emb_cmt = add # ignore/add/remove/force + +# Number of spaces before a trailing or embedded comment +sp_num_before_tr_emb_cmt = 0 # number + +# Control space between a Java annotation and the open paren. +sp_annotation_paren = ignore # ignore/add/remove/force + +# +# Code alignment (not left column spaces/tabs) +# + +# Whether to keep non-indenting tabs +align_keep_tabs = false # false/true + +# Whether to use tabs for aligning +align_with_tabs = false # false/true + +# Whether to bump out to the next tab when aligning +align_on_tabstop = false # false/true + +# Whether to left-align numbers +align_number_left = false # false/true + +# Whether to keep whitespace not required for alignment. +align_keep_extra_space = false # false/true + +# Align variable definitions in prototypes and functions +align_func_params = false # false/true + +# Align parameters in single-line functions that have the same name. +# The function names must already be aligned with each other. +align_same_func_call_params = false # false/true + +# The span for aligning variable definitions (0=don't align) +align_var_def_span = 0 # number + +# How to align the star in variable definitions. +# 0=Part of the type 'void * foo;' +# 1=Part of the variable 'void *foo;' +# 2=Dangling 'void *foo;' +align_var_def_star_style = 0 # number + +# How to align the '&' in variable definitions. +# 0=Part of the type +# 1=Part of the variable +# 2=Dangling +align_var_def_amp_style = 0 # number + +# The threshold for aligning variable definitions (0=no limit) +align_var_def_thresh = 0 # number + +# The gap for aligning variable definitions +align_var_def_gap = 0 # number + +# Whether to align the colon in struct bit fields +align_var_def_colon = false # false/true + +# Whether to align any attribute after the variable name +align_var_def_attribute = false # false/true + +# Whether to align inline struct/enum/union variable definitions +align_var_def_inline = false # false/true + +# The span for aligning on '=' in assignments (0=don't align) +align_assign_span = 0 # number + +# The threshold for aligning on '=' in assignments (0=no limit) +align_assign_thresh = 0 # number + +# The span for aligning on '=' in enums (0=don't align) +align_enum_equ_span = 0 # number + +# The threshold for aligning on '=' in enums (0=no limit) +align_enum_equ_thresh = 0 # number + +# The span for aligning struct/union (0=don't align) +align_var_struct_span = 0 # number + +# The threshold for aligning struct/union member definitions (0=no limit) +align_var_struct_thresh = 0 # number + +# The gap for aligning struct/union member definitions +align_var_struct_gap = 0 # number + +# The span for aligning struct initializer values (0=don't align) +align_struct_init_span = 0 # number + +# The minimum space between the type and the synonym of a typedef +align_typedef_gap = 0 # number + +# The span for aligning single-line typedefs (0=don't align) +align_typedef_span = 0 # number + +# How to align typedef'd functions with other typedefs +# 0: Don't mix them at all +# 1: align the open paren with the types +# 2: align the function type name with the other type names +align_typedef_func = 0 # number + +# Controls the positioning of the '*' in typedefs. Just try it. +# 0: Align on typedef type, ignore '*' +# 1: The '*' is part of type name: typedef int *pint; +# 2: The '*' is part of the type, but dangling: typedef int *pint; +align_typedef_star_style = 0 # number + +# Controls the positioning of the '&' in typedefs. Just try it. +# 0: Align on typedef type, ignore '&' +# 1: The '&' is part of type name: typedef int &pint; +# 2: The '&' is part of the type, but dangling: typedef int &pint; +align_typedef_amp_style = 0 # number + +# The span for aligning comments that end lines (0=don't align) +align_right_cmt_span = 0 # number + +# If aligning comments, mix with comments after '}' and #endif with less than 3 spaces before the comment +align_right_cmt_mix = false # false/true + +# If a trailing comment is more than this number of columns away from the text it follows, +# it will qualify for being aligned. This has to be > 0 to do anything. +align_right_cmt_gap = 0 # number + +# Align trailing comment at or beyond column N; 'pulls in' comments as a bonus side effect (0=ignore) +align_right_cmt_at_col = 0 # number + +# The span for aligning function prototypes (0=don't align) +align_func_proto_span = 0 # number + +# Minimum gap between the return type and the function name. +align_func_proto_gap = 0 # number + +# Align function protos on the 'operator' keyword instead of what follows +align_on_operator = false # false/true + +# Whether to mix aligning prototype and variable declarations. +# If true, align_var_def_XXX options are used instead of align_func_proto_XXX options. +align_mix_var_proto = false # false/true + +# Align single-line functions with function prototypes, uses align_func_proto_span +align_single_line_func = false # false/true + +# Aligning the open brace of single-line functions. +# Requires align_single_line_func=true, uses align_func_proto_span +align_single_line_brace = false # false/true + +# Gap for align_single_line_brace. +align_single_line_brace_gap = 0 # number + +# The span for aligning ObjC msg spec (0=don't align) +align_oc_msg_spec_span = 0 # number + +# Whether to align macros wrapped with a backslash and a newline. +# This will not work right if the macro contains a multi-line comment. +align_nl_cont = false # false/true + +# # Align macro functions and variables together +align_pp_define_together = false # false/true + +# The minimum space between label and value of a preprocessor define +align_pp_define_gap = 0 # number + +# The span for aligning on '#define' bodies (0=don't align, other=number of lines including comments between blocks) +align_pp_define_span = 0 # number + +# Align lines that start with '<<' with previous '<<'. Default=true +align_left_shift = true # false/true + +# Span for aligning parameters in an Obj-C message call on the ':' (0=don't align) +align_oc_msg_colon_span = 1 # number + +# If true, always align with the first parameter, even if it is too short. +align_oc_msg_colon_first = false # false/true + +# Aligning parameters in an Obj-C '+' or '-' declaration on the ':' +align_oc_decl_colon = true # false/true + +# +# Newline adding and removing options +# + +# Whether to collapse empty blocks between '{' and '}' +nl_collapse_empty_body = true # false/true + +# Don't split one-line braced assignments - 'foo_t f = { 1, 2 };' +nl_assign_leave_one_liners = false # false/true + +# Don't split one-line braced statements inside a class xx { } body +nl_class_leave_one_liners = false # false/true + +# Don't split one-line enums: 'enum foo { BAR = 15 };' +nl_enum_leave_one_liners = false # false/true + +# Don't split one-line get or set functions +nl_getset_leave_one_liners = false # false/true + +# Don't split one-line function definitions - 'int foo() { return 0; }' +nl_func_leave_one_liners = false # false/true + +# Don't split one-line C++11 lambdas - '[]() { return 0; }' +nl_cpp_lambda_leave_one_liners = false # false/true + +# Don't split one-line if/else statements - 'if(a) b++;' +nl_if_leave_one_liners = false # false/true + +# Don't split one-line OC messages +nl_oc_msg_leave_one_liner = false # false/true + +# Add or remove newlines at the start of the file +nl_start_of_file = remove # ignore/add/remove/force + +# The number of newlines at the start of the file (only used if nl_start_of_file is 'add' or 'force' +nl_start_of_file_min = 0 # number + +# Add or remove newline at the end of the file +nl_end_of_file = add # ignore/add/remove/force + +# The number of newlines at the end of the file (only used if nl_end_of_file is 'add' or 'force') +nl_end_of_file_min = 1 # number + +# Add or remove newline between '=' and '{' +nl_assign_brace = ignore # ignore/add/remove/force + +# Add or remove newline between '=' and '[' (D only) +nl_assign_square = ignore # ignore/add/remove/force + +# Add or remove newline after '= [' (D only). Will also affect the newline before the ']' +nl_after_square_assign = ignore # ignore/add/remove/force + +# The number of blank lines after a block of variable definitions at the top of a function body +# 0 = No change (default) +nl_func_var_def_blk = 0 # number + +# The number of newlines before a block of typedefs +# 0 = No change (default) +nl_typedef_blk_start = 0 # number + +# The number of newlines after a block of typedefs +# 0 = No change (default) +nl_typedef_blk_end = 0 # number + +# The maximum consecutive newlines within a block of typedefs +# 0 = No change (default) +nl_typedef_blk_in = 0 # number + +# The number of newlines before a block of variable definitions not at the top of a function body +# 0 = No change (default) +nl_var_def_blk_start = 0 # number + +# The number of newlines after a block of variable definitions not at the top of a function body +# 0 = No change (default) +nl_var_def_blk_end = 0 # number + +# The maximum consecutive newlines within a block of variable definitions +# 0 = No change (default) +nl_var_def_blk_in = 0 # number + +# Add or remove newline between a function call's ')' and '{', as in: +# list_for_each(item, &list) { } +nl_fcall_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'enum' and '{' +nl_enum_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'struct and '{' +nl_struct_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'union' and '{' +nl_union_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'if' and '{' +nl_if_brace = remove # ignore/add/remove/force + +# Add or remove newline between '}' and 'else' +nl_brace_else = add # ignore/add/remove/force + +# Add or remove newline between 'else if' and '{' +# If set to ignore, nl_if_brace is used instead +nl_elseif_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'else' and '{' +nl_else_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'else' and 'if' +nl_else_if = remove # ignore/add/remove/force + +# Add or remove newline between '}' and 'finally' +nl_brace_finally = ignore # ignore/add/remove/force + +# Add or remove newline between 'finally' and '{' +nl_finally_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'try' and '{' +nl_try_brace = remove # ignore/add/remove/force + +# Add or remove newline between get/set and '{' +nl_getset_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'for' and '{' +nl_for_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'catch' and '{' +nl_catch_brace = remove # ignore/add/remove/force + +# Add or remove newline between '}' and 'catch' +nl_brace_catch = add # ignore/add/remove/force + +# Add or remove newline between '}' and ']' +nl_brace_square = remove # ignore/add/remove/force + +# Add or remove newline between '}' and ')' in a function invocation +nl_brace_fparen = remove # ignore/add/remove/force + +# Add or remove newline between 'while' and '{' +nl_while_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'scope (x)' and '{' (D) +nl_scope_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'unittest' and '{' (D) +nl_unittest_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'version (x)' and '{' (D) +nl_version_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'using' and '{' +nl_using_brace = ignore # ignore/add/remove/force + +# Add or remove newline between two open or close braces. +# Due to general newline/brace handling, REMOVE may not work. +nl_brace_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'do' and '{' +nl_do_brace = remove # ignore/add/remove/force + +# Add or remove newline between '}' and 'while' of 'do' statement +nl_brace_while = add # ignore/add/remove/force + +# Add or remove newline between 'switch' and '{' +nl_switch_brace = remove # ignore/add/remove/force + +# Add a newline between ')' and '{' if the ')' is on a different line than the if/for/etc. +# Overrides nl_for_brace, nl_if_brace, nl_switch_brace, nl_while_switch, and nl_catch_brace. +nl_multi_line_cond = false # false/true + +# Force a newline in a define after the macro name for multi-line defines. +nl_multi_line_define = false # false/true + +# Whether to put a newline before 'case' statement +nl_before_case = false # false/true + +# Add or remove newline between ')' and 'throw' +nl_before_throw = ignore # ignore/add/remove/force + +# Whether to put a newline after 'case' statement +nl_after_case = false # false/true + +# Add or remove a newline between a case ':' and '{'. Overrides nl_after_case. +nl_case_colon_brace = ignore # ignore/add/remove/force + +# Newline between namespace and { +nl_namespace_brace = add # ignore/add/remove/force + +# Add or remove newline between 'template<>' and whatever follows. +nl_template_class = ignore # ignore/add/remove/force + +# Add or remove newline between 'class' and '{' +nl_class_brace = add # ignore/add/remove/force + +# Add or remove newline after each ',' in the class base list +nl_class_init_args = ignore # ignore/add/remove/force + +# Add or remove newline after each ',' in the constructor member initialization +nl_constr_init_args = ignore # ignore/add/remove/force + +# Add or remove newline between return type and function name in a function definition +nl_func_type_name = ignore # ignore/add/remove/force + +# Add or remove newline between return type and function name inside a class {} +# Uses nl_func_type_name or nl_func_proto_type_name if set to ignore. +nl_func_type_name_class = ignore # ignore/add/remove/force + +# Add or remove newline between function scope and name in a definition +# Controls the newline after '::' in 'void A::f() { }' +nl_func_scope_name = ignore # ignore/add/remove/force + +# Add or remove newline between return type and function name in a prototype +nl_func_proto_type_name = ignore # ignore/add/remove/force + +# Add or remove newline between a function name and the opening '(' +nl_func_paren = ignore # ignore/add/remove/force + +# Add or remove newline between a function name and the opening '(' in the definition +nl_func_def_paren = ignore # ignore/add/remove/force + +# Add or remove newline after '(' in a function declaration +nl_func_decl_start = ignore # ignore/add/remove/force + +# Add or remove newline after '(' in a function definition +nl_func_def_start = ignore # ignore/add/remove/force + +# Overrides nl_func_decl_start when there is only one parameter. +nl_func_decl_start_single = ignore # ignore/add/remove/force + +# Overrides nl_func_def_start when there is only one parameter. +nl_func_def_start_single = ignore # ignore/add/remove/force + +# Add or remove newline after each ',' in a function declaration +nl_func_decl_args = ignore # ignore/add/remove/force + +# Add or remove newline after each ',' in a function definition +nl_func_def_args = ignore # ignore/add/remove/force + +# Add or remove newline before the ')' in a function declaration +nl_func_decl_end = ignore # ignore/add/remove/force + +# Add or remove newline before the ')' in a function definition +nl_func_def_end = ignore # ignore/add/remove/force + +# Overrides nl_func_decl_end when there is only one parameter. +nl_func_decl_end_single = ignore # ignore/add/remove/force + +# Overrides nl_func_def_end when there is only one parameter. +nl_func_def_end_single = ignore # ignore/add/remove/force + +# Add or remove newline between '()' in a function declaration. +nl_func_decl_empty = ignore # ignore/add/remove/force + +# Add or remove newline between '()' in a function definition. +nl_func_def_empty = ignore # ignore/add/remove/force + +# Whether to put each OC message parameter on a separate line +# See nl_oc_msg_leave_one_liner +nl_oc_msg_args = false # false/true + +# Add or remove newline between function signature and '{' +nl_fdef_brace = add # ignore/add/remove/force + +# Add or remove newline between C++11 lambda signature and '{' +nl_cpp_ldef_brace = ignore # ignore/add/remove/force + +# Add or remove a newline between the return keyword and return expression. +nl_return_expr = ignore # ignore/add/remove/force + +# Whether to put a newline after semicolons, except in 'for' statements +nl_after_semicolon = true # false/true + +# Java: Control the newline between the ')' and '{{' of the double brace initializer. +nl_paren_dbrace_open = ignore # ignore/add/remove/force + +# Whether to put a newline after brace open. +# This also adds a newline before the matching brace close. +nl_after_brace_open = false # false/true + +# If nl_after_brace_open and nl_after_brace_open_cmt are true, a newline is +# placed between the open brace and a trailing single-line comment. +nl_after_brace_open_cmt = false # false/true + +# Whether to put a newline after a virtual brace open with a non-empty body. +# These occur in un-braced if/while/do/for statement bodies. +nl_after_vbrace_open = true # false/true + +# Whether to put a newline after a virtual brace open with an empty body. +# These occur in un-braced if/while/do/for statement bodies. +nl_after_vbrace_open_empty = true # false/true + +# Whether to put a newline after a brace close. +# Does not apply if followed by a necessary ';'. +nl_after_brace_close = false # false/true + +# Whether to put a newline after a virtual brace close. +# Would add a newline before return in: 'if (foo) a++; return;' +nl_after_vbrace_close = true # false/true + +# Control the newline between the close brace and 'b' in: 'struct { int a; } b;' +# Affects enums, unions, and structures. If set to ignore, uses nl_after_brace_close +nl_brace_struct_var = ignore # ignore/add/remove/force + +# Whether to alter newlines in '#define' macros +nl_define_macro = false # false/true + +# Whether to not put blanks after '#ifxx', '#elxx', or before '#endif' +nl_squeeze_ifdef = false # false/true + +# Add or remove blank line before 'if' +nl_before_if = ignore # ignore/add/remove/force + +# Add or remove blank line after 'if' statement +nl_after_if = ignore # ignore/add/remove/force + +# Add or remove blank line before 'for' +nl_before_for = ignore # ignore/add/remove/force + +# Add or remove blank line after 'for' statement +nl_after_for = ignore # ignore/add/remove/force + +# Add or remove blank line before 'while' +nl_before_while = ignore # ignore/add/remove/force + +# Add or remove blank line after 'while' statement +nl_after_while = ignore # ignore/add/remove/force + +# Add or remove blank line before 'switch' +nl_before_switch = ignore # ignore/add/remove/force + +# Add or remove blank line after 'switch' statement +nl_after_switch = ignore # ignore/add/remove/force + +# Add or remove blank line before 'do' +nl_before_do = ignore # ignore/add/remove/force + +# Add or remove blank line after 'do/while' statement +nl_after_do = ignore # ignore/add/remove/force + +# Whether to double-space commented-entries in struct/enum +nl_ds_struct_enum_cmt = false # false/true + +# Whether to double-space before the close brace of a struct/union/enum +# (lower priority than 'eat_blanks_before_close_brace') +nl_ds_struct_enum_close_brace = false # false/true + +# Add or remove a newline around a class colon. +# Related to pos_class_colon, nl_class_init_args, and pos_class_comma. +nl_class_colon = ignore # ignore/add/remove/force + +# Add or remove a newline around a class constructor colon. +# Related to pos_constr_colon, nl_constr_init_args, and pos_constr_comma. +nl_constr_colon = ignore # ignore/add/remove/force + +# Change simple unbraced if statements into a one-liner +# 'if(b)\n i++;' => 'if(b) i++;' +nl_create_if_one_liner = false # false/true + +# Change simple unbraced for statements into a one-liner +# 'for (i=0;i<5;i++)\n foo(i);' => 'for (i=0;i<5;i++) foo(i);' +nl_create_for_one_liner = false # false/true + +# Change simple unbraced while statements into a one-liner +# 'while (i<5)\n foo(i++);' => 'while (i<5) foo(i++);' +nl_create_while_one_liner = false # false/true + +# +# Positioning options +# + +# The position of arithmetic operators in wrapped expressions +pos_arith = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of assignment in wrapped expressions. +# Do not affect '=' followed by '{' +pos_assign = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of boolean operators in wrapped expressions +pos_bool = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of comparison operators in wrapped expressions +pos_compare = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of conditional (b ? t : f) operators in wrapped expressions +pos_conditional = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of the comma in wrapped expressions +pos_comma = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of the comma in the class base list +pos_class_comma = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of the comma in the constructor initialization list +pos_constr_comma = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of colons between class and base class list +pos_class_colon = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of colons between constructor and member initialization +pos_constr_colon = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# +# Line Splitting options +# + +# Try to limit code width to N number of columns +code_width = 0 # number + +# Whether to fully split long 'for' statements at semi-colons +ls_for_split_full = false # false/true + +# Whether to fully split long function protos/calls at commas +ls_func_split_full = false # false/true + +# Whether to split lines as close to code_width as possible and ignore some groupings +ls_code_width = false # false/true + +# +# Blank line options +# + +# The maximum consecutive newlines +nl_max = 0 # number + +# The number of newlines after a function prototype, if followed by another function prototype +nl_after_func_proto = 0 # number + +# The number of newlines after a function prototype, if not followed by another function prototype +nl_after_func_proto_group = 0 # number + +# The number of newlines after '}' of a multi-line function body +nl_after_func_body = 0 # number + +# The number of newlines after '}' of a multi-line function body in a class declaration +nl_after_func_body_class = 0 # number + +# The number of newlines after '}' of a single line function body +nl_after_func_body_one_liner = 0 # number + +# The minimum number of newlines before a multi-line comment. +# Doesn't apply if after a brace open or another multi-line comment. +nl_before_block_comment = 0 # number + +# The minimum number of newlines before a single-line C comment. +# Doesn't apply if after a brace open or other single-line C comments. +nl_before_c_comment = 0 # number + +# The minimum number of newlines before a CPP comment. +# Doesn't apply if after a brace open or other CPP comments. +nl_before_cpp_comment = 0 # number + +# Whether to force a newline after a multi-line comment. +nl_after_multiline_comment = false # false/true + +# The number of newlines after '}' or ';' of a struct/enum/union definition +nl_after_struct = 0 # number + +# The number of newlines after '}' or ';' of a class definition +nl_after_class = 0 # number + +# The number of newlines before a 'private:', 'public:', 'protected:', 'signals:', or 'slots:' label. +# Will not change the newline count if after a brace open. +# 0 = No change. +nl_before_access_spec = 0 # number + +# The number of newlines after a 'private:', 'public:', 'protected:', 'signals:', or 'slots:' label. +# 0 = No change. +nl_after_access_spec = 0 # number + +# The number of newlines between a function def and the function comment. +# 0 = No change. +nl_comment_func_def = 0 # number + +# The number of newlines after a try-catch-finally block that isn't followed by a brace close. +# 0 = No change. +nl_after_try_catch_finally = 0 # number + +# The number of newlines before and after a property, indexer or event decl. +# 0 = No change. +nl_around_cs_property = 0 # number + +# The number of newlines between the get/set/add/remove handlers in C#. +# 0 = No change. +nl_between_get_set = 0 # number + +# Add or remove newline between C# property and the '{' +nl_property_brace = ignore # ignore/add/remove/force + +# Whether to remove blank lines after '{' +eat_blanks_after_open_brace = false # false/true + +# Whether to remove blank lines before '}' +eat_blanks_before_close_brace = false # false/true + +# How aggressively to remove extra newlines not in preproc. +# 0: No change +# 1: Remove most newlines not handled by other config +# 2: Remove all newlines and reformat completely by config +nl_remove_extra_newlines = 0 # number + +# Whether to put a blank line before 'return' statements, unless after an open brace. +nl_before_return = false # false/true + +# Whether to put a blank line after 'return' statements, unless followed by a close brace. +nl_after_return = false # false/true + +# Whether to put a newline after a Java annotation statement. +# Only affects annotations that are after a newline. +nl_after_annotation = ignore # ignore/add/remove/force + +# Controls the newline between two annotations. +nl_between_annotation = ignore # ignore/add/remove/force + +# +# Code modifying options (non-whitespace) +# + +# Add or remove braces on single-line 'do' statement +mod_full_brace_do = add # ignore/add/remove/force + +# Add or remove braces on single-line 'for' statement +mod_full_brace_for = add # ignore/add/remove/force + +# Add or remove braces on single-line function definitions. (Pawn) +mod_full_brace_function = add # ignore/add/remove/force + +# Add or remove braces on single-line 'if' statement. Will not remove the braces if they contain an 'else'. +mod_full_brace_if = add # ignore/add/remove/force + +# Make all if/elseif/else statements in a chain be braced or not. Overrides mod_full_brace_if. +# If any must be braced, they are all braced. If all can be unbraced, then the braces are removed. +mod_full_brace_if_chain = false # false/true + +# Don't remove braces around statements that span N newlines +mod_full_brace_nl = 0 # number + +# Add or remove braces on single-line 'while' statement +mod_full_brace_while = add # ignore/add/remove/force + +# Add or remove braces on single-line 'using ()' statement +mod_full_brace_using = ignore # ignore/add/remove/force + +# Add or remove unnecessary paren on 'return' statement +mod_paren_on_return = ignore # ignore/add/remove/force + +# Whether to change optional semicolons to real semicolons +mod_pawn_semicolon = false # false/true + +# Add parens on 'while' and 'if' statement around bools +mod_full_paren_if_bool = true # false/true + +# Whether to remove superfluous semicolons +mod_remove_extra_semicolon = false # false/true + +# If a function body exceeds the specified number of newlines and doesn't have a comment after +# the close brace, a comment will be added. +mod_add_long_function_closebrace_comment = 0 # number + +# If a namespace body exceeds the specified number of newlines and doesn't have a comment after +# the close brace, a comment will be added. +mod_add_long_namespace_closebrace_comment = 0 # number + +# If a switch body exceeds the specified number of newlines and doesn't have a comment after +# the close brace, a comment will be added. +mod_add_long_switch_closebrace_comment = 0 # number + +# If an #ifdef body exceeds the specified number of newlines and doesn't have a comment after +# the #endif, a comment will be added. +mod_add_long_ifdef_endif_comment = 0 # number + +# If an #ifdef or #else body exceeds the specified number of newlines and doesn't have a comment after +# the #else, a comment will be added. +mod_add_long_ifdef_else_comment = 0 # number + +# If TRUE, will sort consecutive single-line 'import' statements [Java, D] +mod_sort_import = false # false/true + +# If TRUE, will sort consecutive single-line 'using' statements [C#] +mod_sort_using = false # false/true + +# If TRUE, will sort consecutive single-line '#include' statements [C/C++] and '#import' statements [Obj-C] +# This is generally a bad idea, as it may break your code. +mod_sort_include = false # false/true + +# If TRUE, it will move a 'break' that appears after a fully braced 'case' before the close brace. +mod_move_case_break = true # false/true + +# Will add or remove the braces around a fully braced case statement. +# Will only remove the braces if there are no variable declarations in the block. +mod_case_brace = ignore # ignore/add/remove/force + +# If TRUE, it will remove a void 'return;' that appears as the last statement in a function. +mod_remove_empty_return = false # false/true + +# +# Comment modifications +# + +# Try to wrap comments at cmt_width columns +cmt_width = 0 # number + +# Set the comment reflow mode (default: 0) +# 0: no reflowing (apart from the line wrapping due to cmt_width) +# 1: no touching at all +# 2: full reflow +cmt_reflow_mode = 0 # number + +# Whether to convert all tabs to spaces in comments. Default is to leave tabs inside comments alone, unless used for indenting. +cmt_convert_tab_to_spaces = false # false/true + +# If false, disable all multi-line comment changes, including cmt_width. keyword substitution, and leading chars. +# Default is true. +cmt_indent_multi = true # false/true + +# Whether to group c-comments that look like they are in a block +cmt_c_group = false # false/true + +# Whether to put an empty '/*' on the first line of the combined c-comment +cmt_c_nl_start = false # false/true + +# Whether to put a newline before the closing '*/' of the combined c-comment +cmt_c_nl_end = false # false/true + +# Whether to group cpp-comments that look like they are in a block +cmt_cpp_group = false # false/true + +# Whether to put an empty '/*' on the first line of the combined cpp-comment +cmt_cpp_nl_start = false # false/true + +# Whether to put a newline before the closing '*/' of the combined cpp-comment +cmt_cpp_nl_end = false # false/true + +# Whether to change cpp-comments into c-comments +cmt_cpp_to_c = false # false/true + +# Whether to put a star on subsequent comment lines +cmt_star_cont = true # false/true + +# The number of spaces to insert at the start of subsequent comment lines +cmt_sp_before_star_cont = 0 # number + +# The number of spaces to insert after the star on subsequent comment lines +cmt_sp_after_star_cont = 0 # number + +# For multi-line comments with a '*' lead, remove leading spaces if the first and last lines of +# the comment are the same length. Default=True +cmt_multi_check_last = true # false/true + +# The filename that contains text to insert at the head of a file if the file doesn't start with a C/C++ comment. +# Will substitute $(filename) with the current file's name. +cmt_insert_file_header = "" # string + +# The filename that contains text to insert at the end of a file if the file doesn't end with a C/C++ comment. +# Will substitute $(filename) with the current file's name. +cmt_insert_file_footer = "" # string + +# The filename that contains text to insert before a function implementation if the function isn't preceded with a C/C++ comment. +# Will substitute $(function) with the function name and $(javaparam) with the javadoc @param and @return stuff. +# Will also substitute $(fclass) with the class name: void CFoo::Bar() { ... } +cmt_insert_func_header = "" # string + +# The filename that contains text to insert before a class if the class isn't preceded with a C/C++ comment. +# Will substitute $(class) with the class name. +cmt_insert_class_header = "" # string + +# The filename that contains text to insert before a Obj-C message specification if the method isn't preceded with a C/C++ comment. +# Will substitute $(message) with the function name and $(javaparam) with the javadoc @param and @return stuff. +cmt_insert_oc_msg_header = "" # string + +# If a preprocessor is encountered when stepping backwards from a function name, then +# this option decides whether the comment should be inserted. +# Affects cmt_insert_oc_msg_header, cmt_insert_func_header and cmt_insert_class_header. +cmt_insert_before_preproc = false # false/true + +# +# Preprocessor options +# + +# Control indent of preprocessors inside #if blocks at brace level 0 (file-level) +pp_indent = ignore # ignore/add/remove/force + +# Whether to indent #if/#else/#endif at the brace level (true) or from column 1 (false) +pp_indent_at_level = false # false/true + +# Specifies the number of columns to indent preprocessors per level at brace level 0 (file-level). +# If pp_indent_at_level=false, specifies the number of columns to indent preprocessors per level at brace level > 0 (function-level). +# Default=1. +pp_indent_count = 1 # number + +# Add or remove space after # based on pp_level of #if blocks +pp_space = ignore # ignore/add/remove/force + +# Sets the number of spaces added with pp_space +pp_space_count = 0 # number + +# The indent for #region and #endregion in C# and '#pragma region' in C/C++ +pp_indent_region = 0 # number + +# Whether to indent the code between #region and #endregion +pp_region_indent_code = false # false/true + +# If pp_indent_at_level=true, sets the indent for #if, #else, and #endif when not at file-level. +# 0: indent preprocessors using output_tab_size. +# >0: column at which all preprocessors will be indented. +pp_indent_if = 0 # number + +# Control whether to indent the code between #if, #else and #endif. +pp_if_indent_code = false # false/true + +# Whether to indent '#define' at the brace level (true) or from column 1 (false) +pp_define_at_level = false # false/true + +# You can force a token to be a type with the 'type' option. +# Example: +# type myfoo1 myfoo2 +# +# You can create custom macro-based indentation using macro-open, +# macro-else and macro-close. +# Example: +# macro-open BEGIN_TEMPLATE_MESSAGE_MAP +# macro-open BEGIN_MESSAGE_MAP +# macro-close END_MESSAGE_MAP +# +# You can assign any keyword to any type with the set option. +# set func_call_user _ N_ +# +# The full syntax description of all custom definition config entries +# is shown below: +# +# define custom tokens as: +# - embed whitespace in token using '' escape character, or +# put token in quotes +# - these: ' " and ` are recognized as quote delimiters +# +# type token1 token2 token3 ... +# ^ optionally specify multiple tokens on a single line +# define def_token output_token +# ^ output_token is optional, then NULL is assumed +# macro-open token +# macro-close token +# macro-else token +# set id token1 token2 ... +# ^ optionally specify multiple tokens on a single line +# ^ id is one of the names in token_enum.h sans the CT_ prefix, +# e.g. PP_PRAGMA +# +# all tokens are separated by any mix of ',' commas, '=' equal signs +# and whitespace (space, tab) +# +# You can add support for other file extensions using the 'file_ext' command. +# The first arg is the language name used with the '-l' option. +# The remaining args are file extensions, matched with 'endswith'. +# file_ext CPP .ch .cxx .cpp.in +# diff --git a/local_pod_repo/toxcore/.gitignore b/local_pod_repo/toxcore/.gitignore new file mode 100644 index 0000000..d2dcf7f --- /dev/null +++ b/local_pod_repo/toxcore/.gitignore @@ -0,0 +1,32 @@ +# OS X +.DS_Store + +# Xcode +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +profile +*.moved-aside +DerivedData +*.hmap +*.ipa + +# Bundler +.bundle + +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control +# +# Note: if you ignore the Pods directory, make sure to uncomment +# `pod install` in .travis.yml +# +# Pods/ diff --git a/local_pod_repo/toxcore/0002_zoff_tc___capabilites.diff b/local_pod_repo/toxcore/0002_zoff_tc___capabilites.diff new file mode 100644 index 0000000..05bcd64 --- /dev/null +++ b/local_pod_repo/toxcore/0002_zoff_tc___capabilites.diff @@ -0,0 +1,187 @@ +commit f9a7099bcf2a37095384bbf9418afce815d67138 +Author: zoff99 +Date: Sat Apr 16 10:21:57 2022 +0200 + + capabilites + +diff --git a/toxcore/Messenger.c b/toxcore/Messenger.c +index 715b23fd9..d7388d54d 100644 +--- a/toxcore/Messenger.c ++++ b/toxcore/Messenger.c +@@ -113,6 +113,15 @@ static bool send_online_packet(Messenger *m, int32_t friendnumber) + return false; + } + ++ uint8_t buf[TOX_CAPABILITIES_SIZE + 1]; ++ buf[0] = PACKET_ID_ONLINE; ++ net_pack_u64(buf + 1, TOX_CAPABILITIES_CURRENT); ++ ++ if (write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, ++ m->friendlist[friendnumber].friendcon_id), buf, (TOX_CAPABILITIES_SIZE + 1), false) == -1) { ++ return false; ++ } ++ + uint8_t packet = PACKET_ID_ONLINE; + return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), &packet, sizeof(packet), false) != -1; +@@ -166,6 +175,7 @@ static int32_t init_new_friend(Messenger *m, const uint8_t *real_pk, uint8_t sta + m->friendlist[i].userstatus = USERSTATUS_NONE; + m->friendlist[i].is_typing = false; + m->friendlist[i].message_id = 0; ++ m->friendlist[i].toxcore_capabilities = TOX_CAPABILITY_BASIC; + friend_connection_callbacks(m->fr_c, friendcon_id, MESSENGER_CALLBACK_INDEX, &m_handle_status, &m_handle_packet, + &m_handle_lossy_packet, m, i); + +@@ -1862,6 +1872,19 @@ static int m_handle_status(void *object, int i, bool status, void *userdata) + return 0; + } + ++/* get capabilities of friend's toxcore ++ * return TOX_CAPABILITY_BASIC on any error ++ */ ++uint64_t m_get_friend_toxcore_capabilities(const Messenger *m, int32_t friendnumber) ++{ ++ if (!m_friend_exists(m, friendnumber)) { ++ return TOX_CAPABILITY_BASIC; ++ } ++ ++ // return toxcore_capabilities for friend, not matter if ONLINE or OFFLINE ++ return m->friendlist[friendnumber].toxcore_capabilities; ++} ++ + static int m_handle_packet(void *object, int i, const uint8_t *temp, uint16_t len, void *userdata) + { + if (len == 0) { +@@ -1874,9 +1897,20 @@ static int m_handle_packet(void *object, int i, const uint8_t *temp, uint16_t le + const uint16_t data_length = len - 1; + + if (m->friendlist[i].status != FRIEND_ONLINE) { +- if (packet_id == PACKET_ID_ONLINE && len == 1) { +- set_friend_status(m, i, FRIEND_ONLINE, userdata); +- send_online_packet(m, i); ++ if (packet_id == PACKET_ID_ONLINE) { ++ if (len == (TOX_CAPABILITIES_SIZE + 1)) { ++ uint64_t received_caps; ++ net_unpack_u64(data, &received_caps); ++ m->friendlist[i].toxcore_capabilities = received_caps; ++ LOGGER_DEBUG(m->log, "got capabilties: %llu friendnum: %d", ++ (long long unsigned int)m->friendlist[i].toxcore_capabilities, (int)i); ++ } else if (len == 1) { ++ set_friend_status(m, i, FRIEND_ONLINE, userdata); ++ send_online_packet(m, i); ++ LOGGER_DEBUG(m->log, "got online packet for friendnum: %d", (int)i); ++ } else { ++ return -1; ++ } + } else { + return -1; + } +diff --git a/toxcore/Messenger.h b/toxcore/Messenger.h +index 3bb818a32..dce79eed2 100644 +--- a/toxcore/Messenger.h ++++ b/toxcore/Messenger.h +@@ -72,6 +72,30 @@ typedef struct Messenger_Options { + uint8_t state_plugins_length; + } Messenger_Options; + ++/* this means no special capabilities, in other words clients that are older ++ * and did not implement this feature yet ++ */ ++#define TOX_CAPABILITY_BASIC 0 ++/* ATTENTION: if you are adding new flags in your fork or toxcore, ++ * or in c-toxcore master, ++ * please coordinate with us first! ++ * thank you, the Tox Devs. ++ */ ++#define TOX_CAPABILITY_CAPABILITIES ((uint64_t)1) << 0 ++#define TOX_CAPABILITY_MSGV2 ((uint64_t)1) << 1 ++#define TOX_CAPABILITY_TOXAV_H264 ((uint64_t)1) << 2 ++#define TOX_CAPABILITY_MSGV3 ((uint64_t)1) << 3 ++/* add new flags/bits here */ ++/* if the TOX_CAPABILITY_NEXT_IMPLEMENTATION flag is set it means ++ * we are using a different system for indicating capabilities now, ++ * and TOX_CAPABILITIES_* should be ignored and just the new (not yet known) ++ * system should be used ++ */ ++#define TOX_CAPABILITY_NEXT_IMPLEMENTATION ((uint64_t)1) << 63 ++/* hardcoded capabilities of this version/branch of toxcore */ ++#define TOX_CAPABILITIES_CURRENT (uint64_t)(TOX_CAPABILITY_CAPABILITIES | TOX_CAPABILITY_MSGV3) ++/* size of the FLAGS in bytes */ ++#define TOX_CAPABILITIES_SIZE sizeof(uint64_t) + + struct Receipts { + uint32_t packet_num; +@@ -226,6 +254,7 @@ typedef struct Friend { + + struct Receipts *receipts_start; + struct Receipts *receipts_end; ++ uint64_t toxcore_capabilities; + } Friend; + + struct Messenger { +@@ -494,6 +523,10 @@ non_null() int m_copy_self_statusmessage(const Messenger *m, uint8_t *buf); + non_null() uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber); + non_null() uint8_t m_get_self_userstatus(const Messenger *m); + ++/* get capabilities of friend's toxcore ++ * return TOX_CAPABILITY_BASIC on any error ++ */ ++uint64_t m_get_friend_toxcore_capabilities(const Messenger *m, int32_t friendnumber); + + /** @brief returns timestamp of last time friendnumber was seen online or 0 if never seen. + * if friendnumber is invalid this function will return UINT64_MAX. +diff --git a/toxcore/tox.c b/toxcore/tox.c +index cf350c59e..845bee680 100644 +--- a/toxcore/tox.c ++++ b/toxcore/tox.c +@@ -1299,6 +1299,20 @@ uint64_t tox_friend_get_last_online(const Tox *tox, uint32_t friend_number, Tox_ + return timestamp; + } + ++uint64_t tox_friend_get_capabilities(const Tox *tox, uint32_t friend_number) ++{ ++ tox_lock(tox); ++ const uint64_t capabilities = m_get_friend_toxcore_capabilities(tox->m, friend_number); ++ tox_unlock(tox); ++ ++ return capabilities; ++} ++ ++uint64_t tox_self_get_capabilities(void) ++{ ++ return (TOX_CAPABILITIES_CURRENT); ++} ++ + size_t tox_self_get_friend_list_size(const Tox *tox) + { + assert(tox != nullptr); +diff --git a/toxcore/tox.h b/toxcore/tox.h +index 56f9daf05..2ce668c42 100644 +--- a/toxcore/tox.h ++++ b/toxcore/tox.h +@@ -1136,9 +1136,12 @@ void tox_self_get_public_key(const Tox *tox, uint8_t *public_key); + */ + void tox_self_get_secret_key(const Tox *tox, uint8_t *secret_key); + ++/** ++ * Return the capabilities flags for this tox instance. ++ */ ++uint64_t tox_self_get_capabilities(void); + /** @} */ + +- + /** @{ + * @name User-visible client information (nickname/status) + */ +@@ -1525,6 +1528,11 @@ typedef enum Tox_Err_Friend_Query { + + } Tox_Err_Friend_Query; + ++/** ++ * Return the capabilities flags for a friend. If the friend number is invalid, the ++ * return value is unspecified. ++ */ ++uint64_t tox_friend_get_capabilities(const Tox *tox, uint32_t friend_number); + + /** + * @brief Return the length of the friend's name. diff --git a/local_pod_repo/toxcore/LICENSE b/local_pod_repo/toxcore/LICENSE new file mode 100644 index 0000000..c0d352b --- /dev/null +++ b/local_pod_repo/toxcore/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/local_pod_repo/toxcore/Podfile b/local_pod_repo/toxcore/Podfile new file mode 100644 index 0000000..3d7bf01 --- /dev/null +++ b/local_pod_repo/toxcore/Podfile @@ -0,0 +1,8 @@ +source 'https://github.com/CocoaPods/Specs.git' + +# ignore all warnings from all pods +inhibit_all_warnings! + +def common_pods + pod "msgpack-c", :path => '../msgpack-c' +end diff --git a/local_pod_repo/toxcore/README.md b/local_pod_repo/toxcore/README.md new file mode 100644 index 0000000..d9435bc --- /dev/null +++ b/local_pod_repo/toxcore/README.md @@ -0,0 +1,12 @@ +# toxcore + +CocoaPods wrapper for [c-toxcore](https://github.com/TokTok/c-toxcore). + +#### Note + +Pod has same version number as toxcore. + +## Authors + +Dmytro Vorobiov, d@dvor.me
+https://github.com/Zoxcore diff --git a/local_pod_repo/toxcore/build-vpx.sh b/local_pod_repo/toxcore/build-vpx.sh new file mode 100755 index 0000000..918b77a --- /dev/null +++ b/local_pod_repo/toxcore/build-vpx.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +TEMP_DIR="temp" +LIBVPX_DIR="libvpx" +VPX_FRAMEWORK="vpx.framework" + +# $1 - framework directory +remove_old_framework() { + if [ -d "$1" ]; then + echo "Removing $1" + rm -rf "$1" + fi +} + +clone_libvpx() { + echo "Cloning libvpx" + mkdir $TEMP_DIR + cd $TEMP_DIR + git clone --branch v1.4.0 --depth 1 https://github.com/webmproject/libvpx.git $LIBVPX_DIR +} + +# $1 - patch file name +patch_vpx() { + echo "Patching libvpx" + cd $LIBVPX_DIR + git apply ../../$1 + cd ../.. +} + +build_vpx() { + echo "Building vpx framework" + $TEMP_DIR/$LIBVPX_DIR/build/make/iosbuild.sh --show-build-output --verbose + + echo "Moving headers from $VPX_FRAMEWORK/Headers/vpx/ to $VPX_FRAMEWORK/Headers/" + mv $VPX_FRAMEWORK/Headers/vpx/* $VPX_FRAMEWORK/Headers/ + rm -rf $VPX_FRAMEWORK/Headers/vpx +} + +# $2 - directory to move framework to +move_vpx() { + echo "Moving framework from $VPX_FRAMEWORK to $1/$VPX_FRAMEWORK" + mkdir $1 + mv $VPX_FRAMEWORK $1/$VPX_FRAMEWORK +} + +cleanup() { + echo "Removing $TEMP_DIR directory" + rm -rf $TEMP_DIR +} + +# $1 - patch file name +# $2 - directory install framework to +do_all() { + echo "Building for $2" + + remove_old_framework $2 + + clone_libvpx + patch_vpx $1 + build_vpx + move_vpx $2 + + cleanup +} + +do_all vpx-osx.diff osx +do_all vpx-ios.diff ios + +echo "Done" diff --git a/local_pod_repo/toxcore/install-tox.sh b/local_pod_repo/toxcore/install-tox.sh new file mode 100755 index 0000000..b06c7cf --- /dev/null +++ b/local_pod_repo/toxcore/install-tox.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +_HOME2_=$(dirname $0) +export _HOME2_ +_HOME_=$(cd $_HOME2_;pwd) +export _HOME_ + +echo $_HOME_ +cd $_HOME_ + +GIT_PATH="toxcore-git" +OUTPUT="toxcore" + +DIRS=( + "toxcore" + "toxav" + "toxdns" + "toxencryptsave" +) + +echo "Removing old toxcore directory" +rm -rf $OUTPUT +mkdir $OUTPUT + +rm -Rf $GIT_PATH/ +git clone https://github.com/TokTok/c-toxcore $GIT_PATH/ + +cd $GIT_PATH/ +git checkout "v0.2.18" + +echo "Applying msgv3_addon.patch" +git apply --reject --whitespace=fix ../msgv3_addon.patch + +echo "Applying 0002_zoff_tc___capabilites.diff" +git apply --reject --whitespace=fix ../0002_zoff_tc___capabilites.diff + +cd .. + +for dir in ${DIRS[@]}; do + echo "Copying files from $GIT_PATH/$dir to $OUTPUT/$dir" + cp -rv $GIT_PATH/$dir $OUTPUT +done + +cd $GIT_PATH/ +echo "cleanup" +git checkout toxcore/* +cd .. + +echo "Changing all .c files to .m files (making Xcode happy)" +for file in toxcore/**/*.c; do + mv -v "$file" "${file%.c}.m" +done + +for file in toxcore/toxcore/events/*.c; do + mv -v "$file" "${file%.c}.m" +done + +remove_files_matching() { + for file in $1; do + echo "Removing $file" + rm $file + done +} + +remove_files_matching "toxcore/**/*.bazel" +remove_files_matching "toxcore/**/*_test.cpp" +remove_files_matching "toxcore/**/*_test.cc" +remove_files_matching "toxcore/**/*.api.h" + +echo "patching toxcore includes ..." +cd toxcore/ +grep -rl '#include ' | grep -v 'install-tox.sh' | xargs -L1 sed -i -e 's_#include _#include "sodium.h"_' +grep -rl '#include ' | grep -v 'install-tox.sh' | xargs -L1 sed -i -e 's_#include _#include "opus.h"_' +grep -rl '#include "../third_party/cmp/cmp.h"' | grep -v 'install-tox.sh' | xargs -L1 sed -i -e 'sx#include "../third_party/cmp/cmp.h"x#include "cmp.h"x' +cd .. diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/arm64-darwin-gcc/vpx_config.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/arm64-darwin-gcc/vpx_config.h new file mode 100644 index 0000000..5aff874 --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/arm64-darwin-gcc/vpx_config.h @@ -0,0 +1,102 @@ +/* Copyright (c) 2011 The WebM project authors. All Rights Reserved. */ +/* */ +/* Use of this source code is governed by a BSD-style license */ +/* that can be found in the LICENSE file in the root of the source */ +/* tree. An additional intellectual property rights grant can be found */ +/* in the file PATENTS. All contributing project authors may */ +/* be found in the AUTHORS file in the root of the source tree. */ +/* This file automatically generated by configure. Do not edit! */ +#ifndef VPX_CONFIG_H +#define VPX_CONFIG_H +#define RESTRICT +#define INLINE inline +#define ARCH_ARM 1 +#define ARCH_MIPS 0 +#define ARCH_X86 0 +#define ARCH_X86_64 0 +#define ARCH_PPC32 0 +#define ARCH_PPC64 0 +#define HAVE_EDSP 0 +#define HAVE_MEDIA 0 +#define HAVE_NEON 1 +#define HAVE_NEON_ASM 0 +#define HAVE_MIPS32 0 +#define HAVE_DSPR2 0 +#define HAVE_MIPS64 0 +#define HAVE_MMX 0 +#define HAVE_SSE 0 +#define HAVE_SSE2 0 +#define HAVE_SSE3 0 +#define HAVE_SSSE3 0 +#define HAVE_SSE4_1 0 +#define HAVE_AVX 0 +#define HAVE_AVX2 0 +#define HAVE_ALTIVEC 0 +#define HAVE_VPX_PORTS 1 +#define HAVE_STDINT_H 1 +#define HAVE_ALT_TREE_LAYOUT 0 +#define HAVE_PTHREAD_H 1 +#define HAVE_SYS_MMAN_H 1 +#define HAVE_UNISTD_H 1 +#define CONFIG_DEPENDENCY_TRACKING 1 +#define CONFIG_EXTERNAL_BUILD 0 +#define CONFIG_INSTALL_DOCS 1 +#define CONFIG_INSTALL_BINS 1 +#define CONFIG_INSTALL_LIBS 1 +#define CONFIG_INSTALL_SRCS 0 +#define CONFIG_USE_X86INC 0 +#define CONFIG_DEBUG 0 +#define CONFIG_GPROF 0 +#define CONFIG_GCOV 0 +#define CONFIG_RVCT 0 +#define CONFIG_GCC 1 +#define CONFIG_MSVS 0 +#define CONFIG_PIC 0 +#define CONFIG_BIG_ENDIAN 0 +#define CONFIG_CODEC_SRCS 0 +#define CONFIG_DEBUG_LIBS 0 +#define CONFIG_FAST_UNALIGNED 1 +#define CONFIG_MEM_MANAGER 0 +#define CONFIG_MEM_TRACKER 0 +#define CONFIG_MEM_CHECKS 0 +#define CONFIG_DEQUANT_TOKENS 0 +#define CONFIG_DC_RECON 0 +#define CONFIG_RUNTIME_CPU_DETECT 0 +#define CONFIG_POSTPROC 0 +#define CONFIG_VP9_POSTPROC 0 +#define CONFIG_MULTITHREAD 1 +#define CONFIG_INTERNAL_STATS 0 +#define CONFIG_VP8_ENCODER 1 +#define CONFIG_VP8_DECODER 1 +#define CONFIG_VP9_ENCODER 1 +#define CONFIG_VP9_DECODER 1 +#define CONFIG_VP8 1 +#define CONFIG_VP9 1 +#define CONFIG_ENCODERS 1 +#define CONFIG_DECODERS 1 +#define CONFIG_STATIC_MSVCRT 0 +#define CONFIG_SPATIAL_RESAMPLING 1 +#define CONFIG_REALTIME_ONLY 0 +#define CONFIG_ONTHEFLY_BITPACKING 0 +#define CONFIG_ERROR_CONCEALMENT 0 +#define CONFIG_SHARED 0 +#define CONFIG_STATIC 1 +#define CONFIG_SMALL 0 +#define CONFIG_POSTPROC_VISUALIZER 0 +#define CONFIG_OS_SUPPORT 1 +#define CONFIG_UNIT_TESTS 0 +#define CONFIG_WEBM_IO 0 +#define CONFIG_LIBYUV 0 +#define CONFIG_DECODE_PERF_TESTS 0 +#define CONFIG_ENCODE_PERF_TESTS 0 +#define CONFIG_MULTI_RES_ENCODING 0 +#define CONFIG_TEMPORAL_DENOISING 1 +#define CONFIG_VP9_TEMPORAL_DENOISING 0 +#define CONFIG_COEFFICIENT_RANGE_CHECKING 0 +#define CONFIG_VP9_HIGHBITDEPTH 0 +#define CONFIG_EXPERIMENTAL 0 +#define CONFIG_SIZE_LIMIT 0 +#define CONFIG_SPATIAL_SVC 0 +#define CONFIG_FP_MB_STATS 0 +#define CONFIG_EMULATE_HARDWARE 0 +#endif /* VPX_CONFIG_H */ diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/armv7-darwin-gcc/vpx_config.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/armv7-darwin-gcc/vpx_config.h new file mode 100644 index 0000000..48a6c80 --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/armv7-darwin-gcc/vpx_config.h @@ -0,0 +1,102 @@ +/* Copyright (c) 2011 The WebM project authors. All Rights Reserved. */ +/* */ +/* Use of this source code is governed by a BSD-style license */ +/* that can be found in the LICENSE file in the root of the source */ +/* tree. An additional intellectual property rights grant can be found */ +/* in the file PATENTS. All contributing project authors may */ +/* be found in the AUTHORS file in the root of the source tree. */ +/* This file automatically generated by configure. Do not edit! */ +#ifndef VPX_CONFIG_H +#define VPX_CONFIG_H +#define RESTRICT +#define INLINE inline +#define ARCH_ARM 1 +#define ARCH_MIPS 0 +#define ARCH_X86 0 +#define ARCH_X86_64 0 +#define ARCH_PPC32 0 +#define ARCH_PPC64 0 +#define HAVE_EDSP 0 +#define HAVE_MEDIA 1 +#define HAVE_NEON 1 +#define HAVE_NEON_ASM 1 +#define HAVE_MIPS32 0 +#define HAVE_DSPR2 0 +#define HAVE_MIPS64 0 +#define HAVE_MMX 0 +#define HAVE_SSE 0 +#define HAVE_SSE2 0 +#define HAVE_SSE3 0 +#define HAVE_SSSE3 0 +#define HAVE_SSE4_1 0 +#define HAVE_AVX 0 +#define HAVE_AVX2 0 +#define HAVE_ALTIVEC 0 +#define HAVE_VPX_PORTS 1 +#define HAVE_STDINT_H 1 +#define HAVE_ALT_TREE_LAYOUT 0 +#define HAVE_PTHREAD_H 1 +#define HAVE_SYS_MMAN_H 1 +#define HAVE_UNISTD_H 1 +#define CONFIG_DEPENDENCY_TRACKING 1 +#define CONFIG_EXTERNAL_BUILD 0 +#define CONFIG_INSTALL_DOCS 1 +#define CONFIG_INSTALL_BINS 1 +#define CONFIG_INSTALL_LIBS 1 +#define CONFIG_INSTALL_SRCS 0 +#define CONFIG_USE_X86INC 0 +#define CONFIG_DEBUG 0 +#define CONFIG_GPROF 0 +#define CONFIG_GCOV 0 +#define CONFIG_RVCT 0 +#define CONFIG_GCC 1 +#define CONFIG_MSVS 0 +#define CONFIG_PIC 0 +#define CONFIG_BIG_ENDIAN 0 +#define CONFIG_CODEC_SRCS 0 +#define CONFIG_DEBUG_LIBS 0 +#define CONFIG_FAST_UNALIGNED 1 +#define CONFIG_MEM_MANAGER 0 +#define CONFIG_MEM_TRACKER 0 +#define CONFIG_MEM_CHECKS 0 +#define CONFIG_DEQUANT_TOKENS 0 +#define CONFIG_DC_RECON 0 +#define CONFIG_RUNTIME_CPU_DETECT 0 +#define CONFIG_POSTPROC 0 +#define CONFIG_VP9_POSTPROC 0 +#define CONFIG_MULTITHREAD 1 +#define CONFIG_INTERNAL_STATS 0 +#define CONFIG_VP8_ENCODER 1 +#define CONFIG_VP8_DECODER 1 +#define CONFIG_VP9_ENCODER 1 +#define CONFIG_VP9_DECODER 1 +#define CONFIG_VP8 1 +#define CONFIG_VP9 1 +#define CONFIG_ENCODERS 1 +#define CONFIG_DECODERS 1 +#define CONFIG_STATIC_MSVCRT 0 +#define CONFIG_SPATIAL_RESAMPLING 1 +#define CONFIG_REALTIME_ONLY 0 +#define CONFIG_ONTHEFLY_BITPACKING 0 +#define CONFIG_ERROR_CONCEALMENT 0 +#define CONFIG_SHARED 0 +#define CONFIG_STATIC 1 +#define CONFIG_SMALL 0 +#define CONFIG_POSTPROC_VISUALIZER 0 +#define CONFIG_OS_SUPPORT 1 +#define CONFIG_UNIT_TESTS 0 +#define CONFIG_WEBM_IO 0 +#define CONFIG_LIBYUV 0 +#define CONFIG_DECODE_PERF_TESTS 0 +#define CONFIG_ENCODE_PERF_TESTS 0 +#define CONFIG_MULTI_RES_ENCODING 0 +#define CONFIG_TEMPORAL_DENOISING 1 +#define CONFIG_VP9_TEMPORAL_DENOISING 0 +#define CONFIG_COEFFICIENT_RANGE_CHECKING 0 +#define CONFIG_VP9_HIGHBITDEPTH 0 +#define CONFIG_EXPERIMENTAL 0 +#define CONFIG_SIZE_LIMIT 0 +#define CONFIG_SPATIAL_SVC 0 +#define CONFIG_FP_MB_STATS 0 +#define CONFIG_EMULATE_HARDWARE 0 +#endif /* VPX_CONFIG_H */ diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/armv7s-darwin-gcc/vpx_config.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/armv7s-darwin-gcc/vpx_config.h new file mode 100644 index 0000000..48a6c80 --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/armv7s-darwin-gcc/vpx_config.h @@ -0,0 +1,102 @@ +/* Copyright (c) 2011 The WebM project authors. All Rights Reserved. */ +/* */ +/* Use of this source code is governed by a BSD-style license */ +/* that can be found in the LICENSE file in the root of the source */ +/* tree. An additional intellectual property rights grant can be found */ +/* in the file PATENTS. All contributing project authors may */ +/* be found in the AUTHORS file in the root of the source tree. */ +/* This file automatically generated by configure. Do not edit! */ +#ifndef VPX_CONFIG_H +#define VPX_CONFIG_H +#define RESTRICT +#define INLINE inline +#define ARCH_ARM 1 +#define ARCH_MIPS 0 +#define ARCH_X86 0 +#define ARCH_X86_64 0 +#define ARCH_PPC32 0 +#define ARCH_PPC64 0 +#define HAVE_EDSP 0 +#define HAVE_MEDIA 1 +#define HAVE_NEON 1 +#define HAVE_NEON_ASM 1 +#define HAVE_MIPS32 0 +#define HAVE_DSPR2 0 +#define HAVE_MIPS64 0 +#define HAVE_MMX 0 +#define HAVE_SSE 0 +#define HAVE_SSE2 0 +#define HAVE_SSE3 0 +#define HAVE_SSSE3 0 +#define HAVE_SSE4_1 0 +#define HAVE_AVX 0 +#define HAVE_AVX2 0 +#define HAVE_ALTIVEC 0 +#define HAVE_VPX_PORTS 1 +#define HAVE_STDINT_H 1 +#define HAVE_ALT_TREE_LAYOUT 0 +#define HAVE_PTHREAD_H 1 +#define HAVE_SYS_MMAN_H 1 +#define HAVE_UNISTD_H 1 +#define CONFIG_DEPENDENCY_TRACKING 1 +#define CONFIG_EXTERNAL_BUILD 0 +#define CONFIG_INSTALL_DOCS 1 +#define CONFIG_INSTALL_BINS 1 +#define CONFIG_INSTALL_LIBS 1 +#define CONFIG_INSTALL_SRCS 0 +#define CONFIG_USE_X86INC 0 +#define CONFIG_DEBUG 0 +#define CONFIG_GPROF 0 +#define CONFIG_GCOV 0 +#define CONFIG_RVCT 0 +#define CONFIG_GCC 1 +#define CONFIG_MSVS 0 +#define CONFIG_PIC 0 +#define CONFIG_BIG_ENDIAN 0 +#define CONFIG_CODEC_SRCS 0 +#define CONFIG_DEBUG_LIBS 0 +#define CONFIG_FAST_UNALIGNED 1 +#define CONFIG_MEM_MANAGER 0 +#define CONFIG_MEM_TRACKER 0 +#define CONFIG_MEM_CHECKS 0 +#define CONFIG_DEQUANT_TOKENS 0 +#define CONFIG_DC_RECON 0 +#define CONFIG_RUNTIME_CPU_DETECT 0 +#define CONFIG_POSTPROC 0 +#define CONFIG_VP9_POSTPROC 0 +#define CONFIG_MULTITHREAD 1 +#define CONFIG_INTERNAL_STATS 0 +#define CONFIG_VP8_ENCODER 1 +#define CONFIG_VP8_DECODER 1 +#define CONFIG_VP9_ENCODER 1 +#define CONFIG_VP9_DECODER 1 +#define CONFIG_VP8 1 +#define CONFIG_VP9 1 +#define CONFIG_ENCODERS 1 +#define CONFIG_DECODERS 1 +#define CONFIG_STATIC_MSVCRT 0 +#define CONFIG_SPATIAL_RESAMPLING 1 +#define CONFIG_REALTIME_ONLY 0 +#define CONFIG_ONTHEFLY_BITPACKING 0 +#define CONFIG_ERROR_CONCEALMENT 0 +#define CONFIG_SHARED 0 +#define CONFIG_STATIC 1 +#define CONFIG_SMALL 0 +#define CONFIG_POSTPROC_VISUALIZER 0 +#define CONFIG_OS_SUPPORT 1 +#define CONFIG_UNIT_TESTS 0 +#define CONFIG_WEBM_IO 0 +#define CONFIG_LIBYUV 0 +#define CONFIG_DECODE_PERF_TESTS 0 +#define CONFIG_ENCODE_PERF_TESTS 0 +#define CONFIG_MULTI_RES_ENCODING 0 +#define CONFIG_TEMPORAL_DENOISING 1 +#define CONFIG_VP9_TEMPORAL_DENOISING 0 +#define CONFIG_COEFFICIENT_RANGE_CHECKING 0 +#define CONFIG_VP9_HIGHBITDEPTH 0 +#define CONFIG_EXPERIMENTAL 0 +#define CONFIG_SIZE_LIMIT 0 +#define CONFIG_SPATIAL_SVC 0 +#define CONFIG_FP_MB_STATS 0 +#define CONFIG_EMULATE_HARDWARE 0 +#endif /* VPX_CONFIG_H */ diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/vp8.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vp8.h new file mode 100644 index 0000000..2a31af6 --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vp8.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/*!\defgroup vp8 VP8 + * \ingroup codecs + * VP8 is vpx's newest video compression algorithm that uses motion + * compensated prediction, Discrete Cosine Transform (DCT) coding of the + * prediction error signal and context dependent entropy coding techniques + * based on arithmetic principles. It features: + * - YUV 4:2:0 image format + * - Macro-block based coding (16x16 luma plus two 8x8 chroma) + * - 1/4 (1/8) pixel accuracy motion compensated prediction + * - 4x4 DCT transform + * - 128 level linear quantizer + * - In loop deblocking filter + * - Context-based entropy coding + * + * @{ + */ +/*!\file + * \brief Provides controls common to both the VP8 encoder and decoder. + */ +#ifndef VPX_VP8_H_ +#define VPX_VP8_H_ + +#include "./vpx_codec.h" +#include "./vpx_image.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*!\brief Control functions + * + * The set of macros define the control functions of VP8 interface + */ +enum vp8_com_control_id { + VP8_SET_REFERENCE = 1, /**< pass in an external frame into decoder to be used as reference frame */ + VP8_COPY_REFERENCE = 2, /**< get a copy of reference frame from the decoder */ + VP8_SET_POSTPROC = 3, /**< set the decoder's post processing settings */ + VP8_SET_DBG_COLOR_REF_FRAME = 4, /**< set the reference frames to color for each macroblock */ + VP8_SET_DBG_COLOR_MB_MODES = 5, /**< set which macro block modes to color */ + VP8_SET_DBG_COLOR_B_MODES = 6, /**< set which blocks modes to color */ + VP8_SET_DBG_DISPLAY_MV = 7, /**< set which motion vector modes to draw */ + + /* TODO(jkoleszar): The encoder incorrectly reuses some of these values (5+) + * for its control ids. These should be migrated to something like the + * VP8_DECODER_CTRL_ID_START range next time we're ready to break the ABI. + */ + VP9_GET_REFERENCE = 128, /**< get a pointer to a reference frame */ + VP8_COMMON_CTRL_ID_MAX, + VP8_DECODER_CTRL_ID_START = 256 +}; + +/*!\brief post process flags + * + * The set of macros define VP8 decoder post processing flags + */ +enum vp8_postproc_level { + VP8_NOFILTERING = 0, + VP8_DEBLOCK = 1 << 0, + VP8_DEMACROBLOCK = 1 << 1, + VP8_ADDNOISE = 1 << 2, + VP8_DEBUG_TXT_FRAME_INFO = 1 << 3, /**< print frame information */ + VP8_DEBUG_TXT_MBLK_MODES = 1 << 4, /**< print macro block modes over each macro block */ + VP8_DEBUG_TXT_DC_DIFF = 1 << 5, /**< print dc diff for each macro block */ + VP8_DEBUG_TXT_RATE_INFO = 1 << 6, /**< print video rate info (encoder only) */ + VP8_MFQE = 1 << 10 +}; + +/*!\brief post process flags + * + * This define a structure that describe the post processing settings. For + * the best objective measure (using the PSNR metric) set post_proc_flag + * to VP8_DEBLOCK and deblocking_level to 1. + */ + +typedef struct vp8_postproc_cfg { + int post_proc_flag; /**< the types of post processing to be done, should be combination of "vp8_postproc_level" */ + int deblocking_level; /**< the strength of deblocking, valid range [0, 16] */ + int noise_level; /**< the strength of additive noise, valid range [0, 16] */ +} vp8_postproc_cfg_t; + +/*!\brief reference frame type + * + * The set of macros define the type of VP8 reference frames + */ +typedef enum vpx_ref_frame_type { + VP8_LAST_FRAME = 1, + VP8_GOLD_FRAME = 2, + VP8_ALTR_FRAME = 4 +} vpx_ref_frame_type_t; + +/*!\brief reference frame data struct + * + * Define the data struct to access vp8 reference frames. + */ +typedef struct vpx_ref_frame { + vpx_ref_frame_type_t frame_type; /**< which reference frame */ + vpx_image_t img; /**< reference frame data in image format */ +} vpx_ref_frame_t; + +/*!\brief VP9 specific reference frame data struct + * + * Define the data struct to access vp9 reference frames. + */ +typedef struct vp9_ref_frame { + int idx; /**< frame index to get (input) */ + vpx_image_t img; /**< img structure to populate (output) */ +} vp9_ref_frame_t; + +/*!\brief vp8 decoder control function parameter type + * + * defines the data type for each of VP8 decoder control function requires + */ +VPX_CTRL_USE_TYPE(VP8_SET_REFERENCE, vpx_ref_frame_t *) +VPX_CTRL_USE_TYPE(VP8_COPY_REFERENCE, vpx_ref_frame_t *) +VPX_CTRL_USE_TYPE(VP8_SET_POSTPROC, vp8_postproc_cfg_t *) +VPX_CTRL_USE_TYPE(VP8_SET_DBG_COLOR_REF_FRAME, int) +VPX_CTRL_USE_TYPE(VP8_SET_DBG_COLOR_MB_MODES, int) +VPX_CTRL_USE_TYPE(VP8_SET_DBG_COLOR_B_MODES, int) +VPX_CTRL_USE_TYPE(VP8_SET_DBG_DISPLAY_MV, int) +VPX_CTRL_USE_TYPE(VP9_GET_REFERENCE, vp9_ref_frame_t *) + +/*! @} - end defgroup vp8 */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // VPX_VP8_H_ diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/vp8cx.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vp8cx.h new file mode 100644 index 0000000..60b588f --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vp8cx.h @@ -0,0 +1,699 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef VPX_VP8CX_H_ +#define VPX_VP8CX_H_ + +/*!\defgroup vp8_encoder WebM VP8/VP9 Encoder + * \ingroup vp8 + * + * @{ + */ +#include "./vp8.h" +#include "./vpx_encoder.h" + +/*!\file + * \brief Provides definitions for using VP8 or VP9 encoder algorithm within the + * vpx Codec Interface. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/*!\name Algorithm interface for VP8 + * + * This interface provides the capability to encode raw VP8 streams. + * @{ + */ +extern vpx_codec_iface_t vpx_codec_vp8_cx_algo; +extern vpx_codec_iface_t *vpx_codec_vp8_cx(void); +/*!@} - end algorithm interface member group*/ + +/*!\name Algorithm interface for VP9 + * + * This interface provides the capability to encode raw VP9 streams. + * @{ + */ +extern vpx_codec_iface_t vpx_codec_vp9_cx_algo; +extern vpx_codec_iface_t *vpx_codec_vp9_cx(void); +/*!@} - end algorithm interface member group*/ + + +/* + * Algorithm Flags + */ + +/*!\brief Don't reference the last frame + * + * When this flag is set, the encoder will not use the last frame as a + * predictor. When not set, the encoder will choose whether to use the + * last frame or not automatically. + */ +#define VP8_EFLAG_NO_REF_LAST (1<<16) + + +/*!\brief Don't reference the golden frame + * + * When this flag is set, the encoder will not use the golden frame as a + * predictor. When not set, the encoder will choose whether to use the + * golden frame or not automatically. + */ +#define VP8_EFLAG_NO_REF_GF (1<<17) + + +/*!\brief Don't reference the alternate reference frame + * + * When this flag is set, the encoder will not use the alt ref frame as a + * predictor. When not set, the encoder will choose whether to use the + * alt ref frame or not automatically. + */ +#define VP8_EFLAG_NO_REF_ARF (1<<21) + + +/*!\brief Don't update the last frame + * + * When this flag is set, the encoder will not update the last frame with + * the contents of the current frame. + */ +#define VP8_EFLAG_NO_UPD_LAST (1<<18) + + +/*!\brief Don't update the golden frame + * + * When this flag is set, the encoder will not update the golden frame with + * the contents of the current frame. + */ +#define VP8_EFLAG_NO_UPD_GF (1<<22) + + +/*!\brief Don't update the alternate reference frame + * + * When this flag is set, the encoder will not update the alt ref frame with + * the contents of the current frame. + */ +#define VP8_EFLAG_NO_UPD_ARF (1<<23) + + +/*!\brief Force golden frame update + * + * When this flag is set, the encoder copy the contents of the current frame + * to the golden frame buffer. + */ +#define VP8_EFLAG_FORCE_GF (1<<19) + + +/*!\brief Force alternate reference frame update + * + * When this flag is set, the encoder copy the contents of the current frame + * to the alternate reference frame buffer. + */ +#define VP8_EFLAG_FORCE_ARF (1<<24) + + +/*!\brief Disable entropy update + * + * When this flag is set, the encoder will not update its internal entropy + * model based on the entropy of this frame. + */ +#define VP8_EFLAG_NO_UPD_ENTROPY (1<<20) + + +/*!\brief VPx encoder control functions + * + * This set of macros define the control functions available for VPx + * encoder interface. + * + * \sa #vpx_codec_control + */ +enum vp8e_enc_control_id { + /*!\brief Codec control function to set mode of entropy update in encoder. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_UPD_ENTROPY = 5, + + /*!\brief Codec control function to set reference update mode in encoder. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_UPD_REFERENCE, + + /*!\brief Codec control function to set which reference frame encoder can use. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_USE_REFERENCE, + + /*!\brief Codec control function to pass an ROI map to encoder. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_ROI_MAP, + + /*!\brief Codec control function to pass an Active map to encoder. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_ACTIVEMAP, + + /*!\brief Codec control function to set encoder scaling mode. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_SCALEMODE = 11, + + /*!\brief Codec control function to set encoder internal speed settings. + * + * Changes in this value influences, among others, the encoder's selection + * of motion estimation methods. Values greater than 0 will increase encoder + * speed at the expense of quality. + * + * \note Valid range for VP8: -16..16 + * \note Valid range for VP9: -8..8 + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_CPUUSED = 13, + + /*!\brief Codec control function to enable automatic set and use alf frames. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_ENABLEAUTOALTREF, + + /*!\brief control function to set noise sensitivity + * + * 0: off, 1: OnYOnly, 2: OnYUV, + * 3: OnYUVAggressive, 4: Adaptive + * + * Supported in codecs: VP8 + */ + VP8E_SET_NOISE_SENSITIVITY, + + /*!\brief Codec control function to set sharpness. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_SHARPNESS, + + /*!\brief Codec control function to set the threshold for MBs treated static. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_STATIC_THRESHOLD, + + /*!\brief Codec control function to set the number of token partitions. + * + * Supported in codecs: VP8 + */ + VP8E_SET_TOKEN_PARTITIONS, + + /*!\brief Codec control function to get last quantizer chosen by the encoder. + * + * Return value uses internal quantizer scale defined by the codec. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_GET_LAST_QUANTIZER, + + /*!\brief Codec control function to get last quantizer chosen by the encoder. + * + * Return value uses the 0..63 scale as used by the rc_*_quantizer config + * parameters. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_GET_LAST_QUANTIZER_64, + + /*!\brief Codec control function to set the max no of frames to create arf. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_ARNR_MAXFRAMES, + + /*!\brief Codec control function to set the filter strength for the arf. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_ARNR_STRENGTH, + + /*!\deprecated control function to set the filter type to use for the arf. */ + VP8E_SET_ARNR_TYPE, + + /*!\brief Codec control function to set visual tuning. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_TUNING, + + /*!\brief Codec control function to set constrained quality level. + * + * \attention For this value to be used vpx_codec_enc_cfg_t::g_usage must be + * set to #VPX_CQ. + * \note Valid range: 0..63 + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_CQ_LEVEL, + + /*!\brief Codec control function to set Max data rate for Intra frames. + * + * This value controls additional clamping on the maximum size of a + * keyframe. It is expressed as a percentage of the average + * per-frame bitrate, with the special (and default) value 0 meaning + * unlimited, or no additional clamping beyond the codec's built-in + * algorithm. + * + * For example, to allocate no more than 4.5 frames worth of bitrate + * to a keyframe, set this to 450. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_MAX_INTRA_BITRATE_PCT, + + /*!\brief Codec control function to set reference and update frame flags. + * + * Supported in codecs: VP8 + */ + VP8E_SET_FRAME_FLAGS, + + /*!\brief Codec control function to set max data rate for Inter frames. + * + * This value controls additional clamping on the maximum size of an + * inter frame. It is expressed as a percentage of the average + * per-frame bitrate, with the special (and default) value 0 meaning + * unlimited, or no additional clamping beyond the codec's built-in + * algorithm. + * + * For example, to allow no more than 4.5 frames worth of bitrate + * to an inter frame, set this to 450. + * + * Supported in codecs: VP9 + */ + VP9E_SET_MAX_INTER_BITRATE_PCT, + + /*!\brief Boost percentage for Golden Frame in CBR mode. + * + * This value controls the amount of boost given to Golden Frame in + * CBR mode. It is expressed as a percentage of the average + * per-frame bitrate, with the special (and default) value 0 meaning + * the feature is off, i.e., no golden frame boost in CBR mode and + * average bitrate target is used. + * + * For example, to allow 100% more bits, i.e, 2X, in a golden frame + * than average frame, set this to 100. + * + * Supported in codecs: VP9 + */ + VP9E_SET_GF_CBR_BOOST_PCT, + + /*!\brief Codec control function to set the temporal layer id. + * + * For temporal scalability: this control allows the application to set the + * layer id for each frame to be encoded. Note that this control must be set + * for every frame prior to encoding. The usage of this control function + * supersedes the internal temporal pattern counter, which is now deprecated. + * + * Supported in codecs: VP8 + */ + VP8E_SET_TEMPORAL_LAYER_ID, + + /*!\brief Codec control function to set encoder screen content mode. + * + * Supported in codecs: VP8 + */ + VP8E_SET_SCREEN_CONTENT_MODE, + + /*!\brief Codec control function to set lossless encoding mode. + * + * VP9 can operate in lossless encoding mode, in which the bitstream + * produced will be able to decode and reconstruct a perfect copy of + * input source. This control function provides a mean to switch encoder + * into lossless coding mode(1) or normal coding mode(0) that may be lossy. + * 0 = lossy coding mode + * 1 = lossless coding mode + * + * By default, encoder operates in normal coding mode (maybe lossy). + * + * Supported in codecs: VP9 + */ + VP9E_SET_LOSSLESS, + + /*!\brief Codec control function to set number of tile columns. + * + * In encoding and decoding, VP9 allows an input image frame be partitioned + * into separated vertical tile columns, which can be encoded or decoded + * independently. This enables easy implementation of parallel encoding and + * decoding. This control requests the encoder to use column tiles in + * encoding an input frame, with number of tile columns (in Log2 unit) as + * the parameter: + * 0 = 1 tile column + * 1 = 2 tile columns + * 2 = 4 tile columns + * ..... + * n = 2**n tile columns + * The requested tile columns will be capped by encoder based on image size + * limitation (The minimum width of a tile column is 256 pixel, the maximum + * is 4096). + * + * By default, the value is 0, i.e. one single column tile for entire image. + * + * Supported in codecs: VP9 + */ + VP9E_SET_TILE_COLUMNS, + + /*!\brief Codec control function to set number of tile rows. + * + * In encoding and decoding, VP9 allows an input image frame be partitioned + * into separated horizontal tile rows. Tile rows are encoded or decoded + * sequentially. Even though encoding/decoding of later tile rows depends on + * earlier ones, this allows the encoder to output data packets for tile rows + * prior to completely processing all tile rows in a frame, thereby reducing + * the latency in processing between input and output. The parameter + * for this control describes the number of tile rows, which has a valid + * range [0, 2]: + * 0 = 1 tile row + * 1 = 2 tile rows + * 2 = 4 tile rows + * + * By default, the value is 0, i.e. one single row tile for entire image. + * + * Supported in codecs: VP9 + */ + VP9E_SET_TILE_ROWS, + + /*!\brief Codec control function to enable frame parallel decoding feature. + * + * VP9 has a bitstream feature to reduce decoding dependency between frames + * by turning off backward update of probability context used in encoding + * and decoding. This allows staged parallel processing of more than one + * video frames in the decoder. This control function provides a mean to + * turn this feature on or off for bitstreams produced by encoder. + * + * By default, this feature is off. + * + * Supported in codecs: VP9 + */ + VP9E_SET_FRAME_PARALLEL_DECODING, + + /*!\brief Codec control function to set adaptive quantization mode. + * + * VP9 has a segment based feature that allows encoder to adaptively change + * quantization parameter for each segment within a frame to improve the + * subjective quality. This control makes encoder operate in one of the + * several AQ_modes supported. + * + * By default, encoder operates with AQ_Mode 0(adaptive quantization off). + * + * Supported in codecs: VP9 + */ + VP9E_SET_AQ_MODE, + + /*!\brief Codec control function to enable/disable periodic Q boost. + * + * One VP9 encoder speed feature is to enable quality boost by lowering + * frame level Q periodically. This control function provides a mean to + * turn on/off this feature. + * 0 = off + * 1 = on + * + * By default, the encoder is allowed to use this feature for appropriate + * encoding modes. + * + * Supported in codecs: VP9 + */ + VP9E_SET_FRAME_PERIODIC_BOOST, + + /*!\brief Codec control function to set noise sensitivity. + * + * 0: off, 1: On(YOnly) + * + * Supported in codecs: VP9 + */ + VP9E_SET_NOISE_SENSITIVITY, + + /*!\brief Codec control function to turn on/off SVC in encoder. + * \note Return value is VPX_CODEC_INVALID_PARAM if the encoder does not + * support SVC in its current encoding mode + * 0: off, 1: on + * + * Supported in codecs: VP9 + */ + VP9E_SET_SVC, + +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) + /*!\brief Codec control function to set parameters for SVC. + * \note Parameters contain min_q, max_q, scaling factor for each of the + * SVC layers. + * + * Supported in codecs: VP9 + */ + VP9E_SET_SVC_PARAMETERS, +#endif + + /*!\brief Codec control function to set svc layer for spatial and temporal. + * \note Valid ranges: 0..#vpx_codec_enc_cfg::ss_number_layers for spatial + * layer and 0..#vpx_codec_enc_cfg::ts_number_layers for + * temporal layer. + * + * Supported in codecs: VP9 + */ + VP9E_SET_SVC_LAYER_ID, + + /*!\brief Codec control function to set content type. + * \note Valid parameter range: + * VP9E_CONTENT_DEFAULT = Regular video content (Default) + * VP9E_CONTENT_SCREEN = Screen capture content + * + * Supported in codecs: VP9 + */ + VP9E_SET_TUNE_CONTENT, + +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) + /*!\brief Codec control function to get svc layer ID. + * \note The layer ID returned is for the data packet from the registered + * callback function. + * + * Supported in codecs: VP9 + */ + VP9E_GET_SVC_LAYER_ID, + + /*!\brief Codec control function to register callback to get per layer packet. + * \note Parameter for this control function is a structure with a callback + * function and a pointer to private data used by the callback. + * + * Supported in codecs: VP9 + */ + VP9E_REGISTER_CX_CALLBACK, +#endif + + /*!\brief Codec control function to set color space info. + * \note Valid ranges: 0..7, default is "UNKNOWN". + * 0 = UNKNOWN, + * 1 = BT_601 + * 2 = BT_709 + * 3 = SMPTE_170 + * 4 = SMPTE_240 + * 5 = BT_2020 + * 6 = RESERVED + * 7 = SRGB + * + * Supported in codecs: VP9 + */ + VP9E_SET_COLOR_SPACE, +}; + +/*!\brief vpx 1-D scaling mode + * + * This set of constants define 1-D vpx scaling modes + */ +typedef enum vpx_scaling_mode_1d { + VP8E_NORMAL = 0, + VP8E_FOURFIVE = 1, + VP8E_THREEFIVE = 2, + VP8E_ONETWO = 3 +} VPX_SCALING_MODE; + + +/*!\brief vpx region of interest map + * + * These defines the data structures for the region of interest map + * + */ + +typedef struct vpx_roi_map { + /*! An id between 0 and 3 for each 16x16 region within a frame. */ + unsigned char *roi_map; + unsigned int rows; /**< Number of rows. */ + unsigned int cols; /**< Number of columns. */ + // TODO(paulwilkins): broken for VP9 which has 8 segments + // q and loop filter deltas for each segment + // (see MAX_MB_SEGMENTS) + int delta_q[4]; /**< Quantizer deltas. */ + int delta_lf[4]; /**< Loop filter deltas. */ + /*! Static breakout threshold for each segment. */ + unsigned int static_threshold[4]; +} vpx_roi_map_t; + +/*!\brief vpx active region map + * + * These defines the data structures for active region map + * + */ + + +typedef struct vpx_active_map { + unsigned char *active_map; /**< specify an on (1) or off (0) each 16x16 region within a frame */ + unsigned int rows; /**< number of rows */ + unsigned int cols; /**< number of cols */ +} vpx_active_map_t; + +/*!\brief vpx image scaling mode + * + * This defines the data structure for image scaling mode + * + */ +typedef struct vpx_scaling_mode { + VPX_SCALING_MODE h_scaling_mode; /**< horizontal scaling mode */ + VPX_SCALING_MODE v_scaling_mode; /**< vertical scaling mode */ +} vpx_scaling_mode_t; + +/*!\brief VP8 token partition mode + * + * This defines VP8 partitioning mode for compressed data, i.e., the number of + * sub-streams in the bitstream. Used for parallelized decoding. + * + */ + +typedef enum { + VP8_ONE_TOKENPARTITION = 0, + VP8_TWO_TOKENPARTITION = 1, + VP8_FOUR_TOKENPARTITION = 2, + VP8_EIGHT_TOKENPARTITION = 3 +} vp8e_token_partitions; + +/*!brief VP9 encoder content type */ +typedef enum { + VP9E_CONTENT_DEFAULT, + VP9E_CONTENT_SCREEN, + VP9E_CONTENT_INVALID +} vp9e_tune_content; + +/*!\brief VP8 model tuning parameters + * + * Changes the encoder to tune for certain types of input material. + * + */ +typedef enum { + VP8_TUNE_PSNR, + VP8_TUNE_SSIM +} vp8e_tuning; + +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) +/*!\brief vp9 svc layer parameters + * + * This defines the spatial and temporal layer id numbers for svc encoding. + * This is used with the #VP9E_SET_SVC_LAYER_ID control to set the spatial and + * temporal layer id for the current frame. + * + */ +typedef struct vpx_svc_layer_id { + int spatial_layer_id; /**< Spatial layer id number. */ + int temporal_layer_id; /**< Temporal layer id number. */ +} vpx_svc_layer_id_t; +#else +/*!\brief vp9 svc layer parameters + * + * This defines the temporal layer id numbers for svc encoding. + * This is used with the #VP9E_SET_SVC_LAYER_ID control to set the + * temporal layer id for the current frame. + * + */ +typedef struct vpx_svc_layer_id { + int temporal_layer_id; /**< Temporal layer id number. */ +} vpx_svc_layer_id_t; +#endif + +/*!\brief VP8 encoder control function parameter type + * + * Defines the data types that VP8E control functions take. Note that + * additional common controls are defined in vp8.h + * + */ + + +/* These controls have been deprecated in favor of the flags parameter to + * vpx_codec_encode(). See the definition of VP8_EFLAG_* above. + */ +VPX_CTRL_USE_TYPE_DEPRECATED(VP8E_UPD_ENTROPY, int) +VPX_CTRL_USE_TYPE_DEPRECATED(VP8E_UPD_REFERENCE, int) +VPX_CTRL_USE_TYPE_DEPRECATED(VP8E_USE_REFERENCE, int) + +VPX_CTRL_USE_TYPE(VP8E_SET_FRAME_FLAGS, int) +VPX_CTRL_USE_TYPE(VP8E_SET_TEMPORAL_LAYER_ID, int) +VPX_CTRL_USE_TYPE(VP8E_SET_ROI_MAP, vpx_roi_map_t *) +VPX_CTRL_USE_TYPE(VP8E_SET_ACTIVEMAP, vpx_active_map_t *) +VPX_CTRL_USE_TYPE(VP8E_SET_SCALEMODE, vpx_scaling_mode_t *) + +VPX_CTRL_USE_TYPE(VP9E_SET_SVC, int) +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) +VPX_CTRL_USE_TYPE(VP9E_SET_SVC_PARAMETERS, void *) +VPX_CTRL_USE_TYPE(VP9E_REGISTER_CX_CALLBACK, void *) +#endif +VPX_CTRL_USE_TYPE(VP9E_SET_SVC_LAYER_ID, vpx_svc_layer_id_t *) + +VPX_CTRL_USE_TYPE(VP8E_SET_CPUUSED, int) +VPX_CTRL_USE_TYPE(VP8E_SET_ENABLEAUTOALTREF, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_NOISE_SENSITIVITY, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_SHARPNESS, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_STATIC_THRESHOLD, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_TOKEN_PARTITIONS, int) /* vp8e_token_partitions */ + +VPX_CTRL_USE_TYPE(VP8E_SET_ARNR_MAXFRAMES, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_ARNR_STRENGTH, unsigned int) +VPX_CTRL_USE_TYPE_DEPRECATED(VP8E_SET_ARNR_TYPE, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_TUNING, int) /* vp8e_tuning */ +VPX_CTRL_USE_TYPE(VP8E_SET_CQ_LEVEL, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_TILE_COLUMNS, int) +VPX_CTRL_USE_TYPE(VP9E_SET_TILE_ROWS, int) + +VPX_CTRL_USE_TYPE(VP8E_GET_LAST_QUANTIZER, int *) +VPX_CTRL_USE_TYPE(VP8E_GET_LAST_QUANTIZER_64, int *) +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) +VPX_CTRL_USE_TYPE(VP9E_GET_SVC_LAYER_ID, vpx_svc_layer_id_t *) +#endif + +VPX_CTRL_USE_TYPE(VP8E_SET_MAX_INTRA_BITRATE_PCT, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_MAX_INTER_BITRATE_PCT, unsigned int) + +VPX_CTRL_USE_TYPE(VP8E_SET_SCREEN_CONTENT_MODE, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_GF_CBR_BOOST_PCT, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_LOSSLESS, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_FRAME_PARALLEL_DECODING, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_AQ_MODE, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_FRAME_PERIODIC_BOOST, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_NOISE_SENSITIVITY, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_TUNE_CONTENT, int) /* vp9e_tune_content */ + +VPX_CTRL_USE_TYPE(VP9E_SET_COLOR_SPACE, int) +/*! @} - end defgroup vp8_encoder */ +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // VPX_VP8CX_H_ diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/vp8dx.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vp8dx.h new file mode 100644 index 0000000..83898bf --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vp8dx.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +/*!\defgroup vp8_decoder WebM VP8/VP9 Decoder + * \ingroup vp8 + * + * @{ + */ +/*!\file + * \brief Provides definitions for using VP8 or VP9 within the vpx Decoder + * interface. + */ +#ifndef VPX_VP8DX_H_ +#define VPX_VP8DX_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Include controls common to both the encoder and decoder */ +#include "./vp8.h" + +/*!\name Algorithm interface for VP8 + * + * This interface provides the capability to decode VP8 streams. + * @{ + */ +extern vpx_codec_iface_t vpx_codec_vp8_dx_algo; +extern vpx_codec_iface_t *vpx_codec_vp8_dx(void); +/*!@} - end algorithm interface member group*/ + +/*!\name Algorithm interface for VP9 + * + * This interface provides the capability to decode VP9 streams. + * @{ + */ +extern vpx_codec_iface_t vpx_codec_vp9_dx_algo; +extern vpx_codec_iface_t *vpx_codec_vp9_dx(void); +/*!@} - end algorithm interface member group*/ + + +/*!\enum vp8_dec_control_id + * \brief VP8 decoder control functions + * + * This set of macros define the control functions available for the VP8 + * decoder interface. + * + * \sa #vpx_codec_control + */ +enum vp8_dec_control_id { + /** control function to get info on which reference frames were updated + * by the last decode + */ + VP8D_GET_LAST_REF_UPDATES = VP8_DECODER_CTRL_ID_START, + + /** check if the indicated frame is corrupted */ + VP8D_GET_FRAME_CORRUPTED, + + /** control function to get info on which reference frames were used + * by the last decode + */ + VP8D_GET_LAST_REF_USED, + + /** decryption function to decrypt encoded buffer data immediately + * before decoding. Takes a vpx_decrypt_init, which contains + * a callback function and opaque context pointer. + */ + VPXD_SET_DECRYPTOR, + VP8D_SET_DECRYPTOR = VPXD_SET_DECRYPTOR, + + /** control function to get the dimensions that the current frame is decoded + * at. This may be different to the intended display size for the frame as + * specified in the wrapper or frame header (see VP9D_GET_DISPLAY_SIZE). */ + VP9D_GET_FRAME_SIZE, + + /** control function to get the current frame's intended display dimensions + * (as specified in the wrapper or frame header). This may be different to + * the decoded dimensions of this frame (see VP9D_GET_FRAME_SIZE). */ + VP9D_GET_DISPLAY_SIZE, + + /** control function to get the bit depth of the stream. */ + VP9D_GET_BIT_DEPTH, + + /** control function to set the byte alignment of the planes in the reference + * buffers. Valid values are power of 2, from 32 to 1024. A value of 0 sets + * legacy alignment. I.e. Y plane is aligned to 32 bytes, U plane directly + * follows Y plane, and V plane directly follows U plane. Default value is 0. + */ + VP9_SET_BYTE_ALIGNMENT, + + /** control function to invert the decoding order to from right to left. The + * function is used in a test to confirm the decoding independence of tile + * columns. The function may be used in application where this order + * of decoding is desired. + * + * TODO(yaowu): Rework the unit test that uses this control, and in a future + * release, this test-only control shall be removed. + */ + VP9_INVERT_TILE_DECODE_ORDER, + + VP8_DECODER_CTRL_ID_MAX +}; + +/** Decrypt n bytes of data from input -> output, using the decrypt_state + * passed in VPXD_SET_DECRYPTOR. + */ +typedef void (*vpx_decrypt_cb)(void *decrypt_state, const unsigned char *input, + unsigned char *output, int count); + +/*!\brief Structure to hold decryption state + * + * Defines a structure to hold the decryption state and access function. + */ +typedef struct vpx_decrypt_init { + /*! Decrypt callback. */ + vpx_decrypt_cb decrypt_cb; + + /*! Decryption state. */ + void *decrypt_state; +} vpx_decrypt_init; + +/*!\brief A deprecated alias for vpx_decrypt_init. + */ +typedef vpx_decrypt_init vp8_decrypt_init; + + +/*!\brief VP8 decoder control function parameter type + * + * Defines the data types that VP8D control functions take. Note that + * additional common controls are defined in vp8.h + * + */ + + +VPX_CTRL_USE_TYPE(VP8D_GET_LAST_REF_UPDATES, int *) +VPX_CTRL_USE_TYPE(VP8D_GET_FRAME_CORRUPTED, int *) +VPX_CTRL_USE_TYPE(VP8D_GET_LAST_REF_USED, int *) +VPX_CTRL_USE_TYPE(VPXD_SET_DECRYPTOR, vpx_decrypt_init *) +VPX_CTRL_USE_TYPE(VP8D_SET_DECRYPTOR, vpx_decrypt_init *) +VPX_CTRL_USE_TYPE(VP9D_GET_DISPLAY_SIZE, int *) +VPX_CTRL_USE_TYPE(VP9D_GET_BIT_DEPTH, unsigned int *) +VPX_CTRL_USE_TYPE(VP9D_GET_FRAME_SIZE, int *) +VPX_CTRL_USE_TYPE(VP9_INVERT_TILE_DECODE_ORDER, int) + +/*! @} - end defgroup vp8_decoder */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // VPX_VP8DX_H_ diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_codec.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_codec.h new file mode 100644 index 0000000..b94e173 --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_codec.h @@ -0,0 +1,479 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +/*!\defgroup codec Common Algorithm Interface + * This abstraction allows applications to easily support multiple video + * formats with minimal code duplication. This section describes the interface + * common to all codecs (both encoders and decoders). + * @{ + */ + +/*!\file + * \brief Describes the codec algorithm interface to applications. + * + * This file describes the interface between an application and a + * video codec algorithm. + * + * An application instantiates a specific codec instance by using + * vpx_codec_init() and a pointer to the algorithm's interface structure: + *
+ *     my_app.c:
+ *       extern vpx_codec_iface_t my_codec;
+ *       {
+ *           vpx_codec_ctx_t algo;
+ *           res = vpx_codec_init(&algo, &my_codec);
+ *       }
+ *     
+ * + * Once initialized, the instance is manged using other functions from + * the vpx_codec_* family. + */ +#ifndef VPX_VPX_CODEC_H_ +#define VPX_VPX_CODEC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./vpx_integer.h" +#include "./vpx_image.h" + + /*!\brief Decorator indicating a function is deprecated */ +#ifndef DEPRECATED +#if defined(__GNUC__) && __GNUC__ +#define DEPRECATED __attribute__ ((deprecated)) +#elif defined(_MSC_VER) +#define DEPRECATED +#else +#define DEPRECATED +#endif +#endif /* DEPRECATED */ + +#ifndef DECLSPEC_DEPRECATED +#if defined(__GNUC__) && __GNUC__ +#define DECLSPEC_DEPRECATED /**< \copydoc #DEPRECATED */ +#elif defined(_MSC_VER) +#define DECLSPEC_DEPRECATED __declspec(deprecated) /**< \copydoc #DEPRECATED */ +#else +#define DECLSPEC_DEPRECATED /**< \copydoc #DEPRECATED */ +#endif +#endif /* DECLSPEC_DEPRECATED */ + + /*!\brief Decorator indicating a function is potentially unused */ +#ifdef UNUSED +#elif __GNUC__ +#define UNUSED __attribute__ ((unused)) +#else +#define UNUSED +#endif + + /*!\brief Current ABI version number + * + * \internal + * If this file is altered in any way that changes the ABI, this value + * must be bumped. Examples include, but are not limited to, changing + * types, removing or reassigning enums, adding/removing/rearranging + * fields to structures + */ +#define VPX_CODEC_ABI_VERSION (3 + VPX_IMAGE_ABI_VERSION) /**<\hideinitializer*/ + + /*!\brief Algorithm return codes */ + typedef enum { + /*!\brief Operation completed without error */ + VPX_CODEC_OK, + + /*!\brief Unspecified error */ + VPX_CODEC_ERROR, + + /*!\brief Memory operation failed */ + VPX_CODEC_MEM_ERROR, + + /*!\brief ABI version mismatch */ + VPX_CODEC_ABI_MISMATCH, + + /*!\brief Algorithm does not have required capability */ + VPX_CODEC_INCAPABLE, + + /*!\brief The given bitstream is not supported. + * + * The bitstream was unable to be parsed at the highest level. The decoder + * is unable to proceed. This error \ref SHOULD be treated as fatal to the + * stream. */ + VPX_CODEC_UNSUP_BITSTREAM, + + /*!\brief Encoded bitstream uses an unsupported feature + * + * The decoder does not implement a feature required by the encoder. This + * return code should only be used for features that prevent future + * pictures from being properly decoded. This error \ref MAY be treated as + * fatal to the stream or \ref MAY be treated as fatal to the current GOP. + */ + VPX_CODEC_UNSUP_FEATURE, + + /*!\brief The coded data for this stream is corrupt or incomplete + * + * There was a problem decoding the current frame. This return code + * should only be used for failures that prevent future pictures from + * being properly decoded. This error \ref MAY be treated as fatal to the + * stream or \ref MAY be treated as fatal to the current GOP. If decoding + * is continued for the current GOP, artifacts may be present. + */ + VPX_CODEC_CORRUPT_FRAME, + + /*!\brief An application-supplied parameter is not valid. + * + */ + VPX_CODEC_INVALID_PARAM, + + /*!\brief An iterator reached the end of list. + * + */ + VPX_CODEC_LIST_END + + } + vpx_codec_err_t; + + + /*! \brief Codec capabilities bitfield + * + * Each codec advertises the capabilities it supports as part of its + * ::vpx_codec_iface_t interface structure. Capabilities are extra interfaces + * or functionality, and are not required to be supported. + * + * The available flags are specified by VPX_CODEC_CAP_* defines. + */ + typedef long vpx_codec_caps_t; +#define VPX_CODEC_CAP_DECODER 0x1 /**< Is a decoder */ +#define VPX_CODEC_CAP_ENCODER 0x2 /**< Is an encoder */ + + + /*! \brief Initialization-time Feature Enabling + * + * Certain codec features must be known at initialization time, to allow for + * proper memory allocation. + * + * The available flags are specified by VPX_CODEC_USE_* defines. + */ + typedef long vpx_codec_flags_t; + + + /*!\brief Codec interface structure. + * + * Contains function pointers and other data private to the codec + * implementation. This structure is opaque to the application. + */ + typedef const struct vpx_codec_iface vpx_codec_iface_t; + + + /*!\brief Codec private data structure. + * + * Contains data private to the codec implementation. This structure is opaque + * to the application. + */ + typedef struct vpx_codec_priv vpx_codec_priv_t; + + + /*!\brief Iterator + * + * Opaque storage used for iterating over lists. + */ + typedef const void *vpx_codec_iter_t; + + + /*!\brief Codec context structure + * + * All codecs \ref MUST support this context structure fully. In general, + * this data should be considered private to the codec algorithm, and + * not be manipulated or examined by the calling application. Applications + * may reference the 'name' member to get a printable description of the + * algorithm. + */ + typedef struct vpx_codec_ctx { + const char *name; /**< Printable interface name */ + vpx_codec_iface_t *iface; /**< Interface pointers */ + vpx_codec_err_t err; /**< Last returned error */ + const char *err_detail; /**< Detailed info, if available */ + vpx_codec_flags_t init_flags; /**< Flags passed at init time */ + union { + /**< Decoder Configuration Pointer */ + const struct vpx_codec_dec_cfg *dec; + /**< Encoder Configuration Pointer */ + const struct vpx_codec_enc_cfg *enc; + const void *raw; + } config; /**< Configuration pointer aliasing union */ + vpx_codec_priv_t *priv; /**< Algorithm private storage */ + } vpx_codec_ctx_t; + + /*!\brief Bit depth for codec + * * + * This enumeration determines the bit depth of the codec. + */ + typedef enum vpx_bit_depth { + VPX_BITS_8 = 8, /**< 8 bits */ + VPX_BITS_10 = 10, /**< 10 bits */ + VPX_BITS_12 = 12, /**< 12 bits */ + } vpx_bit_depth_t; + + /* + * Library Version Number Interface + * + * For example, see the following sample return values: + * vpx_codec_version() (1<<16 | 2<<8 | 3) + * vpx_codec_version_str() "v1.2.3-rc1-16-gec6a1ba" + * vpx_codec_version_extra_str() "rc1-16-gec6a1ba" + */ + + /*!\brief Return the version information (as an integer) + * + * Returns a packed encoding of the library version number. This will only include + * the major.minor.patch component of the version number. Note that this encoded + * value should be accessed through the macros provided, as the encoding may change + * in the future. + * + */ + int vpx_codec_version(void); +#define VPX_VERSION_MAJOR(v) ((v>>16)&0xff) /**< extract major from packed version */ +#define VPX_VERSION_MINOR(v) ((v>>8)&0xff) /**< extract minor from packed version */ +#define VPX_VERSION_PATCH(v) ((v>>0)&0xff) /**< extract patch from packed version */ + + /*!\brief Return the version major number */ +#define vpx_codec_version_major() ((vpx_codec_version()>>16)&0xff) + + /*!\brief Return the version minor number */ +#define vpx_codec_version_minor() ((vpx_codec_version()>>8)&0xff) + + /*!\brief Return the version patch number */ +#define vpx_codec_version_patch() ((vpx_codec_version()>>0)&0xff) + + + /*!\brief Return the version information (as a string) + * + * Returns a printable string containing the full library version number. This may + * contain additional text following the three digit version number, as to indicate + * release candidates, prerelease versions, etc. + * + */ + const char *vpx_codec_version_str(void); + + + /*!\brief Return the version information (as a string) + * + * Returns a printable "extra string". This is the component of the string returned + * by vpx_codec_version_str() following the three digit version number. + * + */ + const char *vpx_codec_version_extra_str(void); + + + /*!\brief Return the build configuration + * + * Returns a printable string containing an encoded version of the build + * configuration. This may be useful to vpx support. + * + */ + const char *vpx_codec_build_config(void); + + + /*!\brief Return the name for a given interface + * + * Returns a human readable string for name of the given codec interface. + * + * \param[in] iface Interface pointer + * + */ + const char *vpx_codec_iface_name(vpx_codec_iface_t *iface); + + + /*!\brief Convert error number to printable string + * + * Returns a human readable string for the last error returned by the + * algorithm. The returned error will be one line and will not contain + * any newline characters. + * + * + * \param[in] err Error number. + * + */ + const char *vpx_codec_err_to_string(vpx_codec_err_t err); + + + /*!\brief Retrieve error synopsis for codec context + * + * Returns a human readable string for the last error returned by the + * algorithm. The returned error will be one line and will not contain + * any newline characters. + * + * + * \param[in] ctx Pointer to this instance's context. + * + */ + const char *vpx_codec_error(vpx_codec_ctx_t *ctx); + + + /*!\brief Retrieve detailed error information for codec context + * + * Returns a human readable string providing detailed information about + * the last error. + * + * \param[in] ctx Pointer to this instance's context. + * + * \retval NULL + * No detailed information is available. + */ + const char *vpx_codec_error_detail(vpx_codec_ctx_t *ctx); + + + /* REQUIRED FUNCTIONS + * + * The following functions are required to be implemented for all codecs. + * They represent the base case functionality expected of all codecs. + */ + + /*!\brief Destroy a codec instance + * + * Destroys a codec context, freeing any associated memory buffers. + * + * \param[in] ctx Pointer to this instance's context + * + * \retval #VPX_CODEC_OK + * The codec algorithm initialized. + * \retval #VPX_CODEC_MEM_ERROR + * Memory allocation failed. + */ + vpx_codec_err_t vpx_codec_destroy(vpx_codec_ctx_t *ctx); + + + /*!\brief Get the capabilities of an algorithm. + * + * Retrieves the capabilities bitfield from the algorithm's interface. + * + * \param[in] iface Pointer to the algorithm interface + * + */ + vpx_codec_caps_t vpx_codec_get_caps(vpx_codec_iface_t *iface); + + + /*!\brief Control algorithm + * + * This function is used to exchange algorithm specific data with the codec + * instance. This can be used to implement features specific to a particular + * algorithm. + * + * This wrapper function dispatches the request to the helper function + * associated with the given ctrl_id. It tries to call this function + * transparently, but will return #VPX_CODEC_ERROR if the request could not + * be dispatched. + * + * Note that this function should not be used directly. Call the + * #vpx_codec_control wrapper macro instead. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] ctrl_id Algorithm specific control identifier + * + * \retval #VPX_CODEC_OK + * The control request was processed. + * \retval #VPX_CODEC_ERROR + * The control request was not processed. + * \retval #VPX_CODEC_INVALID_PARAM + * The data was not valid. + */ + vpx_codec_err_t vpx_codec_control_(vpx_codec_ctx_t *ctx, + int ctrl_id, + ...); +#if defined(VPX_DISABLE_CTRL_TYPECHECKS) && VPX_DISABLE_CTRL_TYPECHECKS +# define vpx_codec_control(ctx,id,data) vpx_codec_control_(ctx,id,data) +# define VPX_CTRL_USE_TYPE(id, typ) +# define VPX_CTRL_USE_TYPE_DEPRECATED(id, typ) +# define VPX_CTRL_VOID(id, typ) + +#else + /*!\brief vpx_codec_control wrapper macro + * + * This macro allows for type safe conversions across the variadic parameter + * to vpx_codec_control_(). + * + * \internal + * It works by dispatching the call to the control function through a wrapper + * function named with the id parameter. + */ +# define vpx_codec_control(ctx,id,data) vpx_codec_control_##id(ctx,id,data)\ + /**<\hideinitializer*/ + + + /*!\brief vpx_codec_control type definition macro + * + * This macro allows for type safe conversions across the variadic parameter + * to vpx_codec_control_(). It defines the type of the argument for a given + * control identifier. + * + * \internal + * It defines a static function with + * the correctly typed arguments as a wrapper to the type-unsafe internal + * function. + */ +# define VPX_CTRL_USE_TYPE(id, typ) \ + static vpx_codec_err_t \ + vpx_codec_control_##id(vpx_codec_ctx_t*, int, typ) UNUSED;\ + \ + static vpx_codec_err_t \ + vpx_codec_control_##id(vpx_codec_ctx_t *ctx, int ctrl_id, typ data) {\ + return vpx_codec_control_(ctx, ctrl_id, data);\ + } /**<\hideinitializer*/ + + + /*!\brief vpx_codec_control deprecated type definition macro + * + * Like #VPX_CTRL_USE_TYPE, but indicates that the specified control is + * deprecated and should not be used. Consult the documentation for your + * codec for more information. + * + * \internal + * It defines a static function with the correctly typed arguments as a + * wrapper to the type-unsafe internal function. + */ +# define VPX_CTRL_USE_TYPE_DEPRECATED(id, typ) \ + DECLSPEC_DEPRECATED static vpx_codec_err_t \ + vpx_codec_control_##id(vpx_codec_ctx_t*, int, typ) DEPRECATED UNUSED;\ + \ + DECLSPEC_DEPRECATED static vpx_codec_err_t \ + vpx_codec_control_##id(vpx_codec_ctx_t *ctx, int ctrl_id, typ data) {\ + return vpx_codec_control_(ctx, ctrl_id, data);\ + } /**<\hideinitializer*/ + + + /*!\brief vpx_codec_control void type definition macro + * + * This macro allows for type safe conversions across the variadic parameter + * to vpx_codec_control_(). It indicates that a given control identifier takes + * no argument. + * + * \internal + * It defines a static function without a data argument as a wrapper to the + * type-unsafe internal function. + */ +# define VPX_CTRL_VOID(id) \ + static vpx_codec_err_t \ + vpx_codec_control_##id(vpx_codec_ctx_t*, int) UNUSED;\ + \ + static vpx_codec_err_t \ + vpx_codec_control_##id(vpx_codec_ctx_t *ctx, int ctrl_id) {\ + return vpx_codec_control_(ctx, ctrl_id);\ + } /**<\hideinitializer*/ + + +#endif + + /*!@} - end defgroup codec*/ +#ifdef __cplusplus +} +#endif +#endif // VPX_VPX_CODEC_H_ + diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_config.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_config.h new file mode 100644 index 0000000..358ecec --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_config.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* GENERATED FILE: DO NOT EDIT! */ + +#ifndef VPX_FRAMEWORK_HEADERS_VPX_VPX_CONFIG_H_ +#define VPX_FRAMEWORK_HEADERS_VPX_VPX_CONFIG_H_ + +#if defined __aarch64__ +#define VPX_FRAMEWORK_TARGET "arm64-darwin-gcc" +#include "vpx/vpx/arm64-darwin-gcc/vpx_config.h" +#elif defined __ARM_ARCH_7A__ +#define VPX_FRAMEWORK_TARGET "armv7-darwin-gcc" +#include "vpx/vpx/armv7-darwin-gcc/vpx_config.h" +#elif defined __ARM_ARCH_7S__ +#define VPX_FRAMEWORK_TARGET "armv7s-darwin-gcc" +#include "vpx/vpx/armv7s-darwin-gcc/vpx_config.h" +#elif defined __i386__ +#define VPX_FRAMEWORK_TARGET "x86-iphonesimulator-gcc" +#include "vpx/vpx/x86-iphonesimulator-gcc/vpx_config.h" +#elif defined __x86_64__ +#define VPX_FRAMEWORK_TARGET "x86_64-iphonesimulator-gcc" +#include "vpx/vpx/x86_64-iphonesimulator-gcc/vpx_config.h" +#endif + +#endif // VPX_FRAMEWORK_HEADERS_VPX_VPX_CONFIG_H_ \ No newline at end of file diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_decoder.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_decoder.h new file mode 100644 index 0000000..62fd919 --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_decoder.h @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef VPX_VPX_DECODER_H_ +#define VPX_VPX_DECODER_H_ + +/*!\defgroup decoder Decoder Algorithm Interface + * \ingroup codec + * This abstraction allows applications using this decoder to easily support + * multiple video formats with minimal code duplication. This section describes + * the interface common to all decoders. + * @{ + */ + +/*!\file + * \brief Describes the decoder algorithm interface to applications. + * + * This file describes the interface between an application and a + * video decoder algorithm. + * + */ +#ifdef __cplusplus +extern "C" { +#endif + +#include "./vpx_codec.h" +#include "./vpx_frame_buffer.h" + + /*!\brief Current ABI version number + * + * \internal + * If this file is altered in any way that changes the ABI, this value + * must be bumped. Examples include, but are not limited to, changing + * types, removing or reassigning enums, adding/removing/rearranging + * fields to structures + */ +#define VPX_DECODER_ABI_VERSION (3 + VPX_CODEC_ABI_VERSION) /**<\hideinitializer*/ + + /*! \brief Decoder capabilities bitfield + * + * Each decoder advertises the capabilities it supports as part of its + * ::vpx_codec_iface_t interface structure. Capabilities are extra interfaces + * or functionality, and are not required to be supported by a decoder. + * + * The available flags are specified by VPX_CODEC_CAP_* defines. + */ +#define VPX_CODEC_CAP_PUT_SLICE 0x10000 /**< Will issue put_slice callbacks */ +#define VPX_CODEC_CAP_PUT_FRAME 0x20000 /**< Will issue put_frame callbacks */ +#define VPX_CODEC_CAP_POSTPROC 0x40000 /**< Can postprocess decoded frame */ +#define VPX_CODEC_CAP_ERROR_CONCEALMENT 0x80000 /**< Can conceal errors due to + packet loss */ +#define VPX_CODEC_CAP_INPUT_FRAGMENTS 0x100000 /**< Can receive encoded frames + one fragment at a time */ + + /*! \brief Initialization-time Feature Enabling + * + * Certain codec features must be known at initialization time, to allow for + * proper memory allocation. + * + * The available flags are specified by VPX_CODEC_USE_* defines. + */ +#define VPX_CODEC_CAP_FRAME_THREADING 0x200000 /**< Can support frame-based + multi-threading */ +#define VPX_CODEC_CAP_EXTERNAL_FRAME_BUFFER 0x400000 /**< Can support external + frame buffers */ + +#define VPX_CODEC_USE_POSTPROC 0x10000 /**< Postprocess decoded frame */ +#define VPX_CODEC_USE_ERROR_CONCEALMENT 0x20000 /**< Conceal errors in decoded + frames */ +#define VPX_CODEC_USE_INPUT_FRAGMENTS 0x40000 /**< The input frame should be + passed to the decoder one + fragment at a time */ +#define VPX_CODEC_USE_FRAME_THREADING 0x80000 /**< Enable frame-based + multi-threading */ + + /*!\brief Stream properties + * + * This structure is used to query or set properties of the decoded + * stream. Algorithms may extend this structure with data specific + * to their bitstream by setting the sz member appropriately. + */ + typedef struct vpx_codec_stream_info { + unsigned int sz; /**< Size of this structure */ + unsigned int w; /**< Width (or 0 for unknown/default) */ + unsigned int h; /**< Height (or 0 for unknown/default) */ + unsigned int is_kf; /**< Current frame is a keyframe */ + } vpx_codec_stream_info_t; + + /* REQUIRED FUNCTIONS + * + * The following functions are required to be implemented for all decoders. + * They represent the base case functionality expected of all decoders. + */ + + + /*!\brief Initialization Configurations + * + * This structure is used to pass init time configuration options to the + * decoder. + */ + typedef struct vpx_codec_dec_cfg { + unsigned int threads; /**< Maximum number of threads to use, default 1 */ + unsigned int w; /**< Width */ + unsigned int h; /**< Height */ + } vpx_codec_dec_cfg_t; /**< alias for struct vpx_codec_dec_cfg */ + + + /*!\brief Initialize a decoder instance + * + * Initializes a decoder context using the given interface. Applications + * should call the vpx_codec_dec_init convenience macro instead of this + * function directly, to ensure that the ABI version number parameter + * is properly initialized. + * + * If the library was configured with --disable-multithread, this call + * is not thread safe and should be guarded with a lock if being used + * in a multithreaded context. + * + * \param[in] ctx Pointer to this instance's context. + * \param[in] iface Pointer to the algorithm interface to use. + * \param[in] cfg Configuration to use, if known. May be NULL. + * \param[in] flags Bitfield of VPX_CODEC_USE_* flags + * \param[in] ver ABI version number. Must be set to + * VPX_DECODER_ABI_VERSION + * \retval #VPX_CODEC_OK + * The decoder algorithm initialized. + * \retval #VPX_CODEC_MEM_ERROR + * Memory allocation failed. + */ + vpx_codec_err_t vpx_codec_dec_init_ver(vpx_codec_ctx_t *ctx, + vpx_codec_iface_t *iface, + const vpx_codec_dec_cfg_t *cfg, + vpx_codec_flags_t flags, + int ver); + + /*!\brief Convenience macro for vpx_codec_dec_init_ver() + * + * Ensures the ABI version parameter is properly set. + */ +#define vpx_codec_dec_init(ctx, iface, cfg, flags) \ + vpx_codec_dec_init_ver(ctx, iface, cfg, flags, VPX_DECODER_ABI_VERSION) + + + /*!\brief Parse stream info from a buffer + * + * Performs high level parsing of the bitstream. Construction of a decoder + * context is not necessary. Can be used to determine if the bitstream is + * of the proper format, and to extract information from the stream. + * + * \param[in] iface Pointer to the algorithm interface + * \param[in] data Pointer to a block of data to parse + * \param[in] data_sz Size of the data buffer + * \param[in,out] si Pointer to stream info to update. The size member + * \ref MUST be properly initialized, but \ref MAY be + * clobbered by the algorithm. This parameter \ref MAY + * be NULL. + * + * \retval #VPX_CODEC_OK + * Bitstream is parsable and stream information updated + */ + vpx_codec_err_t vpx_codec_peek_stream_info(vpx_codec_iface_t *iface, + const uint8_t *data, + unsigned int data_sz, + vpx_codec_stream_info_t *si); + + + /*!\brief Return information about the current stream. + * + * Returns information about the stream that has been parsed during decoding. + * + * \param[in] ctx Pointer to this instance's context + * \param[in,out] si Pointer to stream info to update. The size member + * \ref MUST be properly initialized, but \ref MAY be + * clobbered by the algorithm. This parameter \ref MAY + * be NULL. + * + * \retval #VPX_CODEC_OK + * Bitstream is parsable and stream information updated + */ + vpx_codec_err_t vpx_codec_get_stream_info(vpx_codec_ctx_t *ctx, + vpx_codec_stream_info_t *si); + + + /*!\brief Decode data + * + * Processes a buffer of coded data. If the processing results in a new + * decoded frame becoming available, PUT_SLICE and PUT_FRAME events may be + * generated, as appropriate. Encoded data \ref MUST be passed in DTS (decode + * time stamp) order. Frames produced will always be in PTS (presentation + * time stamp) order. + * If the decoder is configured with VPX_CODEC_USE_INPUT_FRAGMENTS enabled, + * data and data_sz can contain a fragment of the encoded frame. Fragment + * \#n must contain at least partition \#n, but can also contain subsequent + * partitions (\#n+1 - \#n+i), and if so, fragments \#n+1, .., \#n+i must + * be empty. When no more data is available, this function should be called + * with NULL as data and 0 as data_sz. The memory passed to this function + * must be available until the frame has been decoded. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] data Pointer to this block of new coded data. If + * NULL, a VPX_CODEC_CB_PUT_FRAME event is posted + * for the previously decoded frame. + * \param[in] data_sz Size of the coded data, in bytes. + * \param[in] user_priv Application specific data to associate with + * this frame. + * \param[in] deadline Soft deadline the decoder should attempt to meet, + * in us. Set to zero for unlimited. + * + * \return Returns #VPX_CODEC_OK if the coded data was processed completely + * and future pictures can be decoded without error. Otherwise, + * see the descriptions of the other error codes in ::vpx_codec_err_t + * for recoverability capabilities. + */ + vpx_codec_err_t vpx_codec_decode(vpx_codec_ctx_t *ctx, + const uint8_t *data, + unsigned int data_sz, + void *user_priv, + long deadline); + + + /*!\brief Decoded frames iterator + * + * Iterates over a list of the frames available for display. The iterator + * storage should be initialized to NULL to start the iteration. Iteration is + * complete when this function returns NULL. + * + * The list of available frames becomes valid upon completion of the + * vpx_codec_decode call, and remains valid until the next call to vpx_codec_decode. + * + * \param[in] ctx Pointer to this instance's context + * \param[in,out] iter Iterator storage, initialized to NULL + * + * \return Returns a pointer to an image, if one is ready for display. Frames + * produced will always be in PTS (presentation time stamp) order. + */ + vpx_image_t *vpx_codec_get_frame(vpx_codec_ctx_t *ctx, + vpx_codec_iter_t *iter); + + + /*!\defgroup cap_put_frame Frame-Based Decoding Functions + * + * The following functions are required to be implemented for all decoders + * that advertise the VPX_CODEC_CAP_PUT_FRAME capability. Calling these functions + * for codecs that don't advertise this capability will result in an error + * code being returned, usually VPX_CODEC_ERROR + * @{ + */ + + /*!\brief put frame callback prototype + * + * This callback is invoked by the decoder to notify the application of + * the availability of decoded image data. + */ + typedef void (*vpx_codec_put_frame_cb_fn_t)(void *user_priv, + const vpx_image_t *img); + + + /*!\brief Register for notification of frame completion. + * + * Registers a given function to be called when a decoded frame is + * available. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] cb Pointer to the callback function + * \param[in] user_priv User's private data + * + * \retval #VPX_CODEC_OK + * Callback successfully registered. + * \retval #VPX_CODEC_ERROR + * Decoder context not initialized, or algorithm not capable of + * posting slice completion. + */ + vpx_codec_err_t vpx_codec_register_put_frame_cb(vpx_codec_ctx_t *ctx, + vpx_codec_put_frame_cb_fn_t cb, + void *user_priv); + + + /*!@} - end defgroup cap_put_frame */ + + /*!\defgroup cap_put_slice Slice-Based Decoding Functions + * + * The following functions are required to be implemented for all decoders + * that advertise the VPX_CODEC_CAP_PUT_SLICE capability. Calling these functions + * for codecs that don't advertise this capability will result in an error + * code being returned, usually VPX_CODEC_ERROR + * @{ + */ + + /*!\brief put slice callback prototype + * + * This callback is invoked by the decoder to notify the application of + * the availability of partially decoded image data. The + */ + typedef void (*vpx_codec_put_slice_cb_fn_t)(void *user_priv, + const vpx_image_t *img, + const vpx_image_rect_t *valid, + const vpx_image_rect_t *update); + + + /*!\brief Register for notification of slice completion. + * + * Registers a given function to be called when a decoded slice is + * available. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] cb Pointer to the callback function + * \param[in] user_priv User's private data + * + * \retval #VPX_CODEC_OK + * Callback successfully registered. + * \retval #VPX_CODEC_ERROR + * Decoder context not initialized, or algorithm not capable of + * posting slice completion. + */ + vpx_codec_err_t vpx_codec_register_put_slice_cb(vpx_codec_ctx_t *ctx, + vpx_codec_put_slice_cb_fn_t cb, + void *user_priv); + + + /*!@} - end defgroup cap_put_slice*/ + + /*!\defgroup cap_external_frame_buffer External Frame Buffer Functions + * + * The following section is required to be implemented for all decoders + * that advertise the VPX_CODEC_CAP_EXTERNAL_FRAME_BUFFER capability. + * Calling this function for codecs that don't advertise this capability + * will result in an error code being returned, usually VPX_CODEC_ERROR. + * + * \note + * Currently this only works with VP9. + * @{ + */ + + /*!\brief Pass in external frame buffers for the decoder to use. + * + * Registers functions to be called when libvpx needs a frame buffer + * to decode the current frame and a function to be called when libvpx does + * not internally reference the frame buffer. This set function must + * be called before the first call to decode or libvpx will assume the + * default behavior of allocating frame buffers internally. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] cb_get Pointer to the get callback function + * \param[in] cb_release Pointer to the release callback function + * \param[in] cb_priv Callback's private data + * + * \retval #VPX_CODEC_OK + * External frame buffers will be used by libvpx. + * \retval #VPX_CODEC_INVALID_PARAM + * One or more of the callbacks were NULL. + * \retval #VPX_CODEC_ERROR + * Decoder context not initialized, or algorithm not capable of + * using external frame buffers. + * + * \note + * When decoding VP9, the application may be required to pass in at least + * #VP9_MAXIMUM_REF_BUFFERS + #VPX_MAXIMUM_WORK_BUFFERS external frame + * buffers. + */ + vpx_codec_err_t vpx_codec_set_frame_buffer_functions( + vpx_codec_ctx_t *ctx, + vpx_get_frame_buffer_cb_fn_t cb_get, + vpx_release_frame_buffer_cb_fn_t cb_release, void *cb_priv); + + /*!@} - end defgroup cap_external_frame_buffer */ + + /*!@} - end defgroup decoder*/ +#ifdef __cplusplus +} +#endif +#endif // VPX_VPX_DECODER_H_ + diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_encoder.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_encoder.h new file mode 100644 index 0000000..bf75584 --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_encoder.h @@ -0,0 +1,1023 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef VPX_VPX_ENCODER_H_ +#define VPX_VPX_ENCODER_H_ + +/*!\defgroup encoder Encoder Algorithm Interface + * \ingroup codec + * This abstraction allows applications using this encoder to easily support + * multiple video formats with minimal code duplication. This section describes + * the interface common to all encoders. + * @{ + */ + +/*!\file + * \brief Describes the encoder algorithm interface to applications. + * + * This file describes the interface between an application and a + * video encoder algorithm. + * + */ +#ifdef __cplusplus +extern "C" { +#endif + +#include "./vpx_codec.h" + + /*! Temporal Scalability: Maximum length of the sequence defining frame + * layer membership + */ +#define VPX_TS_MAX_PERIODICITY 16 + + /*! Temporal Scalability: Maximum number of coding layers */ +#define VPX_TS_MAX_LAYERS 5 + + /*!\deprecated Use #VPX_TS_MAX_PERIODICITY instead. */ +#define MAX_PERIODICITY VPX_TS_MAX_PERIODICITY + + /*!\deprecated Use #VPX_TS_MAX_LAYERS instead. */ +#define MAX_LAYERS VPX_TS_MAX_LAYERS + +/*! Spatial Scalability: Maximum number of coding layers */ +#define VPX_SS_MAX_LAYERS 5 + +/*! Spatial Scalability: Default number of coding layers */ +#define VPX_SS_DEFAULT_LAYERS 1 + + /*!\brief Current ABI version number + * + * \internal + * If this file is altered in any way that changes the ABI, this value + * must be bumped. Examples include, but are not limited to, changing + * types, removing or reassigning enums, adding/removing/rearranging + * fields to structures + */ +#define VPX_ENCODER_ABI_VERSION (4 + VPX_CODEC_ABI_VERSION) /**<\hideinitializer*/ + + + /*! \brief Encoder capabilities bitfield + * + * Each encoder advertises the capabilities it supports as part of its + * ::vpx_codec_iface_t interface structure. Capabilities are extra + * interfaces or functionality, and are not required to be supported + * by an encoder. + * + * The available flags are specified by VPX_CODEC_CAP_* defines. + */ +#define VPX_CODEC_CAP_PSNR 0x10000 /**< Can issue PSNR packets */ + + /*! Can output one partition at a time. Each partition is returned in its + * own VPX_CODEC_CX_FRAME_PKT, with the FRAME_IS_FRAGMENT flag set for + * every partition but the last. In this mode all frames are always + * returned partition by partition. + */ +#define VPX_CODEC_CAP_OUTPUT_PARTITION 0x20000 + +/*! Can support input images at greater than 8 bitdepth. + */ +#define VPX_CODEC_CAP_HIGHBITDEPTH 0x40000 + + /*! \brief Initialization-time Feature Enabling + * + * Certain codec features must be known at initialization time, to allow + * for proper memory allocation. + * + * The available flags are specified by VPX_CODEC_USE_* defines. + */ +#define VPX_CODEC_USE_PSNR 0x10000 /**< Calculate PSNR on each frame */ +#define VPX_CODEC_USE_OUTPUT_PARTITION 0x20000 /**< Make the encoder output one + partition at a time. */ +#define VPX_CODEC_USE_HIGHBITDEPTH 0x40000 /**< Use high bitdepth */ + + + /*!\brief Generic fixed size buffer structure + * + * This structure is able to hold a reference to any fixed size buffer. + */ + typedef struct vpx_fixed_buf { + void *buf; /**< Pointer to the data */ + size_t sz; /**< Length of the buffer, in chars */ + } vpx_fixed_buf_t; /**< alias for struct vpx_fixed_buf */ + + + /*!\brief Time Stamp Type + * + * An integer, which when multiplied by the stream's time base, provides + * the absolute time of a sample. + */ + typedef int64_t vpx_codec_pts_t; + + + /*!\brief Compressed Frame Flags + * + * This type represents a bitfield containing information about a compressed + * frame that may be useful to an application. The most significant 16 bits + * can be used by an algorithm to provide additional detail, for example to + * support frame types that are codec specific (MPEG-1 D-frames for example) + */ + typedef uint32_t vpx_codec_frame_flags_t; +#define VPX_FRAME_IS_KEY 0x1 /**< frame is the start of a GOP */ +#define VPX_FRAME_IS_DROPPABLE 0x2 /**< frame can be dropped without affecting + the stream (no future frame depends on + this one) */ +#define VPX_FRAME_IS_INVISIBLE 0x4 /**< frame should be decoded but will not + be shown */ +#define VPX_FRAME_IS_FRAGMENT 0x8 /**< this is a fragment of the encoded + frame */ + + /*!\brief Error Resilient flags + * + * These flags define which error resilient features to enable in the + * encoder. The flags are specified through the + * vpx_codec_enc_cfg::g_error_resilient variable. + */ + typedef uint32_t vpx_codec_er_flags_t; +#define VPX_ERROR_RESILIENT_DEFAULT 0x1 /**< Improve resiliency against + losses of whole frames */ +#define VPX_ERROR_RESILIENT_PARTITIONS 0x2 /**< The frame partitions are + independently decodable by the + bool decoder, meaning that + partitions can be decoded even + though earlier partitions have + been lost. Note that intra + predicition is still done over + the partition boundary. */ + + /*!\brief Encoder output packet variants + * + * This enumeration lists the different kinds of data packets that can be + * returned by calls to vpx_codec_get_cx_data(). Algorithms \ref MAY + * extend this list to provide additional functionality. + */ + enum vpx_codec_cx_pkt_kind { + VPX_CODEC_CX_FRAME_PKT, /**< Compressed video frame */ + VPX_CODEC_STATS_PKT, /**< Two-pass statistics for this frame */ + VPX_CODEC_FPMB_STATS_PKT, /**< first pass mb statistics for this frame */ + VPX_CODEC_PSNR_PKT, /**< PSNR statistics for this frame */ + // Spatial SVC is still experimental and may be removed before the next ABI + // bump. +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) + VPX_CODEC_SPATIAL_SVC_LAYER_SIZES, /**< Sizes for each layer in this frame*/ + VPX_CODEC_SPATIAL_SVC_LAYER_PSNR, /**< PSNR for each layer in this frame*/ +#endif + VPX_CODEC_CUSTOM_PKT = 256 /**< Algorithm extensions */ + }; + + + /*!\brief Encoder output packet + * + * This structure contains the different kinds of output data the encoder + * may produce while compressing a frame. + */ + typedef struct vpx_codec_cx_pkt { + enum vpx_codec_cx_pkt_kind kind; /**< packet variant */ + union { + struct { + void *buf; /**< compressed data buffer */ + size_t sz; /**< length of compressed data */ + vpx_codec_pts_t pts; /**< time stamp to show frame + (in timebase units) */ + unsigned long duration; /**< duration to show frame + (in timebase units) */ + vpx_codec_frame_flags_t flags; /**< flags for this frame */ + int partition_id; /**< the partition id + defines the decoding order + of the partitions. Only + applicable when "output partition" + mode is enabled. First partition + has id 0.*/ + + } frame; /**< data for compressed frame packet */ + vpx_fixed_buf_t twopass_stats; /**< data for two-pass packet */ + vpx_fixed_buf_t firstpass_mb_stats; /**< first pass mb packet */ + struct vpx_psnr_pkt { + unsigned int samples[4]; /**< Number of samples, total/y/u/v */ + uint64_t sse[4]; /**< sum squared error, total/y/u/v */ + double psnr[4]; /**< PSNR, total/y/u/v */ + } psnr; /**< data for PSNR packet */ + vpx_fixed_buf_t raw; /**< data for arbitrary packets */ + // Spatial SVC is still experimental and may be removed before the next + // ABI bump. +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) + size_t layer_sizes[VPX_SS_MAX_LAYERS]; + struct vpx_psnr_pkt layer_psnr[VPX_SS_MAX_LAYERS]; +#endif + + /* This packet size is fixed to allow codecs to extend this + * interface without having to manage storage for raw packets, + * i.e., if it's smaller than 128 bytes, you can store in the + * packet list directly. + */ + char pad[128 - sizeof(enum vpx_codec_cx_pkt_kind)]; /**< fixed sz */ + } data; /**< packet data */ + } vpx_codec_cx_pkt_t; /**< alias for struct vpx_codec_cx_pkt */ + + + /*!\brief Encoder return output buffer callback + * + * This callback function, when registered, returns with packets when each + * spatial layer is encoded. + */ + // putting the definitions here for now. (agrange: find if there + // is a better place for this) + typedef void (* vpx_codec_enc_output_cx_pkt_cb_fn_t)(vpx_codec_cx_pkt_t *pkt, + void *user_data); + + /*!\brief Callback function pointer / user data pair storage */ + typedef struct vpx_codec_enc_output_cx_cb_pair { + vpx_codec_enc_output_cx_pkt_cb_fn_t output_cx_pkt; /**< Callback function */ + void *user_priv; /**< Pointer to private data */ + } vpx_codec_priv_output_cx_pkt_cb_pair_t; + + /*!\brief Rational Number + * + * This structure holds a fractional value. + */ + typedef struct vpx_rational { + int num; /**< fraction numerator */ + int den; /**< fraction denominator */ + } vpx_rational_t; /**< alias for struct vpx_rational */ + + + /*!\brief Multi-pass Encoding Pass */ + enum vpx_enc_pass { + VPX_RC_ONE_PASS, /**< Single pass mode */ + VPX_RC_FIRST_PASS, /**< First pass of multi-pass mode */ + VPX_RC_LAST_PASS /**< Final pass of multi-pass mode */ + }; + + + /*!\brief Rate control mode */ + enum vpx_rc_mode { + VPX_VBR, /**< Variable Bit Rate (VBR) mode */ + VPX_CBR, /**< Constant Bit Rate (CBR) mode */ + VPX_CQ, /**< Constrained Quality (CQ) mode */ + VPX_Q, /**< Constant Quality (Q) mode */ + }; + + + /*!\brief Keyframe placement mode. + * + * This enumeration determines whether keyframes are placed automatically by + * the encoder or whether this behavior is disabled. Older releases of this + * SDK were implemented such that VPX_KF_FIXED meant keyframes were disabled. + * This name is confusing for this behavior, so the new symbols to be used + * are VPX_KF_AUTO and VPX_KF_DISABLED. + */ + enum vpx_kf_mode { + VPX_KF_FIXED, /**< deprecated, implies VPX_KF_DISABLED */ + VPX_KF_AUTO, /**< Encoder determines optimal placement automatically */ + VPX_KF_DISABLED = 0 /**< Encoder does not place keyframes. */ + }; + + + /*!\brief Encoded Frame Flags + * + * This type indicates a bitfield to be passed to vpx_codec_encode(), defining + * per-frame boolean values. By convention, bits common to all codecs will be + * named VPX_EFLAG_*, and bits specific to an algorithm will be named + * /algo/_eflag_*. The lower order 16 bits are reserved for common use. + */ + typedef long vpx_enc_frame_flags_t; +#define VPX_EFLAG_FORCE_KF (1<<0) /**< Force this frame to be a keyframe */ + + + /*!\brief Encoder configuration structure + * + * This structure contains the encoder settings that have common representations + * across all codecs. This doesn't imply that all codecs support all features, + * however. + */ + typedef struct vpx_codec_enc_cfg { + /* + * generic settings (g) + */ + + /*!\brief Algorithm specific "usage" value + * + * Algorithms may define multiple values for usage, which may convey the + * intent of how the application intends to use the stream. If this value + * is non-zero, consult the documentation for the codec to determine its + * meaning. + */ + unsigned int g_usage; + + + /*!\brief Maximum number of threads to use + * + * For multi-threaded implementations, use no more than this number of + * threads. The codec may use fewer threads than allowed. The value + * 0 is equivalent to the value 1. + */ + unsigned int g_threads; + + + /*!\brief Bitstream profile to use + * + * Some codecs support a notion of multiple bitstream profiles. Typically + * this maps to a set of features that are turned on or off. Often the + * profile to use is determined by the features of the intended decoder. + * Consult the documentation for the codec to determine the valid values + * for this parameter, or set to zero for a sane default. + */ + unsigned int g_profile; /**< profile of bitstream to use */ + + + + /*!\brief Width of the frame + * + * This value identifies the presentation resolution of the frame, + * in pixels. Note that the frames passed as input to the encoder must + * have this resolution. Frames will be presented by the decoder in this + * resolution, independent of any spatial resampling the encoder may do. + */ + unsigned int g_w; + + + /*!\brief Height of the frame + * + * This value identifies the presentation resolution of the frame, + * in pixels. Note that the frames passed as input to the encoder must + * have this resolution. Frames will be presented by the decoder in this + * resolution, independent of any spatial resampling the encoder may do. + */ + unsigned int g_h; + + /*!\brief Bit-depth of the codec + * + * This value identifies the bit_depth of the codec, + * Only certain bit-depths are supported as identified in the + * vpx_bit_depth_t enum. + */ + vpx_bit_depth_t g_bit_depth; + + /*!\brief Bit-depth of the input frames + * + * This value identifies the bit_depth of the input frames in bits. + * Note that the frames passed as input to the encoder must have + * this bit-depth. + */ + unsigned int g_input_bit_depth; + + /*!\brief Stream timebase units + * + * Indicates the smallest interval of time, in seconds, used by the stream. + * For fixed frame rate material, or variable frame rate material where + * frames are timed at a multiple of a given clock (ex: video capture), + * the \ref RECOMMENDED method is to set the timebase to the reciprocal + * of the frame rate (ex: 1001/30000 for 29.970 Hz NTSC). This allows the + * pts to correspond to the frame number, which can be handy. For + * re-encoding video from containers with absolute time timestamps, the + * \ref RECOMMENDED method is to set the timebase to that of the parent + * container or multimedia framework (ex: 1/1000 for ms, as in FLV). + */ + struct vpx_rational g_timebase; + + + /*!\brief Enable error resilient modes. + * + * The error resilient bitfield indicates to the encoder which features + * it should enable to take measures for streaming over lossy or noisy + * links. + */ + vpx_codec_er_flags_t g_error_resilient; + + + /*!\brief Multi-pass Encoding Mode + * + * This value should be set to the current phase for multi-pass encoding. + * For single pass, set to #VPX_RC_ONE_PASS. + */ + enum vpx_enc_pass g_pass; + + + /*!\brief Allow lagged encoding + * + * If set, this value allows the encoder to consume a number of input + * frames before producing output frames. This allows the encoder to + * base decisions for the current frame on future frames. This does + * increase the latency of the encoding pipeline, so it is not appropriate + * in all situations (ex: realtime encoding). + * + * Note that this is a maximum value -- the encoder may produce frames + * sooner than the given limit. Set this value to 0 to disable this + * feature. + */ + unsigned int g_lag_in_frames; + + + /* + * rate control settings (rc) + */ + + /*!\brief Temporal resampling configuration, if supported by the codec. + * + * Temporal resampling allows the codec to "drop" frames as a strategy to + * meet its target data rate. This can cause temporal discontinuities in + * the encoded video, which may appear as stuttering during playback. This + * trade-off is often acceptable, but for many applications is not. It can + * be disabled in these cases. + * + * Note that not all codecs support this feature. All vpx VPx codecs do. + * For other codecs, consult the documentation for that algorithm. + * + * This threshold is described as a percentage of the target data buffer. + * When the data buffer falls below this percentage of fullness, a + * dropped frame is indicated. Set the threshold to zero (0) to disable + * this feature. + */ + unsigned int rc_dropframe_thresh; + + + /*!\brief Enable/disable spatial resampling, if supported by the codec. + * + * Spatial resampling allows the codec to compress a lower resolution + * version of the frame, which is then upscaled by the encoder to the + * correct presentation resolution. This increases visual quality at + * low data rates, at the expense of CPU time on the encoder/decoder. + */ + unsigned int rc_resize_allowed; + + /*!\brief Internal coded frame width. + * + * If spatial resampling is enabled this specifies the width of the + * encoded frame. + */ + unsigned int rc_scaled_width; + + /*!\brief Internal coded frame height. + * + * If spatial resampling is enabled this specifies the height of the + * encoded frame. + */ + unsigned int rc_scaled_height; + + /*!\brief Spatial resampling up watermark. + * + * This threshold is described as a percentage of the target data buffer. + * When the data buffer rises above this percentage of fullness, the + * encoder will step up to a higher resolution version of the frame. + */ + unsigned int rc_resize_up_thresh; + + + /*!\brief Spatial resampling down watermark. + * + * This threshold is described as a percentage of the target data buffer. + * When the data buffer falls below this percentage of fullness, the + * encoder will step down to a lower resolution version of the frame. + */ + unsigned int rc_resize_down_thresh; + + + /*!\brief Rate control algorithm to use. + * + * Indicates whether the end usage of this stream is to be streamed over + * a bandwidth constrained link, indicating that Constant Bit Rate (CBR) + * mode should be used, or whether it will be played back on a high + * bandwidth link, as from a local disk, where higher variations in + * bitrate are acceptable. + */ + enum vpx_rc_mode rc_end_usage; + + + /*!\brief Two-pass stats buffer. + * + * A buffer containing all of the stats packets produced in the first + * pass, concatenated. + */ + vpx_fixed_buf_t rc_twopass_stats_in; + + /*!\brief first pass mb stats buffer. + * + * A buffer containing all of the first pass mb stats packets produced + * in the first pass, concatenated. + */ + vpx_fixed_buf_t rc_firstpass_mb_stats_in; + + /*!\brief Target data rate + * + * Target bandwidth to use for this stream, in kilobits per second. + */ + unsigned int rc_target_bitrate; + + + /* + * quantizer settings + */ + + + /*!\brief Minimum (Best Quality) Quantizer + * + * The quantizer is the most direct control over the quality of the + * encoded image. The range of valid values for the quantizer is codec + * specific. Consult the documentation for the codec to determine the + * values to use. To determine the range programmatically, call + * vpx_codec_enc_config_default() with a usage value of 0. + */ + unsigned int rc_min_quantizer; + + + /*!\brief Maximum (Worst Quality) Quantizer + * + * The quantizer is the most direct control over the quality of the + * encoded image. The range of valid values for the quantizer is codec + * specific. Consult the documentation for the codec to determine the + * values to use. To determine the range programmatically, call + * vpx_codec_enc_config_default() with a usage value of 0. + */ + unsigned int rc_max_quantizer; + + + /* + * bitrate tolerance + */ + + + /*!\brief Rate control adaptation undershoot control + * + * This value, expressed as a percentage of the target bitrate, + * controls the maximum allowed adaptation speed of the codec. + * This factor controls the maximum amount of bits that can + * be subtracted from the target bitrate in order to compensate + * for prior overshoot. + * + * Valid values in the range 0-1000. + */ + unsigned int rc_undershoot_pct; + + + /*!\brief Rate control adaptation overshoot control + * + * This value, expressed as a percentage of the target bitrate, + * controls the maximum allowed adaptation speed of the codec. + * This factor controls the maximum amount of bits that can + * be added to the target bitrate in order to compensate for + * prior undershoot. + * + * Valid values in the range 0-1000. + */ + unsigned int rc_overshoot_pct; + + + /* + * decoder buffer model parameters + */ + + + /*!\brief Decoder Buffer Size + * + * This value indicates the amount of data that may be buffered by the + * decoding application. Note that this value is expressed in units of + * time (milliseconds). For example, a value of 5000 indicates that the + * client will buffer (at least) 5000ms worth of encoded data. Use the + * target bitrate (#rc_target_bitrate) to convert to bits/bytes, if + * necessary. + */ + unsigned int rc_buf_sz; + + + /*!\brief Decoder Buffer Initial Size + * + * This value indicates the amount of data that will be buffered by the + * decoding application prior to beginning playback. This value is + * expressed in units of time (milliseconds). Use the target bitrate + * (#rc_target_bitrate) to convert to bits/bytes, if necessary. + */ + unsigned int rc_buf_initial_sz; + + + /*!\brief Decoder Buffer Optimal Size + * + * This value indicates the amount of data that the encoder should try + * to maintain in the decoder's buffer. This value is expressed in units + * of time (milliseconds). Use the target bitrate (#rc_target_bitrate) + * to convert to bits/bytes, if necessary. + */ + unsigned int rc_buf_optimal_sz; + + + /* + * 2 pass rate control parameters + */ + + + /*!\brief Two-pass mode CBR/VBR bias + * + * Bias, expressed on a scale of 0 to 100, for determining target size + * for the current frame. The value 0 indicates the optimal CBR mode + * value should be used. The value 100 indicates the optimal VBR mode + * value should be used. Values in between indicate which way the + * encoder should "lean." + */ + unsigned int rc_2pass_vbr_bias_pct; /**< RC mode bias between CBR and VBR(0-100: 0->CBR, 100->VBR) */ + + + /*!\brief Two-pass mode per-GOP minimum bitrate + * + * This value, expressed as a percentage of the target bitrate, indicates + * the minimum bitrate to be used for a single GOP (aka "section") + */ + unsigned int rc_2pass_vbr_minsection_pct; + + + /*!\brief Two-pass mode per-GOP maximum bitrate + * + * This value, expressed as a percentage of the target bitrate, indicates + * the maximum bitrate to be used for a single GOP (aka "section") + */ + unsigned int rc_2pass_vbr_maxsection_pct; + + + /* + * keyframing settings (kf) + */ + + /*!\brief Keyframe placement mode + * + * This value indicates whether the encoder should place keyframes at a + * fixed interval, or determine the optimal placement automatically + * (as governed by the #kf_min_dist and #kf_max_dist parameters) + */ + enum vpx_kf_mode kf_mode; + + + /*!\brief Keyframe minimum interval + * + * This value, expressed as a number of frames, prevents the encoder from + * placing a keyframe nearer than kf_min_dist to the previous keyframe. At + * least kf_min_dist frames non-keyframes will be coded before the next + * keyframe. Set kf_min_dist equal to kf_max_dist for a fixed interval. + */ + unsigned int kf_min_dist; + + + /*!\brief Keyframe maximum interval + * + * This value, expressed as a number of frames, forces the encoder to code + * a keyframe if one has not been coded in the last kf_max_dist frames. + * A value of 0 implies all frames will be keyframes. Set kf_min_dist + * equal to kf_max_dist for a fixed interval. + */ + unsigned int kf_max_dist; + + /* + * Spatial scalability settings (ss) + */ + + /*!\brief Number of spatial coding layers. + * + * This value specifies the number of spatial coding layers to be used. + */ + unsigned int ss_number_layers; + + /*!\brief Enable auto alt reference flags for each spatial layer. + * + * These values specify if auto alt reference frame is enabled for each + * spatial layer. + */ + int ss_enable_auto_alt_ref[VPX_SS_MAX_LAYERS]; + + /*!\brief Target bitrate for each spatial layer. + * + * These values specify the target coding bitrate to be used for each + * spatial layer. + */ + unsigned int ss_target_bitrate[VPX_SS_MAX_LAYERS]; + + /*!\brief Number of temporal coding layers. + * + * This value specifies the number of temporal layers to be used. + */ + unsigned int ts_number_layers; + + /*!\brief Target bitrate for each temporal layer. + * + * These values specify the target coding bitrate to be used for each + * temporal layer. + */ + unsigned int ts_target_bitrate[VPX_TS_MAX_LAYERS]; + + /*!\brief Frame rate decimation factor for each temporal layer. + * + * These values specify the frame rate decimation factors to apply + * to each temporal layer. + */ + unsigned int ts_rate_decimator[VPX_TS_MAX_LAYERS]; + + /*!\brief Length of the sequence defining frame temporal layer membership. + * + * This value specifies the length of the sequence that defines the + * membership of frames to temporal layers. For example, if the + * ts_periodicity = 8, then the frames are assigned to coding layers with a + * repeated sequence of length 8. + */ + unsigned int ts_periodicity; + + /*!\brief Template defining the membership of frames to temporal layers. + * + * This array defines the membership of frames to temporal coding layers. + * For a 2-layer encoding that assigns even numbered frames to one temporal + * layer (0) and odd numbered frames to a second temporal layer (1) with + * ts_periodicity=8, then ts_layer_id = (0,1,0,1,0,1,0,1). + */ + unsigned int ts_layer_id[VPX_TS_MAX_PERIODICITY]; + } vpx_codec_enc_cfg_t; /**< alias for struct vpx_codec_enc_cfg */ + + /*!\brief vp9 svc extra configure parameters + * + * This defines max/min quantizers and scale factors for each layer + * + */ + typedef struct vpx_svc_parameters { + int max_quantizers[VPX_SS_MAX_LAYERS]; /**< Max Q for each layer */ + int min_quantizers[VPX_SS_MAX_LAYERS]; /**< Min Q for each layer */ + int scaling_factor_num[VPX_SS_MAX_LAYERS]; /**< Scaling factor-numerator*/ + int scaling_factor_den[VPX_SS_MAX_LAYERS]; /**< Scaling factor-denominator*/ + } vpx_svc_extra_cfg_t; + + + /*!\brief Initialize an encoder instance + * + * Initializes a encoder context using the given interface. Applications + * should call the vpx_codec_enc_init convenience macro instead of this + * function directly, to ensure that the ABI version number parameter + * is properly initialized. + * + * If the library was configured with --disable-multithread, this call + * is not thread safe and should be guarded with a lock if being used + * in a multithreaded context. + * + * \param[in] ctx Pointer to this instance's context. + * \param[in] iface Pointer to the algorithm interface to use. + * \param[in] cfg Configuration to use, if known. May be NULL. + * \param[in] flags Bitfield of VPX_CODEC_USE_* flags + * \param[in] ver ABI version number. Must be set to + * VPX_ENCODER_ABI_VERSION + * \retval #VPX_CODEC_OK + * The decoder algorithm initialized. + * \retval #VPX_CODEC_MEM_ERROR + * Memory allocation failed. + */ + vpx_codec_err_t vpx_codec_enc_init_ver(vpx_codec_ctx_t *ctx, + vpx_codec_iface_t *iface, + const vpx_codec_enc_cfg_t *cfg, + vpx_codec_flags_t flags, + int ver); + + + /*!\brief Convenience macro for vpx_codec_enc_init_ver() + * + * Ensures the ABI version parameter is properly set. + */ +#define vpx_codec_enc_init(ctx, iface, cfg, flags) \ + vpx_codec_enc_init_ver(ctx, iface, cfg, flags, VPX_ENCODER_ABI_VERSION) + + + /*!\brief Initialize multi-encoder instance + * + * Initializes multi-encoder context using the given interface. + * Applications should call the vpx_codec_enc_init_multi convenience macro + * instead of this function directly, to ensure that the ABI version number + * parameter is properly initialized. + * + * \param[in] ctx Pointer to this instance's context. + * \param[in] iface Pointer to the algorithm interface to use. + * \param[in] cfg Configuration to use, if known. May be NULL. + * \param[in] num_enc Total number of encoders. + * \param[in] flags Bitfield of VPX_CODEC_USE_* flags + * \param[in] dsf Pointer to down-sampling factors. + * \param[in] ver ABI version number. Must be set to + * VPX_ENCODER_ABI_VERSION + * \retval #VPX_CODEC_OK + * The decoder algorithm initialized. + * \retval #VPX_CODEC_MEM_ERROR + * Memory allocation failed. + */ + vpx_codec_err_t vpx_codec_enc_init_multi_ver(vpx_codec_ctx_t *ctx, + vpx_codec_iface_t *iface, + vpx_codec_enc_cfg_t *cfg, + int num_enc, + vpx_codec_flags_t flags, + vpx_rational_t *dsf, + int ver); + + + /*!\brief Convenience macro for vpx_codec_enc_init_multi_ver() + * + * Ensures the ABI version parameter is properly set. + */ +#define vpx_codec_enc_init_multi(ctx, iface, cfg, num_enc, flags, dsf) \ + vpx_codec_enc_init_multi_ver(ctx, iface, cfg, num_enc, flags, dsf, \ + VPX_ENCODER_ABI_VERSION) + + + /*!\brief Get a default configuration + * + * Initializes a encoder configuration structure with default values. Supports + * the notion of "usages" so that an algorithm may offer different default + * settings depending on the user's intended goal. This function \ref SHOULD + * be called by all applications to initialize the configuration structure + * before specializing the configuration with application specific values. + * + * \param[in] iface Pointer to the algorithm interface to use. + * \param[out] cfg Configuration buffer to populate. + * \param[in] reserved Must set to 0 for VP8 and VP9. + * + * \retval #VPX_CODEC_OK + * The configuration was populated. + * \retval #VPX_CODEC_INCAPABLE + * Interface is not an encoder interface. + * \retval #VPX_CODEC_INVALID_PARAM + * A parameter was NULL, or the usage value was not recognized. + */ + vpx_codec_err_t vpx_codec_enc_config_default(vpx_codec_iface_t *iface, + vpx_codec_enc_cfg_t *cfg, + unsigned int reserved); + + + /*!\brief Set or change configuration + * + * Reconfigures an encoder instance according to the given configuration. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] cfg Configuration buffer to use + * + * \retval #VPX_CODEC_OK + * The configuration was populated. + * \retval #VPX_CODEC_INCAPABLE + * Interface is not an encoder interface. + * \retval #VPX_CODEC_INVALID_PARAM + * A parameter was NULL, or the usage value was not recognized. + */ + vpx_codec_err_t vpx_codec_enc_config_set(vpx_codec_ctx_t *ctx, + const vpx_codec_enc_cfg_t *cfg); + + + /*!\brief Get global stream headers + * + * Retrieves a stream level global header packet, if supported by the codec. + * + * \param[in] ctx Pointer to this instance's context + * + * \retval NULL + * Encoder does not support global header + * \retval Non-NULL + * Pointer to buffer containing global header packet + */ + vpx_fixed_buf_t *vpx_codec_get_global_headers(vpx_codec_ctx_t *ctx); + + +#define VPX_DL_REALTIME (1) /**< deadline parameter analogous to + * VPx REALTIME mode. */ +#define VPX_DL_GOOD_QUALITY (1000000) /**< deadline parameter analogous to + * VPx GOOD QUALITY mode. */ +#define VPX_DL_BEST_QUALITY (0) /**< deadline parameter analogous to + * VPx BEST QUALITY mode. */ + /*!\brief Encode a frame + * + * Encodes a video frame at the given "presentation time." The presentation + * time stamp (PTS) \ref MUST be strictly increasing. + * + * The encoder supports the notion of a soft real-time deadline. Given a + * non-zero value to the deadline parameter, the encoder will make a "best + * effort" guarantee to return before the given time slice expires. It is + * implicit that limiting the available time to encode will degrade the + * output quality. The encoder can be given an unlimited time to produce the + * best possible frame by specifying a deadline of '0'. This deadline + * supercedes the VPx notion of "best quality, good quality, realtime". + * Applications that wish to map these former settings to the new deadline + * based system can use the symbols #VPX_DL_REALTIME, #VPX_DL_GOOD_QUALITY, + * and #VPX_DL_BEST_QUALITY. + * + * When the last frame has been passed to the encoder, this function should + * continue to be called, with the img parameter set to NULL. This will + * signal the end-of-stream condition to the encoder and allow it to encode + * any held buffers. Encoding is complete when vpx_codec_encode() is called + * and vpx_codec_get_cx_data() returns no data. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] img Image data to encode, NULL to flush. + * \param[in] pts Presentation time stamp, in timebase units. + * \param[in] duration Duration to show frame, in timebase units. + * \param[in] flags Flags to use for encoding this frame. + * \param[in] deadline Time to spend encoding, in microseconds. (0=infinite) + * + * \retval #VPX_CODEC_OK + * The configuration was populated. + * \retval #VPX_CODEC_INCAPABLE + * Interface is not an encoder interface. + * \retval #VPX_CODEC_INVALID_PARAM + * A parameter was NULL, the image format is unsupported, etc. + */ + vpx_codec_err_t vpx_codec_encode(vpx_codec_ctx_t *ctx, + const vpx_image_t *img, + vpx_codec_pts_t pts, + unsigned long duration, + vpx_enc_frame_flags_t flags, + unsigned long deadline); + + /*!\brief Set compressed data output buffer + * + * Sets the buffer that the codec should output the compressed data + * into. This call effectively sets the buffer pointer returned in the + * next VPX_CODEC_CX_FRAME_PKT packet. Subsequent packets will be + * appended into this buffer. The buffer is preserved across frames, + * so applications must periodically call this function after flushing + * the accumulated compressed data to disk or to the network to reset + * the pointer to the buffer's head. + * + * `pad_before` bytes will be skipped before writing the compressed + * data, and `pad_after` bytes will be appended to the packet. The size + * of the packet will be the sum of the size of the actual compressed + * data, pad_before, and pad_after. The padding bytes will be preserved + * (not overwritten). + * + * Note that calling this function does not guarantee that the returned + * compressed data will be placed into the specified buffer. In the + * event that the encoded data will not fit into the buffer provided, + * the returned packet \ref MAY point to an internal buffer, as it would + * if this call were never used. In this event, the output packet will + * NOT have any padding, and the application must free space and copy it + * to the proper place. This is of particular note in configurations + * that may output multiple packets for a single encoded frame (e.g., lagged + * encoding) or if the application does not reset the buffer periodically. + * + * Applications may restore the default behavior of the codec providing + * the compressed data buffer by calling this function with a NULL + * buffer. + * + * Applications \ref MUSTNOT call this function during iteration of + * vpx_codec_get_cx_data(). + * + * \param[in] ctx Pointer to this instance's context + * \param[in] buf Buffer to store compressed data into + * \param[in] pad_before Bytes to skip before writing compressed data + * \param[in] pad_after Bytes to skip after writing compressed data + * + * \retval #VPX_CODEC_OK + * The buffer was set successfully. + * \retval #VPX_CODEC_INVALID_PARAM + * A parameter was NULL, the image format is unsupported, etc. + */ + vpx_codec_err_t vpx_codec_set_cx_data_buf(vpx_codec_ctx_t *ctx, + const vpx_fixed_buf_t *buf, + unsigned int pad_before, + unsigned int pad_after); + + + /*!\brief Encoded data iterator + * + * Iterates over a list of data packets to be passed from the encoder to the + * application. The different kinds of packets available are enumerated in + * #vpx_codec_cx_pkt_kind. + * + * #VPX_CODEC_CX_FRAME_PKT packets should be passed to the application's + * muxer. Multiple compressed frames may be in the list. + * #VPX_CODEC_STATS_PKT packets should be appended to a global buffer. + * + * The application \ref MUST silently ignore any packet kinds that it does + * not recognize or support. + * + * The data buffers returned from this function are only guaranteed to be + * valid until the application makes another call to any vpx_codec_* function. + * + * \param[in] ctx Pointer to this instance's context + * \param[in,out] iter Iterator storage, initialized to NULL + * + * \return Returns a pointer to an output data packet (compressed frame data, + * two-pass statistics, etc.) or NULL to signal end-of-list. + * + */ + const vpx_codec_cx_pkt_t *vpx_codec_get_cx_data(vpx_codec_ctx_t *ctx, + vpx_codec_iter_t *iter); + + + /*!\brief Get Preview Frame + * + * Returns an image that can be used as a preview. Shows the image as it would + * exist at the decompressor. The application \ref MUST NOT write into this + * image buffer. + * + * \param[in] ctx Pointer to this instance's context + * + * \return Returns a pointer to a preview image, or NULL if no image is + * available. + * + */ + const vpx_image_t *vpx_codec_get_preview_frame(vpx_codec_ctx_t *ctx); + + + /*!@} - end defgroup encoder*/ +#ifdef __cplusplus +} +#endif +#endif // VPX_VPX_ENCODER_H_ + diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_frame_buffer.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_frame_buffer.h new file mode 100644 index 0000000..9036459 --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_frame_buffer.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2014 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef VPX_VPX_FRAME_BUFFER_H_ +#define VPX_VPX_FRAME_BUFFER_H_ + +/*!\file + * \brief Describes the decoder external frame buffer interface. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./vpx_integer.h" + +/*!\brief The maximum number of work buffers used by libvpx. + * Support maximum 4 threads to decode video in parallel. + * Each thread will use one work buffer. + * TODO(hkuang): Add support to set number of worker threads dynamically. + */ +#define VPX_MAXIMUM_WORK_BUFFERS 8 + +/*!\brief The maximum number of reference buffers that a VP9 encoder may use. + */ +#define VP9_MAXIMUM_REF_BUFFERS 8 + +/*!\brief External frame buffer + * + * This structure holds allocated frame buffers used by the decoder. + */ +typedef struct vpx_codec_frame_buffer { + uint8_t *data; /**< Pointer to the data buffer */ + size_t size; /**< Size of data in bytes */ + void *priv; /**< Frame's private data */ +} vpx_codec_frame_buffer_t; + +/*!\brief get frame buffer callback prototype + * + * This callback is invoked by the decoder to retrieve data for the frame + * buffer in order for the decode call to complete. The callback must + * allocate at least min_size in bytes and assign it to fb->data. The callback + * must zero out all the data allocated. Then the callback must set fb->size + * to the allocated size. The application does not need to align the allocated + * data. The callback is triggered when the decoder needs a frame buffer to + * decode a compressed image into. This function may be called more than once + * for every call to vpx_codec_decode. The application may set fb->priv to + * some data which will be passed back in the ximage and the release function + * call. |fb| is guaranteed to not be NULL. On success the callback must + * return 0. Any failure the callback must return a value less than 0. + * + * \param[in] priv Callback's private data + * \param[in] new_size Size in bytes needed by the buffer + * \param[in,out] fb Pointer to vpx_codec_frame_buffer_t + */ +typedef int (*vpx_get_frame_buffer_cb_fn_t)( + void *priv, size_t min_size, vpx_codec_frame_buffer_t *fb); + +/*!\brief release frame buffer callback prototype + * + * This callback is invoked by the decoder when the frame buffer is not + * referenced by any other buffers. |fb| is guaranteed to not be NULL. On + * success the callback must return 0. Any failure the callback must return + * a value less than 0. + * + * \param[in] priv Callback's private data + * \param[in] fb Pointer to vpx_codec_frame_buffer_t + */ +typedef int (*vpx_release_frame_buffer_cb_fn_t)( + void *priv, vpx_codec_frame_buffer_t *fb); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // VPX_VPX_FRAME_BUFFER_H_ diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_image.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_image.h new file mode 100644 index 0000000..c06d351 --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_image.h @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +/*!\file + * \brief Describes the vpx image descriptor and associated operations + * + */ +#ifndef VPX_VPX_IMAGE_H_ +#define VPX_VPX_IMAGE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + /*!\brief Current ABI version number + * + * \internal + * If this file is altered in any way that changes the ABI, this value + * must be bumped. Examples include, but are not limited to, changing + * types, removing or reassigning enums, adding/removing/rearranging + * fields to structures + */ +#define VPX_IMAGE_ABI_VERSION (3) /**<\hideinitializer*/ + + +#define VPX_IMG_FMT_PLANAR 0x100 /**< Image is a planar format. */ +#define VPX_IMG_FMT_UV_FLIP 0x200 /**< V plane precedes U in memory. */ +#define VPX_IMG_FMT_HAS_ALPHA 0x400 /**< Image has an alpha channel. */ +#define VPX_IMG_FMT_HIGHBITDEPTH 0x800 /**< Image uses 16bit framebuffer. */ + + /*!\brief List of supported image formats */ + typedef enum vpx_img_fmt { + VPX_IMG_FMT_NONE, + VPX_IMG_FMT_RGB24, /**< 24 bit per pixel packed RGB */ + VPX_IMG_FMT_RGB32, /**< 32 bit per pixel packed 0RGB */ + VPX_IMG_FMT_RGB565, /**< 16 bit per pixel, 565 */ + VPX_IMG_FMT_RGB555, /**< 16 bit per pixel, 555 */ + VPX_IMG_FMT_UYVY, /**< UYVY packed YUV */ + VPX_IMG_FMT_YUY2, /**< YUYV packed YUV */ + VPX_IMG_FMT_YVYU, /**< YVYU packed YUV */ + VPX_IMG_FMT_BGR24, /**< 24 bit per pixel packed BGR */ + VPX_IMG_FMT_RGB32_LE, /**< 32 bit packed BGR0 */ + VPX_IMG_FMT_ARGB, /**< 32 bit packed ARGB, alpha=255 */ + VPX_IMG_FMT_ARGB_LE, /**< 32 bit packed BGRA, alpha=255 */ + VPX_IMG_FMT_RGB565_LE, /**< 16 bit per pixel, gggbbbbb rrrrrggg */ + VPX_IMG_FMT_RGB555_LE, /**< 16 bit per pixel, gggbbbbb 0rrrrrgg */ + VPX_IMG_FMT_YV12 = VPX_IMG_FMT_PLANAR | VPX_IMG_FMT_UV_FLIP | 1, /**< planar YVU */ + VPX_IMG_FMT_I420 = VPX_IMG_FMT_PLANAR | 2, + VPX_IMG_FMT_VPXYV12 = VPX_IMG_FMT_PLANAR | VPX_IMG_FMT_UV_FLIP | 3, /** < planar 4:2:0 format with vpx color space */ + VPX_IMG_FMT_VPXI420 = VPX_IMG_FMT_PLANAR | 4, + VPX_IMG_FMT_I422 = VPX_IMG_FMT_PLANAR | 5, + VPX_IMG_FMT_I444 = VPX_IMG_FMT_PLANAR | 6, + VPX_IMG_FMT_I440 = VPX_IMG_FMT_PLANAR | 7, + VPX_IMG_FMT_444A = VPX_IMG_FMT_PLANAR | VPX_IMG_FMT_HAS_ALPHA | 6, + VPX_IMG_FMT_I42016 = VPX_IMG_FMT_I420 | VPX_IMG_FMT_HIGHBITDEPTH, + VPX_IMG_FMT_I42216 = VPX_IMG_FMT_I422 | VPX_IMG_FMT_HIGHBITDEPTH, + VPX_IMG_FMT_I44416 = VPX_IMG_FMT_I444 | VPX_IMG_FMT_HIGHBITDEPTH, + VPX_IMG_FMT_I44016 = VPX_IMG_FMT_I440 | VPX_IMG_FMT_HIGHBITDEPTH + } vpx_img_fmt_t; /**< alias for enum vpx_img_fmt */ + + /*!\brief List of supported color spaces */ + typedef enum vpx_color_space { + VPX_CS_UNKNOWN = 0, /**< Unknown */ + VPX_CS_BT_601 = 1, /**< BT.601 */ + VPX_CS_BT_709 = 2, /**< BT.709 */ + VPX_CS_SMPTE_170 = 3, /**< SMPTE.170 */ + VPX_CS_SMPTE_240 = 4, /**< SMPTE.240 */ + VPX_CS_BT_2020 = 5, /**< BT.2020 */ + VPX_CS_RESERVED = 6, /**< Reserved */ + VPX_CS_SRGB = 7 /**< sRGB */ + } vpx_color_space_t; /**< alias for enum vpx_color_space */ + + /**\brief Image Descriptor */ + typedef struct vpx_image { + vpx_img_fmt_t fmt; /**< Image Format */ + vpx_color_space_t cs; /**< Color Space */ + + /* Image storage dimensions */ + unsigned int w; /**< Stored image width */ + unsigned int h; /**< Stored image height */ + unsigned int bit_depth; /**< Stored image bit-depth */ + + /* Image display dimensions */ + unsigned int d_w; /**< Displayed image width */ + unsigned int d_h; /**< Displayed image height */ + + /* Chroma subsampling info */ + unsigned int x_chroma_shift; /**< subsampling order, X */ + unsigned int y_chroma_shift; /**< subsampling order, Y */ + + /* Image data pointers. */ +#define VPX_PLANE_PACKED 0 /**< To be used for all packed formats */ +#define VPX_PLANE_Y 0 /**< Y (Luminance) plane */ +#define VPX_PLANE_U 1 /**< U (Chroma) plane */ +#define VPX_PLANE_V 2 /**< V (Chroma) plane */ +#define VPX_PLANE_ALPHA 3 /**< A (Transparency) plane */ + unsigned char *planes[4]; /**< pointer to the top left pixel for each plane */ + int stride[4]; /**< stride between rows for each plane */ + + int bps; /**< bits per sample (for packed formats) */ + + /* The following member may be set by the application to associate data + * with this image. + */ + void *user_priv; /**< may be set by the application to associate data + * with this image. */ + + /* The following members should be treated as private. */ + unsigned char *img_data; /**< private */ + int img_data_owner; /**< private */ + int self_allocd; /**< private */ + + void *fb_priv; /**< Frame buffer data associated with the image. */ + } vpx_image_t; /**< alias for struct vpx_image */ + + /**\brief Representation of a rectangle on a surface */ + typedef struct vpx_image_rect { + unsigned int x; /**< leftmost column */ + unsigned int y; /**< topmost row */ + unsigned int w; /**< width */ + unsigned int h; /**< height */ + } vpx_image_rect_t; /**< alias for struct vpx_image_rect */ + + /*!\brief Open a descriptor, allocating storage for the underlying image + * + * Returns a descriptor for storing an image of the given format. The + * storage for the descriptor is allocated on the heap. + * + * \param[in] img Pointer to storage for descriptor. If this parameter + * is NULL, the storage for the descriptor will be + * allocated on the heap. + * \param[in] fmt Format for the image + * \param[in] d_w Width of the image + * \param[in] d_h Height of the image + * \param[in] align Alignment, in bytes, of the image buffer and + * each row in the image(stride). + * + * \return Returns a pointer to the initialized image descriptor. If the img + * parameter is non-null, the value of the img parameter will be + * returned. + */ + vpx_image_t *vpx_img_alloc(vpx_image_t *img, + vpx_img_fmt_t fmt, + unsigned int d_w, + unsigned int d_h, + unsigned int align); + + /*!\brief Open a descriptor, using existing storage for the underlying image + * + * Returns a descriptor for storing an image of the given format. The + * storage for descriptor has been allocated elsewhere, and a descriptor is + * desired to "wrap" that storage. + * + * \param[in] img Pointer to storage for descriptor. If this parameter + * is NULL, the storage for the descriptor will be + * allocated on the heap. + * \param[in] fmt Format for the image + * \param[in] d_w Width of the image + * \param[in] d_h Height of the image + * \param[in] align Alignment, in bytes, of each row in the image. + * \param[in] img_data Storage to use for the image + * + * \return Returns a pointer to the initialized image descriptor. If the img + * parameter is non-null, the value of the img parameter will be + * returned. + */ + vpx_image_t *vpx_img_wrap(vpx_image_t *img, + vpx_img_fmt_t fmt, + unsigned int d_w, + unsigned int d_h, + unsigned int align, + unsigned char *img_data); + + + /*!\brief Set the rectangle identifying the displayed portion of the image + * + * Updates the displayed rectangle (aka viewport) on the image surface to + * match the specified coordinates and size. + * + * \param[in] img Image descriptor + * \param[in] x leftmost column + * \param[in] y topmost row + * \param[in] w width + * \param[in] h height + * + * \return 0 if the requested rectangle is valid, nonzero otherwise. + */ + int vpx_img_set_rect(vpx_image_t *img, + unsigned int x, + unsigned int y, + unsigned int w, + unsigned int h); + + + /*!\brief Flip the image vertically (top for bottom) + * + * Adjusts the image descriptor's pointers and strides to make the image + * be referenced upside-down. + * + * \param[in] img Image descriptor + */ + void vpx_img_flip(vpx_image_t *img); + + /*!\brief Close an image descriptor + * + * Frees all allocated storage associated with an image descriptor. + * + * \param[in] img Image descriptor + */ + void vpx_img_free(vpx_image_t *img); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // VPX_VPX_IMAGE_H_ diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_integer.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_integer.h new file mode 100644 index 0000000..829c9d1 --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_integer.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +#ifndef VPX_VPX_INTEGER_H_ +#define VPX_VPX_INTEGER_H_ + +/* get ptrdiff_t, size_t, wchar_t, NULL */ +#include + +#if defined(_MSC_VER) +#define VPX_FORCE_INLINE __forceinline +#define VPX_INLINE __inline +#else +#define VPX_FORCE_INLINE __inline__ __attribute__(always_inline) +// TODO(jbb): Allow a way to force inline off for older compilers. +#define VPX_INLINE inline +#endif + +#if (defined(_MSC_VER) && (_MSC_VER < 1600)) || defined(VPX_EMULATE_INTTYPES) +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; + +#if (defined(_MSC_VER) && (_MSC_VER < 1600)) +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +#define INT64_MAX _I64_MAX +#define INT32_MAX _I32_MAX +#define INT32_MIN _I32_MIN +#define INT16_MAX _I16_MAX +#define INT16_MIN _I16_MIN +#endif + +#ifndef _UINTPTR_T_DEFINED +typedef size_t uintptr_t; +#endif + +#else + +/* Most platforms have the C99 standard integer types. */ + +#if defined(__cplusplus) +# if !defined(__STDC_FORMAT_MACROS) +# define __STDC_FORMAT_MACROS +# endif +# if !defined(__STDC_LIMIT_MACROS) +# define __STDC_LIMIT_MACROS +# endif +#endif // __cplusplus + +#include + +#endif + +/* VS2010 defines stdint.h, but not inttypes.h */ +#if defined(_MSC_VER) && _MSC_VER < 1800 +#define PRId64 "I64d" +#else +#include +#endif + +#endif // VPX_VPX_INTEGER_H_ diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_version.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_version.h new file mode 100644 index 0000000..bce0381 --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/vpx_version.h @@ -0,0 +1,7 @@ +#define VERSION_MAJOR 1 +#define VERSION_MINOR 4 +#define VERSION_PATCH 0 +#define VERSION_EXTRA "" +#define VERSION_PACKED ((VERSION_MAJOR<<16)|(VERSION_MINOR<<8)|(VERSION_PATCH)) +#define VERSION_STRING_NOSP "v1.4.0" +#define VERSION_STRING " v1.4.0" diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/x86-iphonesimulator-gcc/vpx_config.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/x86-iphonesimulator-gcc/vpx_config.h new file mode 100644 index 0000000..8a95864 --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/x86-iphonesimulator-gcc/vpx_config.h @@ -0,0 +1,102 @@ +/* Copyright (c) 2011 The WebM project authors. All Rights Reserved. */ +/* */ +/* Use of this source code is governed by a BSD-style license */ +/* that can be found in the LICENSE file in the root of the source */ +/* tree. An additional intellectual property rights grant can be found */ +/* in the file PATENTS. All contributing project authors may */ +/* be found in the AUTHORS file in the root of the source tree. */ +/* This file automatically generated by configure. Do not edit! */ +#ifndef VPX_CONFIG_H +#define VPX_CONFIG_H +#define RESTRICT +#define INLINE inline +#define ARCH_ARM 0 +#define ARCH_MIPS 0 +#define ARCH_X86 1 +#define ARCH_X86_64 0 +#define ARCH_PPC32 0 +#define ARCH_PPC64 0 +#define HAVE_EDSP 0 +#define HAVE_MEDIA 0 +#define HAVE_NEON 0 +#define HAVE_NEON_ASM 0 +#define HAVE_MIPS32 0 +#define HAVE_DSPR2 0 +#define HAVE_MIPS64 0 +#define HAVE_MMX 1 +#define HAVE_SSE 1 +#define HAVE_SSE2 1 +#define HAVE_SSE3 1 +#define HAVE_SSSE3 1 +#define HAVE_SSE4_1 1 +#define HAVE_AVX 1 +#define HAVE_AVX2 1 +#define HAVE_ALTIVEC 0 +#define HAVE_VPX_PORTS 1 +#define HAVE_STDINT_H 1 +#define HAVE_ALT_TREE_LAYOUT 0 +#define HAVE_PTHREAD_H 1 +#define HAVE_SYS_MMAN_H 1 +#define HAVE_UNISTD_H 1 +#define CONFIG_DEPENDENCY_TRACKING 1 +#define CONFIG_EXTERNAL_BUILD 0 +#define CONFIG_INSTALL_DOCS 1 +#define CONFIG_INSTALL_BINS 1 +#define CONFIG_INSTALL_LIBS 1 +#define CONFIG_INSTALL_SRCS 0 +#define CONFIG_USE_X86INC 1 +#define CONFIG_DEBUG 0 +#define CONFIG_GPROF 0 +#define CONFIG_GCOV 0 +#define CONFIG_RVCT 0 +#define CONFIG_GCC 1 +#define CONFIG_MSVS 0 +#define CONFIG_PIC 0 +#define CONFIG_BIG_ENDIAN 0 +#define CONFIG_CODEC_SRCS 0 +#define CONFIG_DEBUG_LIBS 0 +#define CONFIG_FAST_UNALIGNED 1 +#define CONFIG_MEM_MANAGER 0 +#define CONFIG_MEM_TRACKER 0 +#define CONFIG_MEM_CHECKS 0 +#define CONFIG_DEQUANT_TOKENS 0 +#define CONFIG_DC_RECON 0 +#define CONFIG_RUNTIME_CPU_DETECT 1 +#define CONFIG_POSTPROC 1 +#define CONFIG_VP9_POSTPROC 0 +#define CONFIG_MULTITHREAD 1 +#define CONFIG_INTERNAL_STATS 0 +#define CONFIG_VP8_ENCODER 1 +#define CONFIG_VP8_DECODER 1 +#define CONFIG_VP9_ENCODER 1 +#define CONFIG_VP9_DECODER 1 +#define CONFIG_VP8 1 +#define CONFIG_VP9 1 +#define CONFIG_ENCODERS 1 +#define CONFIG_DECODERS 1 +#define CONFIG_STATIC_MSVCRT 0 +#define CONFIG_SPATIAL_RESAMPLING 1 +#define CONFIG_REALTIME_ONLY 0 +#define CONFIG_ONTHEFLY_BITPACKING 0 +#define CONFIG_ERROR_CONCEALMENT 0 +#define CONFIG_SHARED 0 +#define CONFIG_STATIC 1 +#define CONFIG_SMALL 0 +#define CONFIG_POSTPROC_VISUALIZER 0 +#define CONFIG_OS_SUPPORT 1 +#define CONFIG_UNIT_TESTS 0 +#define CONFIG_WEBM_IO 1 +#define CONFIG_LIBYUV 0 +#define CONFIG_DECODE_PERF_TESTS 0 +#define CONFIG_ENCODE_PERF_TESTS 0 +#define CONFIG_MULTI_RES_ENCODING 0 +#define CONFIG_TEMPORAL_DENOISING 1 +#define CONFIG_VP9_TEMPORAL_DENOISING 0 +#define CONFIG_COEFFICIENT_RANGE_CHECKING 0 +#define CONFIG_VP9_HIGHBITDEPTH 0 +#define CONFIG_EXPERIMENTAL 0 +#define CONFIG_SIZE_LIMIT 0 +#define CONFIG_SPATIAL_SVC 0 +#define CONFIG_FP_MB_STATS 0 +#define CONFIG_EMULATE_HARDWARE 0 +#endif /* VPX_CONFIG_H */ diff --git a/local_pod_repo/toxcore/ios/vpx.framework/Headers/x86_64-iphonesimulator-gcc/vpx_config.h b/local_pod_repo/toxcore/ios/vpx.framework/Headers/x86_64-iphonesimulator-gcc/vpx_config.h new file mode 100644 index 0000000..ae6b66d --- /dev/null +++ b/local_pod_repo/toxcore/ios/vpx.framework/Headers/x86_64-iphonesimulator-gcc/vpx_config.h @@ -0,0 +1,102 @@ +/* Copyright (c) 2011 The WebM project authors. All Rights Reserved. */ +/* */ +/* Use of this source code is governed by a BSD-style license */ +/* that can be found in the LICENSE file in the root of the source */ +/* tree. An additional intellectual property rights grant can be found */ +/* in the file PATENTS. All contributing project authors may */ +/* be found in the AUTHORS file in the root of the source tree. */ +/* This file automatically generated by configure. Do not edit! */ +#ifndef VPX_CONFIG_H +#define VPX_CONFIG_H +#define RESTRICT +#define INLINE inline +#define ARCH_ARM 0 +#define ARCH_MIPS 0 +#define ARCH_X86 0 +#define ARCH_X86_64 1 +#define ARCH_PPC32 0 +#define ARCH_PPC64 0 +#define HAVE_EDSP 0 +#define HAVE_MEDIA 0 +#define HAVE_NEON 0 +#define HAVE_NEON_ASM 0 +#define HAVE_MIPS32 0 +#define HAVE_DSPR2 0 +#define HAVE_MIPS64 0 +#define HAVE_MMX 1 +#define HAVE_SSE 1 +#define HAVE_SSE2 1 +#define HAVE_SSE3 1 +#define HAVE_SSSE3 1 +#define HAVE_SSE4_1 1 +#define HAVE_AVX 1 +#define HAVE_AVX2 1 +#define HAVE_ALTIVEC 0 +#define HAVE_VPX_PORTS 1 +#define HAVE_STDINT_H 1 +#define HAVE_ALT_TREE_LAYOUT 0 +#define HAVE_PTHREAD_H 1 +#define HAVE_SYS_MMAN_H 1 +#define HAVE_UNISTD_H 1 +#define CONFIG_DEPENDENCY_TRACKING 1 +#define CONFIG_EXTERNAL_BUILD 0 +#define CONFIG_INSTALL_DOCS 1 +#define CONFIG_INSTALL_BINS 1 +#define CONFIG_INSTALL_LIBS 1 +#define CONFIG_INSTALL_SRCS 0 +#define CONFIG_USE_X86INC 1 +#define CONFIG_DEBUG 0 +#define CONFIG_GPROF 0 +#define CONFIG_GCOV 0 +#define CONFIG_RVCT 0 +#define CONFIG_GCC 1 +#define CONFIG_MSVS 0 +#define CONFIG_PIC 0 +#define CONFIG_BIG_ENDIAN 0 +#define CONFIG_CODEC_SRCS 0 +#define CONFIG_DEBUG_LIBS 0 +#define CONFIG_FAST_UNALIGNED 1 +#define CONFIG_MEM_MANAGER 0 +#define CONFIG_MEM_TRACKER 0 +#define CONFIG_MEM_CHECKS 0 +#define CONFIG_DEQUANT_TOKENS 0 +#define CONFIG_DC_RECON 0 +#define CONFIG_RUNTIME_CPU_DETECT 1 +#define CONFIG_POSTPROC 1 +#define CONFIG_VP9_POSTPROC 0 +#define CONFIG_MULTITHREAD 1 +#define CONFIG_INTERNAL_STATS 0 +#define CONFIG_VP8_ENCODER 1 +#define CONFIG_VP8_DECODER 1 +#define CONFIG_VP9_ENCODER 1 +#define CONFIG_VP9_DECODER 1 +#define CONFIG_VP8 1 +#define CONFIG_VP9 1 +#define CONFIG_ENCODERS 1 +#define CONFIG_DECODERS 1 +#define CONFIG_STATIC_MSVCRT 0 +#define CONFIG_SPATIAL_RESAMPLING 1 +#define CONFIG_REALTIME_ONLY 0 +#define CONFIG_ONTHEFLY_BITPACKING 0 +#define CONFIG_ERROR_CONCEALMENT 0 +#define CONFIG_SHARED 0 +#define CONFIG_STATIC 1 +#define CONFIG_SMALL 0 +#define CONFIG_POSTPROC_VISUALIZER 0 +#define CONFIG_OS_SUPPORT 1 +#define CONFIG_UNIT_TESTS 0 +#define CONFIG_WEBM_IO 1 +#define CONFIG_LIBYUV 0 +#define CONFIG_DECODE_PERF_TESTS 0 +#define CONFIG_ENCODE_PERF_TESTS 0 +#define CONFIG_MULTI_RES_ENCODING 0 +#define CONFIG_TEMPORAL_DENOISING 1 +#define CONFIG_VP9_TEMPORAL_DENOISING 0 +#define CONFIG_COEFFICIENT_RANGE_CHECKING 0 +#define CONFIG_VP9_HIGHBITDEPTH 0 +#define CONFIG_EXPERIMENTAL 0 +#define CONFIG_SIZE_LIMIT 0 +#define CONFIG_SPATIAL_SVC 0 +#define CONFIG_FP_MB_STATS 0 +#define CONFIG_EMULATE_HARDWARE 0 +#endif /* VPX_CONFIG_H */ diff --git a/local_pod_repo/toxcore/ios/vpx.framework/vpx b/local_pod_repo/toxcore/ios/vpx.framework/vpx new file mode 100644 index 0000000..83fd763 Binary files /dev/null and b/local_pod_repo/toxcore/ios/vpx.framework/vpx differ diff --git a/local_pod_repo/toxcore/msgv3_addon.patch b/local_pod_repo/toxcore/msgv3_addon.patch new file mode 100644 index 0000000..0640971 --- /dev/null +++ b/local_pod_repo/toxcore/msgv3_addon.patch @@ -0,0 +1,162 @@ +diff --git a/toxcore/Messenger.c b/toxcore/Messenger.c +index 93fbae88..29e215b1 100644 +--- a/toxcore/Messenger.c ++++ b/toxcore/Messenger.c +@@ -497,7 +497,7 @@ bool m_friend_exists(const Messenger *m, int32_t friendnumber) + int m_send_message_generic(Messenger *m, int32_t friendnumber, uint8_t type, const uint8_t *message, uint32_t length, + uint32_t *message_id) + { +- if (type > MESSAGE_ACTION) { ++ if (type > MESSAGE_HIGH_LEVEL_ACK) { + LOGGER_WARNING(m->log, "message type %d is invalid", type); + return -5; + } +@@ -2053,7 +2053,8 @@ static int m_handle_packet(void *object, int i, const uint8_t *temp, uint16_t le + } + + case PACKET_ID_MESSAGE: // fall-through +- case PACKET_ID_ACTION: { ++ case PACKET_ID_ACTION: ++ case PACKET_ID_HIGH_LEVEL_ACK: { + if (data_length == 0) { + break; + } +diff --git a/toxcore/Messenger.h b/toxcore/Messenger.h +index b1395e62..6d15b9a6 100644 +--- a/toxcore/Messenger.h ++++ b/toxcore/Messenger.h +@@ -29,8 +29,9 @@ + #define FRIEND_ADDRESS_SIZE (CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t) + sizeof(uint16_t)) + + typedef enum Message_Type { +- MESSAGE_NORMAL, +- MESSAGE_ACTION, ++ MESSAGE_NORMAL = 0, ++ MESSAGE_ACTION = 1, ++ MESSAGE_HIGH_LEVEL_ACK = 2, + } Message_Type; + + typedef struct Messenger Messenger; +diff --git a/toxcore/crypto_core.c b/toxcore/crypto_core.c +index f52282d02..4be4d8c69 100644 +--- a/toxcore/crypto_core.c ++++ b/toxcore/crypto_core.c +@@ -462,6 +462,11 @@ void random_nonce(const Random *rng, uint8_t *nonce) + random_bytes(rng, nonce, crypto_box_NONCEBYTES); + } + ++void new_symmetric_key_implicit_random(uint8_t *key) ++{ ++ randombytes(key, CRYPTO_SYMMETRIC_KEY_SIZE); ++} ++ + void new_symmetric_key(const Random *rng, uint8_t *key) + { + random_bytes(rng, key, CRYPTO_SYMMETRIC_KEY_SIZE); +diff --git a/toxcore/crypto_core.h b/toxcore/crypto_core.h +index 0aaadeacf..5f525c747 100644 +--- a/toxcore/crypto_core.h ++++ b/toxcore/crypto_core.h +@@ -408,6 +408,15 @@ void increment_nonce(uint8_t *nonce); + non_null() + void increment_nonce_number(uint8_t *nonce, uint32_t increment); + ++/** ++ * @brief Fill a key @ref CRYPTO_SYMMETRIC_KEY_SIZE big with random bytes. ++ * ++ * This does the same as `new_symmetric_key` but without giving the Random object implicitly. ++ * It is as safe as `new_symmetric_key`. ++ */ ++non_null() ++void new_symmetric_key_implicit_random(uint8_t *key); ++ + /** + * @brief Fill a key @ref CRYPTO_SYMMETRIC_KEY_SIZE big with random bytes. + */ +diff --git a/toxcore/net_crypto.h b/toxcore/net_crypto.h +index dd4dfcc5..1ec62f0d 100644 +--- a/toxcore/net_crypto.h ++++ b/toxcore/net_crypto.h +@@ -55,6 +55,7 @@ + #define PACKET_ID_TYPING 51 + #define PACKET_ID_MESSAGE 64 + #define PACKET_ID_ACTION 65 // PACKET_ID_MESSAGE + MESSAGE_ACTION ++#define PACKET_ID_HIGH_LEVEL_ACK 66 // MSG V3 + #define PACKET_ID_MSI 69 // Used by AV to setup calls and etc + #define PACKET_ID_FILE_SENDREQUEST 80 + #define PACKET_ID_FILE_CONTROL 81 +diff --git a/toxcore/tox.c b/toxcore/tox.c +index 7d24aa1b..1df6c4fd 100644 +--- a/toxcore/tox.c ++++ b/toxcore/tox.c +@@ -999,6 +999,17 @@ bool tox_self_set_name(Tox *tox, const uint8_t *name, size_t length, Tox_Err_Set + return false; + } + ++bool tox_messagev3_get_new_message_id(uint8_t *msg_id) ++{ ++ if (msg_id == nullptr) { ++ return false; ++ } ++ ++ /* Tox keys are 32 bytes like TOX_MSGV3_MSGID_LENGTH. */ ++ new_symmetric_key_implicit_random(msg_id); ++ return true; ++} ++ + size_t tox_self_get_name_size(const Tox *tox) + { + assert(tox != nullptr); +diff --git a/toxcore/tox.h b/toxcore/tox.h +index 54ca2aff..79dbdf21 100644 +--- a/toxcore/tox.h ++++ b/toxcore/tox.h +@@ -296,6 +296,11 @@ uint32_t tox_max_friend_request_length(void); + + uint32_t tox_max_message_length(void); + ++#define TOX_MSGV3_MSGID_LENGTH 32 ++#define TOX_MSGV3_TIMESTAMP_LENGTH 4 ++#define TOX_MSGV3_GUARD 2 ++#define TOX_MSGV3_MAX_MESSAGE_LENGTH (TOX_MAX_MESSAGE_LENGTH - TOX_MSGV3_MSGID_LENGTH - TOX_MSGV3_TIMESTAMP_LENGTH - TOX_MSGV3_GUARD) ++ + /** + * @brief Maximum size of custom packets. TODO(iphydf): should be LENGTH? + * +@@ -381,13 +386,18 @@ typedef enum Tox_Message_Type { + /** + * Normal text message. Similar to PRIVMSG on IRC. + */ +- TOX_MESSAGE_TYPE_NORMAL, ++ TOX_MESSAGE_TYPE_NORMAL = 0, + + /** + * A message describing an user action. This is similar to /me (CTCP ACTION) + * on IRC. + */ +- TOX_MESSAGE_TYPE_ACTION, ++ TOX_MESSAGE_TYPE_ACTION = 1, ++ ++ /** ++ * A high level ACK for MSG ID (MSG V3 functionality) ++ */ ++ TOX_MESSAGE_TYPE_HIGH_LEVEL_ACK = 2, + + } Tox_Message_Type; + +@@ -1161,6 +1171,15 @@ size_t tox_self_get_name_size(const Tox *tox); + */ + void tox_self_get_name(const Tox *tox, uint8_t *name); + ++/** ++ * Write new message ID to a byte array. ++ * ++ * @param msg_id A valid memory location at least TOX_HASH_LENGTH bytes in size. ++ * ++ * @return true on success. ++ */ ++bool tox_messagev3_get_new_message_id(uint8_t *msg_id); ++ + /** + * @brief Set the client's status message. + * diff --git a/local_pod_repo/toxcore/osx/vpx.framework/Headers/vp8.h b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vp8.h new file mode 100644 index 0000000..2a31af6 --- /dev/null +++ b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vp8.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/*!\defgroup vp8 VP8 + * \ingroup codecs + * VP8 is vpx's newest video compression algorithm that uses motion + * compensated prediction, Discrete Cosine Transform (DCT) coding of the + * prediction error signal and context dependent entropy coding techniques + * based on arithmetic principles. It features: + * - YUV 4:2:0 image format + * - Macro-block based coding (16x16 luma plus two 8x8 chroma) + * - 1/4 (1/8) pixel accuracy motion compensated prediction + * - 4x4 DCT transform + * - 128 level linear quantizer + * - In loop deblocking filter + * - Context-based entropy coding + * + * @{ + */ +/*!\file + * \brief Provides controls common to both the VP8 encoder and decoder. + */ +#ifndef VPX_VP8_H_ +#define VPX_VP8_H_ + +#include "./vpx_codec.h" +#include "./vpx_image.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*!\brief Control functions + * + * The set of macros define the control functions of VP8 interface + */ +enum vp8_com_control_id { + VP8_SET_REFERENCE = 1, /**< pass in an external frame into decoder to be used as reference frame */ + VP8_COPY_REFERENCE = 2, /**< get a copy of reference frame from the decoder */ + VP8_SET_POSTPROC = 3, /**< set the decoder's post processing settings */ + VP8_SET_DBG_COLOR_REF_FRAME = 4, /**< set the reference frames to color for each macroblock */ + VP8_SET_DBG_COLOR_MB_MODES = 5, /**< set which macro block modes to color */ + VP8_SET_DBG_COLOR_B_MODES = 6, /**< set which blocks modes to color */ + VP8_SET_DBG_DISPLAY_MV = 7, /**< set which motion vector modes to draw */ + + /* TODO(jkoleszar): The encoder incorrectly reuses some of these values (5+) + * for its control ids. These should be migrated to something like the + * VP8_DECODER_CTRL_ID_START range next time we're ready to break the ABI. + */ + VP9_GET_REFERENCE = 128, /**< get a pointer to a reference frame */ + VP8_COMMON_CTRL_ID_MAX, + VP8_DECODER_CTRL_ID_START = 256 +}; + +/*!\brief post process flags + * + * The set of macros define VP8 decoder post processing flags + */ +enum vp8_postproc_level { + VP8_NOFILTERING = 0, + VP8_DEBLOCK = 1 << 0, + VP8_DEMACROBLOCK = 1 << 1, + VP8_ADDNOISE = 1 << 2, + VP8_DEBUG_TXT_FRAME_INFO = 1 << 3, /**< print frame information */ + VP8_DEBUG_TXT_MBLK_MODES = 1 << 4, /**< print macro block modes over each macro block */ + VP8_DEBUG_TXT_DC_DIFF = 1 << 5, /**< print dc diff for each macro block */ + VP8_DEBUG_TXT_RATE_INFO = 1 << 6, /**< print video rate info (encoder only) */ + VP8_MFQE = 1 << 10 +}; + +/*!\brief post process flags + * + * This define a structure that describe the post processing settings. For + * the best objective measure (using the PSNR metric) set post_proc_flag + * to VP8_DEBLOCK and deblocking_level to 1. + */ + +typedef struct vp8_postproc_cfg { + int post_proc_flag; /**< the types of post processing to be done, should be combination of "vp8_postproc_level" */ + int deblocking_level; /**< the strength of deblocking, valid range [0, 16] */ + int noise_level; /**< the strength of additive noise, valid range [0, 16] */ +} vp8_postproc_cfg_t; + +/*!\brief reference frame type + * + * The set of macros define the type of VP8 reference frames + */ +typedef enum vpx_ref_frame_type { + VP8_LAST_FRAME = 1, + VP8_GOLD_FRAME = 2, + VP8_ALTR_FRAME = 4 +} vpx_ref_frame_type_t; + +/*!\brief reference frame data struct + * + * Define the data struct to access vp8 reference frames. + */ +typedef struct vpx_ref_frame { + vpx_ref_frame_type_t frame_type; /**< which reference frame */ + vpx_image_t img; /**< reference frame data in image format */ +} vpx_ref_frame_t; + +/*!\brief VP9 specific reference frame data struct + * + * Define the data struct to access vp9 reference frames. + */ +typedef struct vp9_ref_frame { + int idx; /**< frame index to get (input) */ + vpx_image_t img; /**< img structure to populate (output) */ +} vp9_ref_frame_t; + +/*!\brief vp8 decoder control function parameter type + * + * defines the data type for each of VP8 decoder control function requires + */ +VPX_CTRL_USE_TYPE(VP8_SET_REFERENCE, vpx_ref_frame_t *) +VPX_CTRL_USE_TYPE(VP8_COPY_REFERENCE, vpx_ref_frame_t *) +VPX_CTRL_USE_TYPE(VP8_SET_POSTPROC, vp8_postproc_cfg_t *) +VPX_CTRL_USE_TYPE(VP8_SET_DBG_COLOR_REF_FRAME, int) +VPX_CTRL_USE_TYPE(VP8_SET_DBG_COLOR_MB_MODES, int) +VPX_CTRL_USE_TYPE(VP8_SET_DBG_COLOR_B_MODES, int) +VPX_CTRL_USE_TYPE(VP8_SET_DBG_DISPLAY_MV, int) +VPX_CTRL_USE_TYPE(VP9_GET_REFERENCE, vp9_ref_frame_t *) + +/*! @} - end defgroup vp8 */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // VPX_VP8_H_ diff --git a/local_pod_repo/toxcore/osx/vpx.framework/Headers/vp8cx.h b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vp8cx.h new file mode 100644 index 0000000..60b588f --- /dev/null +++ b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vp8cx.h @@ -0,0 +1,699 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef VPX_VP8CX_H_ +#define VPX_VP8CX_H_ + +/*!\defgroup vp8_encoder WebM VP8/VP9 Encoder + * \ingroup vp8 + * + * @{ + */ +#include "./vp8.h" +#include "./vpx_encoder.h" + +/*!\file + * \brief Provides definitions for using VP8 or VP9 encoder algorithm within the + * vpx Codec Interface. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/*!\name Algorithm interface for VP8 + * + * This interface provides the capability to encode raw VP8 streams. + * @{ + */ +extern vpx_codec_iface_t vpx_codec_vp8_cx_algo; +extern vpx_codec_iface_t *vpx_codec_vp8_cx(void); +/*!@} - end algorithm interface member group*/ + +/*!\name Algorithm interface for VP9 + * + * This interface provides the capability to encode raw VP9 streams. + * @{ + */ +extern vpx_codec_iface_t vpx_codec_vp9_cx_algo; +extern vpx_codec_iface_t *vpx_codec_vp9_cx(void); +/*!@} - end algorithm interface member group*/ + + +/* + * Algorithm Flags + */ + +/*!\brief Don't reference the last frame + * + * When this flag is set, the encoder will not use the last frame as a + * predictor. When not set, the encoder will choose whether to use the + * last frame or not automatically. + */ +#define VP8_EFLAG_NO_REF_LAST (1<<16) + + +/*!\brief Don't reference the golden frame + * + * When this flag is set, the encoder will not use the golden frame as a + * predictor. When not set, the encoder will choose whether to use the + * golden frame or not automatically. + */ +#define VP8_EFLAG_NO_REF_GF (1<<17) + + +/*!\brief Don't reference the alternate reference frame + * + * When this flag is set, the encoder will not use the alt ref frame as a + * predictor. When not set, the encoder will choose whether to use the + * alt ref frame or not automatically. + */ +#define VP8_EFLAG_NO_REF_ARF (1<<21) + + +/*!\brief Don't update the last frame + * + * When this flag is set, the encoder will not update the last frame with + * the contents of the current frame. + */ +#define VP8_EFLAG_NO_UPD_LAST (1<<18) + + +/*!\brief Don't update the golden frame + * + * When this flag is set, the encoder will not update the golden frame with + * the contents of the current frame. + */ +#define VP8_EFLAG_NO_UPD_GF (1<<22) + + +/*!\brief Don't update the alternate reference frame + * + * When this flag is set, the encoder will not update the alt ref frame with + * the contents of the current frame. + */ +#define VP8_EFLAG_NO_UPD_ARF (1<<23) + + +/*!\brief Force golden frame update + * + * When this flag is set, the encoder copy the contents of the current frame + * to the golden frame buffer. + */ +#define VP8_EFLAG_FORCE_GF (1<<19) + + +/*!\brief Force alternate reference frame update + * + * When this flag is set, the encoder copy the contents of the current frame + * to the alternate reference frame buffer. + */ +#define VP8_EFLAG_FORCE_ARF (1<<24) + + +/*!\brief Disable entropy update + * + * When this flag is set, the encoder will not update its internal entropy + * model based on the entropy of this frame. + */ +#define VP8_EFLAG_NO_UPD_ENTROPY (1<<20) + + +/*!\brief VPx encoder control functions + * + * This set of macros define the control functions available for VPx + * encoder interface. + * + * \sa #vpx_codec_control + */ +enum vp8e_enc_control_id { + /*!\brief Codec control function to set mode of entropy update in encoder. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_UPD_ENTROPY = 5, + + /*!\brief Codec control function to set reference update mode in encoder. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_UPD_REFERENCE, + + /*!\brief Codec control function to set which reference frame encoder can use. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_USE_REFERENCE, + + /*!\brief Codec control function to pass an ROI map to encoder. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_ROI_MAP, + + /*!\brief Codec control function to pass an Active map to encoder. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_ACTIVEMAP, + + /*!\brief Codec control function to set encoder scaling mode. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_SCALEMODE = 11, + + /*!\brief Codec control function to set encoder internal speed settings. + * + * Changes in this value influences, among others, the encoder's selection + * of motion estimation methods. Values greater than 0 will increase encoder + * speed at the expense of quality. + * + * \note Valid range for VP8: -16..16 + * \note Valid range for VP9: -8..8 + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_CPUUSED = 13, + + /*!\brief Codec control function to enable automatic set and use alf frames. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_ENABLEAUTOALTREF, + + /*!\brief control function to set noise sensitivity + * + * 0: off, 1: OnYOnly, 2: OnYUV, + * 3: OnYUVAggressive, 4: Adaptive + * + * Supported in codecs: VP8 + */ + VP8E_SET_NOISE_SENSITIVITY, + + /*!\brief Codec control function to set sharpness. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_SHARPNESS, + + /*!\brief Codec control function to set the threshold for MBs treated static. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_STATIC_THRESHOLD, + + /*!\brief Codec control function to set the number of token partitions. + * + * Supported in codecs: VP8 + */ + VP8E_SET_TOKEN_PARTITIONS, + + /*!\brief Codec control function to get last quantizer chosen by the encoder. + * + * Return value uses internal quantizer scale defined by the codec. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_GET_LAST_QUANTIZER, + + /*!\brief Codec control function to get last quantizer chosen by the encoder. + * + * Return value uses the 0..63 scale as used by the rc_*_quantizer config + * parameters. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_GET_LAST_QUANTIZER_64, + + /*!\brief Codec control function to set the max no of frames to create arf. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_ARNR_MAXFRAMES, + + /*!\brief Codec control function to set the filter strength for the arf. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_ARNR_STRENGTH, + + /*!\deprecated control function to set the filter type to use for the arf. */ + VP8E_SET_ARNR_TYPE, + + /*!\brief Codec control function to set visual tuning. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_TUNING, + + /*!\brief Codec control function to set constrained quality level. + * + * \attention For this value to be used vpx_codec_enc_cfg_t::g_usage must be + * set to #VPX_CQ. + * \note Valid range: 0..63 + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_CQ_LEVEL, + + /*!\brief Codec control function to set Max data rate for Intra frames. + * + * This value controls additional clamping on the maximum size of a + * keyframe. It is expressed as a percentage of the average + * per-frame bitrate, with the special (and default) value 0 meaning + * unlimited, or no additional clamping beyond the codec's built-in + * algorithm. + * + * For example, to allocate no more than 4.5 frames worth of bitrate + * to a keyframe, set this to 450. + * + * Supported in codecs: VP8, VP9 + */ + VP8E_SET_MAX_INTRA_BITRATE_PCT, + + /*!\brief Codec control function to set reference and update frame flags. + * + * Supported in codecs: VP8 + */ + VP8E_SET_FRAME_FLAGS, + + /*!\brief Codec control function to set max data rate for Inter frames. + * + * This value controls additional clamping on the maximum size of an + * inter frame. It is expressed as a percentage of the average + * per-frame bitrate, with the special (and default) value 0 meaning + * unlimited, or no additional clamping beyond the codec's built-in + * algorithm. + * + * For example, to allow no more than 4.5 frames worth of bitrate + * to an inter frame, set this to 450. + * + * Supported in codecs: VP9 + */ + VP9E_SET_MAX_INTER_BITRATE_PCT, + + /*!\brief Boost percentage for Golden Frame in CBR mode. + * + * This value controls the amount of boost given to Golden Frame in + * CBR mode. It is expressed as a percentage of the average + * per-frame bitrate, with the special (and default) value 0 meaning + * the feature is off, i.e., no golden frame boost in CBR mode and + * average bitrate target is used. + * + * For example, to allow 100% more bits, i.e, 2X, in a golden frame + * than average frame, set this to 100. + * + * Supported in codecs: VP9 + */ + VP9E_SET_GF_CBR_BOOST_PCT, + + /*!\brief Codec control function to set the temporal layer id. + * + * For temporal scalability: this control allows the application to set the + * layer id for each frame to be encoded. Note that this control must be set + * for every frame prior to encoding. The usage of this control function + * supersedes the internal temporal pattern counter, which is now deprecated. + * + * Supported in codecs: VP8 + */ + VP8E_SET_TEMPORAL_LAYER_ID, + + /*!\brief Codec control function to set encoder screen content mode. + * + * Supported in codecs: VP8 + */ + VP8E_SET_SCREEN_CONTENT_MODE, + + /*!\brief Codec control function to set lossless encoding mode. + * + * VP9 can operate in lossless encoding mode, in which the bitstream + * produced will be able to decode and reconstruct a perfect copy of + * input source. This control function provides a mean to switch encoder + * into lossless coding mode(1) or normal coding mode(0) that may be lossy. + * 0 = lossy coding mode + * 1 = lossless coding mode + * + * By default, encoder operates in normal coding mode (maybe lossy). + * + * Supported in codecs: VP9 + */ + VP9E_SET_LOSSLESS, + + /*!\brief Codec control function to set number of tile columns. + * + * In encoding and decoding, VP9 allows an input image frame be partitioned + * into separated vertical tile columns, which can be encoded or decoded + * independently. This enables easy implementation of parallel encoding and + * decoding. This control requests the encoder to use column tiles in + * encoding an input frame, with number of tile columns (in Log2 unit) as + * the parameter: + * 0 = 1 tile column + * 1 = 2 tile columns + * 2 = 4 tile columns + * ..... + * n = 2**n tile columns + * The requested tile columns will be capped by encoder based on image size + * limitation (The minimum width of a tile column is 256 pixel, the maximum + * is 4096). + * + * By default, the value is 0, i.e. one single column tile for entire image. + * + * Supported in codecs: VP9 + */ + VP9E_SET_TILE_COLUMNS, + + /*!\brief Codec control function to set number of tile rows. + * + * In encoding and decoding, VP9 allows an input image frame be partitioned + * into separated horizontal tile rows. Tile rows are encoded or decoded + * sequentially. Even though encoding/decoding of later tile rows depends on + * earlier ones, this allows the encoder to output data packets for tile rows + * prior to completely processing all tile rows in a frame, thereby reducing + * the latency in processing between input and output. The parameter + * for this control describes the number of tile rows, which has a valid + * range [0, 2]: + * 0 = 1 tile row + * 1 = 2 tile rows + * 2 = 4 tile rows + * + * By default, the value is 0, i.e. one single row tile for entire image. + * + * Supported in codecs: VP9 + */ + VP9E_SET_TILE_ROWS, + + /*!\brief Codec control function to enable frame parallel decoding feature. + * + * VP9 has a bitstream feature to reduce decoding dependency between frames + * by turning off backward update of probability context used in encoding + * and decoding. This allows staged parallel processing of more than one + * video frames in the decoder. This control function provides a mean to + * turn this feature on or off for bitstreams produced by encoder. + * + * By default, this feature is off. + * + * Supported in codecs: VP9 + */ + VP9E_SET_FRAME_PARALLEL_DECODING, + + /*!\brief Codec control function to set adaptive quantization mode. + * + * VP9 has a segment based feature that allows encoder to adaptively change + * quantization parameter for each segment within a frame to improve the + * subjective quality. This control makes encoder operate in one of the + * several AQ_modes supported. + * + * By default, encoder operates with AQ_Mode 0(adaptive quantization off). + * + * Supported in codecs: VP9 + */ + VP9E_SET_AQ_MODE, + + /*!\brief Codec control function to enable/disable periodic Q boost. + * + * One VP9 encoder speed feature is to enable quality boost by lowering + * frame level Q periodically. This control function provides a mean to + * turn on/off this feature. + * 0 = off + * 1 = on + * + * By default, the encoder is allowed to use this feature for appropriate + * encoding modes. + * + * Supported in codecs: VP9 + */ + VP9E_SET_FRAME_PERIODIC_BOOST, + + /*!\brief Codec control function to set noise sensitivity. + * + * 0: off, 1: On(YOnly) + * + * Supported in codecs: VP9 + */ + VP9E_SET_NOISE_SENSITIVITY, + + /*!\brief Codec control function to turn on/off SVC in encoder. + * \note Return value is VPX_CODEC_INVALID_PARAM if the encoder does not + * support SVC in its current encoding mode + * 0: off, 1: on + * + * Supported in codecs: VP9 + */ + VP9E_SET_SVC, + +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) + /*!\brief Codec control function to set parameters for SVC. + * \note Parameters contain min_q, max_q, scaling factor for each of the + * SVC layers. + * + * Supported in codecs: VP9 + */ + VP9E_SET_SVC_PARAMETERS, +#endif + + /*!\brief Codec control function to set svc layer for spatial and temporal. + * \note Valid ranges: 0..#vpx_codec_enc_cfg::ss_number_layers for spatial + * layer and 0..#vpx_codec_enc_cfg::ts_number_layers for + * temporal layer. + * + * Supported in codecs: VP9 + */ + VP9E_SET_SVC_LAYER_ID, + + /*!\brief Codec control function to set content type. + * \note Valid parameter range: + * VP9E_CONTENT_DEFAULT = Regular video content (Default) + * VP9E_CONTENT_SCREEN = Screen capture content + * + * Supported in codecs: VP9 + */ + VP9E_SET_TUNE_CONTENT, + +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) + /*!\brief Codec control function to get svc layer ID. + * \note The layer ID returned is for the data packet from the registered + * callback function. + * + * Supported in codecs: VP9 + */ + VP9E_GET_SVC_LAYER_ID, + + /*!\brief Codec control function to register callback to get per layer packet. + * \note Parameter for this control function is a structure with a callback + * function and a pointer to private data used by the callback. + * + * Supported in codecs: VP9 + */ + VP9E_REGISTER_CX_CALLBACK, +#endif + + /*!\brief Codec control function to set color space info. + * \note Valid ranges: 0..7, default is "UNKNOWN". + * 0 = UNKNOWN, + * 1 = BT_601 + * 2 = BT_709 + * 3 = SMPTE_170 + * 4 = SMPTE_240 + * 5 = BT_2020 + * 6 = RESERVED + * 7 = SRGB + * + * Supported in codecs: VP9 + */ + VP9E_SET_COLOR_SPACE, +}; + +/*!\brief vpx 1-D scaling mode + * + * This set of constants define 1-D vpx scaling modes + */ +typedef enum vpx_scaling_mode_1d { + VP8E_NORMAL = 0, + VP8E_FOURFIVE = 1, + VP8E_THREEFIVE = 2, + VP8E_ONETWO = 3 +} VPX_SCALING_MODE; + + +/*!\brief vpx region of interest map + * + * These defines the data structures for the region of interest map + * + */ + +typedef struct vpx_roi_map { + /*! An id between 0 and 3 for each 16x16 region within a frame. */ + unsigned char *roi_map; + unsigned int rows; /**< Number of rows. */ + unsigned int cols; /**< Number of columns. */ + // TODO(paulwilkins): broken for VP9 which has 8 segments + // q and loop filter deltas for each segment + // (see MAX_MB_SEGMENTS) + int delta_q[4]; /**< Quantizer deltas. */ + int delta_lf[4]; /**< Loop filter deltas. */ + /*! Static breakout threshold for each segment. */ + unsigned int static_threshold[4]; +} vpx_roi_map_t; + +/*!\brief vpx active region map + * + * These defines the data structures for active region map + * + */ + + +typedef struct vpx_active_map { + unsigned char *active_map; /**< specify an on (1) or off (0) each 16x16 region within a frame */ + unsigned int rows; /**< number of rows */ + unsigned int cols; /**< number of cols */ +} vpx_active_map_t; + +/*!\brief vpx image scaling mode + * + * This defines the data structure for image scaling mode + * + */ +typedef struct vpx_scaling_mode { + VPX_SCALING_MODE h_scaling_mode; /**< horizontal scaling mode */ + VPX_SCALING_MODE v_scaling_mode; /**< vertical scaling mode */ +} vpx_scaling_mode_t; + +/*!\brief VP8 token partition mode + * + * This defines VP8 partitioning mode for compressed data, i.e., the number of + * sub-streams in the bitstream. Used for parallelized decoding. + * + */ + +typedef enum { + VP8_ONE_TOKENPARTITION = 0, + VP8_TWO_TOKENPARTITION = 1, + VP8_FOUR_TOKENPARTITION = 2, + VP8_EIGHT_TOKENPARTITION = 3 +} vp8e_token_partitions; + +/*!brief VP9 encoder content type */ +typedef enum { + VP9E_CONTENT_DEFAULT, + VP9E_CONTENT_SCREEN, + VP9E_CONTENT_INVALID +} vp9e_tune_content; + +/*!\brief VP8 model tuning parameters + * + * Changes the encoder to tune for certain types of input material. + * + */ +typedef enum { + VP8_TUNE_PSNR, + VP8_TUNE_SSIM +} vp8e_tuning; + +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) +/*!\brief vp9 svc layer parameters + * + * This defines the spatial and temporal layer id numbers for svc encoding. + * This is used with the #VP9E_SET_SVC_LAYER_ID control to set the spatial and + * temporal layer id for the current frame. + * + */ +typedef struct vpx_svc_layer_id { + int spatial_layer_id; /**< Spatial layer id number. */ + int temporal_layer_id; /**< Temporal layer id number. */ +} vpx_svc_layer_id_t; +#else +/*!\brief vp9 svc layer parameters + * + * This defines the temporal layer id numbers for svc encoding. + * This is used with the #VP9E_SET_SVC_LAYER_ID control to set the + * temporal layer id for the current frame. + * + */ +typedef struct vpx_svc_layer_id { + int temporal_layer_id; /**< Temporal layer id number. */ +} vpx_svc_layer_id_t; +#endif + +/*!\brief VP8 encoder control function parameter type + * + * Defines the data types that VP8E control functions take. Note that + * additional common controls are defined in vp8.h + * + */ + + +/* These controls have been deprecated in favor of the flags parameter to + * vpx_codec_encode(). See the definition of VP8_EFLAG_* above. + */ +VPX_CTRL_USE_TYPE_DEPRECATED(VP8E_UPD_ENTROPY, int) +VPX_CTRL_USE_TYPE_DEPRECATED(VP8E_UPD_REFERENCE, int) +VPX_CTRL_USE_TYPE_DEPRECATED(VP8E_USE_REFERENCE, int) + +VPX_CTRL_USE_TYPE(VP8E_SET_FRAME_FLAGS, int) +VPX_CTRL_USE_TYPE(VP8E_SET_TEMPORAL_LAYER_ID, int) +VPX_CTRL_USE_TYPE(VP8E_SET_ROI_MAP, vpx_roi_map_t *) +VPX_CTRL_USE_TYPE(VP8E_SET_ACTIVEMAP, vpx_active_map_t *) +VPX_CTRL_USE_TYPE(VP8E_SET_SCALEMODE, vpx_scaling_mode_t *) + +VPX_CTRL_USE_TYPE(VP9E_SET_SVC, int) +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) +VPX_CTRL_USE_TYPE(VP9E_SET_SVC_PARAMETERS, void *) +VPX_CTRL_USE_TYPE(VP9E_REGISTER_CX_CALLBACK, void *) +#endif +VPX_CTRL_USE_TYPE(VP9E_SET_SVC_LAYER_ID, vpx_svc_layer_id_t *) + +VPX_CTRL_USE_TYPE(VP8E_SET_CPUUSED, int) +VPX_CTRL_USE_TYPE(VP8E_SET_ENABLEAUTOALTREF, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_NOISE_SENSITIVITY, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_SHARPNESS, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_STATIC_THRESHOLD, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_TOKEN_PARTITIONS, int) /* vp8e_token_partitions */ + +VPX_CTRL_USE_TYPE(VP8E_SET_ARNR_MAXFRAMES, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_ARNR_STRENGTH, unsigned int) +VPX_CTRL_USE_TYPE_DEPRECATED(VP8E_SET_ARNR_TYPE, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_TUNING, int) /* vp8e_tuning */ +VPX_CTRL_USE_TYPE(VP8E_SET_CQ_LEVEL, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_TILE_COLUMNS, int) +VPX_CTRL_USE_TYPE(VP9E_SET_TILE_ROWS, int) + +VPX_CTRL_USE_TYPE(VP8E_GET_LAST_QUANTIZER, int *) +VPX_CTRL_USE_TYPE(VP8E_GET_LAST_QUANTIZER_64, int *) +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) +VPX_CTRL_USE_TYPE(VP9E_GET_SVC_LAYER_ID, vpx_svc_layer_id_t *) +#endif + +VPX_CTRL_USE_TYPE(VP8E_SET_MAX_INTRA_BITRATE_PCT, unsigned int) +VPX_CTRL_USE_TYPE(VP8E_SET_MAX_INTER_BITRATE_PCT, unsigned int) + +VPX_CTRL_USE_TYPE(VP8E_SET_SCREEN_CONTENT_MODE, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_GF_CBR_BOOST_PCT, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_LOSSLESS, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_FRAME_PARALLEL_DECODING, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_AQ_MODE, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_FRAME_PERIODIC_BOOST, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_NOISE_SENSITIVITY, unsigned int) + +VPX_CTRL_USE_TYPE(VP9E_SET_TUNE_CONTENT, int) /* vp9e_tune_content */ + +VPX_CTRL_USE_TYPE(VP9E_SET_COLOR_SPACE, int) +/*! @} - end defgroup vp8_encoder */ +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // VPX_VP8CX_H_ diff --git a/local_pod_repo/toxcore/osx/vpx.framework/Headers/vp8dx.h b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vp8dx.h new file mode 100644 index 0000000..83898bf --- /dev/null +++ b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vp8dx.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +/*!\defgroup vp8_decoder WebM VP8/VP9 Decoder + * \ingroup vp8 + * + * @{ + */ +/*!\file + * \brief Provides definitions for using VP8 or VP9 within the vpx Decoder + * interface. + */ +#ifndef VPX_VP8DX_H_ +#define VPX_VP8DX_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Include controls common to both the encoder and decoder */ +#include "./vp8.h" + +/*!\name Algorithm interface for VP8 + * + * This interface provides the capability to decode VP8 streams. + * @{ + */ +extern vpx_codec_iface_t vpx_codec_vp8_dx_algo; +extern vpx_codec_iface_t *vpx_codec_vp8_dx(void); +/*!@} - end algorithm interface member group*/ + +/*!\name Algorithm interface for VP9 + * + * This interface provides the capability to decode VP9 streams. + * @{ + */ +extern vpx_codec_iface_t vpx_codec_vp9_dx_algo; +extern vpx_codec_iface_t *vpx_codec_vp9_dx(void); +/*!@} - end algorithm interface member group*/ + + +/*!\enum vp8_dec_control_id + * \brief VP8 decoder control functions + * + * This set of macros define the control functions available for the VP8 + * decoder interface. + * + * \sa #vpx_codec_control + */ +enum vp8_dec_control_id { + /** control function to get info on which reference frames were updated + * by the last decode + */ + VP8D_GET_LAST_REF_UPDATES = VP8_DECODER_CTRL_ID_START, + + /** check if the indicated frame is corrupted */ + VP8D_GET_FRAME_CORRUPTED, + + /** control function to get info on which reference frames were used + * by the last decode + */ + VP8D_GET_LAST_REF_USED, + + /** decryption function to decrypt encoded buffer data immediately + * before decoding. Takes a vpx_decrypt_init, which contains + * a callback function and opaque context pointer. + */ + VPXD_SET_DECRYPTOR, + VP8D_SET_DECRYPTOR = VPXD_SET_DECRYPTOR, + + /** control function to get the dimensions that the current frame is decoded + * at. This may be different to the intended display size for the frame as + * specified in the wrapper or frame header (see VP9D_GET_DISPLAY_SIZE). */ + VP9D_GET_FRAME_SIZE, + + /** control function to get the current frame's intended display dimensions + * (as specified in the wrapper or frame header). This may be different to + * the decoded dimensions of this frame (see VP9D_GET_FRAME_SIZE). */ + VP9D_GET_DISPLAY_SIZE, + + /** control function to get the bit depth of the stream. */ + VP9D_GET_BIT_DEPTH, + + /** control function to set the byte alignment of the planes in the reference + * buffers. Valid values are power of 2, from 32 to 1024. A value of 0 sets + * legacy alignment. I.e. Y plane is aligned to 32 bytes, U plane directly + * follows Y plane, and V plane directly follows U plane. Default value is 0. + */ + VP9_SET_BYTE_ALIGNMENT, + + /** control function to invert the decoding order to from right to left. The + * function is used in a test to confirm the decoding independence of tile + * columns. The function may be used in application where this order + * of decoding is desired. + * + * TODO(yaowu): Rework the unit test that uses this control, and in a future + * release, this test-only control shall be removed. + */ + VP9_INVERT_TILE_DECODE_ORDER, + + VP8_DECODER_CTRL_ID_MAX +}; + +/** Decrypt n bytes of data from input -> output, using the decrypt_state + * passed in VPXD_SET_DECRYPTOR. + */ +typedef void (*vpx_decrypt_cb)(void *decrypt_state, const unsigned char *input, + unsigned char *output, int count); + +/*!\brief Structure to hold decryption state + * + * Defines a structure to hold the decryption state and access function. + */ +typedef struct vpx_decrypt_init { + /*! Decrypt callback. */ + vpx_decrypt_cb decrypt_cb; + + /*! Decryption state. */ + void *decrypt_state; +} vpx_decrypt_init; + +/*!\brief A deprecated alias for vpx_decrypt_init. + */ +typedef vpx_decrypt_init vp8_decrypt_init; + + +/*!\brief VP8 decoder control function parameter type + * + * Defines the data types that VP8D control functions take. Note that + * additional common controls are defined in vp8.h + * + */ + + +VPX_CTRL_USE_TYPE(VP8D_GET_LAST_REF_UPDATES, int *) +VPX_CTRL_USE_TYPE(VP8D_GET_FRAME_CORRUPTED, int *) +VPX_CTRL_USE_TYPE(VP8D_GET_LAST_REF_USED, int *) +VPX_CTRL_USE_TYPE(VPXD_SET_DECRYPTOR, vpx_decrypt_init *) +VPX_CTRL_USE_TYPE(VP8D_SET_DECRYPTOR, vpx_decrypt_init *) +VPX_CTRL_USE_TYPE(VP9D_GET_DISPLAY_SIZE, int *) +VPX_CTRL_USE_TYPE(VP9D_GET_BIT_DEPTH, unsigned int *) +VPX_CTRL_USE_TYPE(VP9D_GET_FRAME_SIZE, int *) +VPX_CTRL_USE_TYPE(VP9_INVERT_TILE_DECODE_ORDER, int) + +/*! @} - end defgroup vp8_decoder */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // VPX_VP8DX_H_ diff --git a/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_codec.h b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_codec.h new file mode 100644 index 0000000..b94e173 --- /dev/null +++ b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_codec.h @@ -0,0 +1,479 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +/*!\defgroup codec Common Algorithm Interface + * This abstraction allows applications to easily support multiple video + * formats with minimal code duplication. This section describes the interface + * common to all codecs (both encoders and decoders). + * @{ + */ + +/*!\file + * \brief Describes the codec algorithm interface to applications. + * + * This file describes the interface between an application and a + * video codec algorithm. + * + * An application instantiates a specific codec instance by using + * vpx_codec_init() and a pointer to the algorithm's interface structure: + *
+ *     my_app.c:
+ *       extern vpx_codec_iface_t my_codec;
+ *       {
+ *           vpx_codec_ctx_t algo;
+ *           res = vpx_codec_init(&algo, &my_codec);
+ *       }
+ *     
+ * + * Once initialized, the instance is manged using other functions from + * the vpx_codec_* family. + */ +#ifndef VPX_VPX_CODEC_H_ +#define VPX_VPX_CODEC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./vpx_integer.h" +#include "./vpx_image.h" + + /*!\brief Decorator indicating a function is deprecated */ +#ifndef DEPRECATED +#if defined(__GNUC__) && __GNUC__ +#define DEPRECATED __attribute__ ((deprecated)) +#elif defined(_MSC_VER) +#define DEPRECATED +#else +#define DEPRECATED +#endif +#endif /* DEPRECATED */ + +#ifndef DECLSPEC_DEPRECATED +#if defined(__GNUC__) && __GNUC__ +#define DECLSPEC_DEPRECATED /**< \copydoc #DEPRECATED */ +#elif defined(_MSC_VER) +#define DECLSPEC_DEPRECATED __declspec(deprecated) /**< \copydoc #DEPRECATED */ +#else +#define DECLSPEC_DEPRECATED /**< \copydoc #DEPRECATED */ +#endif +#endif /* DECLSPEC_DEPRECATED */ + + /*!\brief Decorator indicating a function is potentially unused */ +#ifdef UNUSED +#elif __GNUC__ +#define UNUSED __attribute__ ((unused)) +#else +#define UNUSED +#endif + + /*!\brief Current ABI version number + * + * \internal + * If this file is altered in any way that changes the ABI, this value + * must be bumped. Examples include, but are not limited to, changing + * types, removing or reassigning enums, adding/removing/rearranging + * fields to structures + */ +#define VPX_CODEC_ABI_VERSION (3 + VPX_IMAGE_ABI_VERSION) /**<\hideinitializer*/ + + /*!\brief Algorithm return codes */ + typedef enum { + /*!\brief Operation completed without error */ + VPX_CODEC_OK, + + /*!\brief Unspecified error */ + VPX_CODEC_ERROR, + + /*!\brief Memory operation failed */ + VPX_CODEC_MEM_ERROR, + + /*!\brief ABI version mismatch */ + VPX_CODEC_ABI_MISMATCH, + + /*!\brief Algorithm does not have required capability */ + VPX_CODEC_INCAPABLE, + + /*!\brief The given bitstream is not supported. + * + * The bitstream was unable to be parsed at the highest level. The decoder + * is unable to proceed. This error \ref SHOULD be treated as fatal to the + * stream. */ + VPX_CODEC_UNSUP_BITSTREAM, + + /*!\brief Encoded bitstream uses an unsupported feature + * + * The decoder does not implement a feature required by the encoder. This + * return code should only be used for features that prevent future + * pictures from being properly decoded. This error \ref MAY be treated as + * fatal to the stream or \ref MAY be treated as fatal to the current GOP. + */ + VPX_CODEC_UNSUP_FEATURE, + + /*!\brief The coded data for this stream is corrupt or incomplete + * + * There was a problem decoding the current frame. This return code + * should only be used for failures that prevent future pictures from + * being properly decoded. This error \ref MAY be treated as fatal to the + * stream or \ref MAY be treated as fatal to the current GOP. If decoding + * is continued for the current GOP, artifacts may be present. + */ + VPX_CODEC_CORRUPT_FRAME, + + /*!\brief An application-supplied parameter is not valid. + * + */ + VPX_CODEC_INVALID_PARAM, + + /*!\brief An iterator reached the end of list. + * + */ + VPX_CODEC_LIST_END + + } + vpx_codec_err_t; + + + /*! \brief Codec capabilities bitfield + * + * Each codec advertises the capabilities it supports as part of its + * ::vpx_codec_iface_t interface structure. Capabilities are extra interfaces + * or functionality, and are not required to be supported. + * + * The available flags are specified by VPX_CODEC_CAP_* defines. + */ + typedef long vpx_codec_caps_t; +#define VPX_CODEC_CAP_DECODER 0x1 /**< Is a decoder */ +#define VPX_CODEC_CAP_ENCODER 0x2 /**< Is an encoder */ + + + /*! \brief Initialization-time Feature Enabling + * + * Certain codec features must be known at initialization time, to allow for + * proper memory allocation. + * + * The available flags are specified by VPX_CODEC_USE_* defines. + */ + typedef long vpx_codec_flags_t; + + + /*!\brief Codec interface structure. + * + * Contains function pointers and other data private to the codec + * implementation. This structure is opaque to the application. + */ + typedef const struct vpx_codec_iface vpx_codec_iface_t; + + + /*!\brief Codec private data structure. + * + * Contains data private to the codec implementation. This structure is opaque + * to the application. + */ + typedef struct vpx_codec_priv vpx_codec_priv_t; + + + /*!\brief Iterator + * + * Opaque storage used for iterating over lists. + */ + typedef const void *vpx_codec_iter_t; + + + /*!\brief Codec context structure + * + * All codecs \ref MUST support this context structure fully. In general, + * this data should be considered private to the codec algorithm, and + * not be manipulated or examined by the calling application. Applications + * may reference the 'name' member to get a printable description of the + * algorithm. + */ + typedef struct vpx_codec_ctx { + const char *name; /**< Printable interface name */ + vpx_codec_iface_t *iface; /**< Interface pointers */ + vpx_codec_err_t err; /**< Last returned error */ + const char *err_detail; /**< Detailed info, if available */ + vpx_codec_flags_t init_flags; /**< Flags passed at init time */ + union { + /**< Decoder Configuration Pointer */ + const struct vpx_codec_dec_cfg *dec; + /**< Encoder Configuration Pointer */ + const struct vpx_codec_enc_cfg *enc; + const void *raw; + } config; /**< Configuration pointer aliasing union */ + vpx_codec_priv_t *priv; /**< Algorithm private storage */ + } vpx_codec_ctx_t; + + /*!\brief Bit depth for codec + * * + * This enumeration determines the bit depth of the codec. + */ + typedef enum vpx_bit_depth { + VPX_BITS_8 = 8, /**< 8 bits */ + VPX_BITS_10 = 10, /**< 10 bits */ + VPX_BITS_12 = 12, /**< 12 bits */ + } vpx_bit_depth_t; + + /* + * Library Version Number Interface + * + * For example, see the following sample return values: + * vpx_codec_version() (1<<16 | 2<<8 | 3) + * vpx_codec_version_str() "v1.2.3-rc1-16-gec6a1ba" + * vpx_codec_version_extra_str() "rc1-16-gec6a1ba" + */ + + /*!\brief Return the version information (as an integer) + * + * Returns a packed encoding of the library version number. This will only include + * the major.minor.patch component of the version number. Note that this encoded + * value should be accessed through the macros provided, as the encoding may change + * in the future. + * + */ + int vpx_codec_version(void); +#define VPX_VERSION_MAJOR(v) ((v>>16)&0xff) /**< extract major from packed version */ +#define VPX_VERSION_MINOR(v) ((v>>8)&0xff) /**< extract minor from packed version */ +#define VPX_VERSION_PATCH(v) ((v>>0)&0xff) /**< extract patch from packed version */ + + /*!\brief Return the version major number */ +#define vpx_codec_version_major() ((vpx_codec_version()>>16)&0xff) + + /*!\brief Return the version minor number */ +#define vpx_codec_version_minor() ((vpx_codec_version()>>8)&0xff) + + /*!\brief Return the version patch number */ +#define vpx_codec_version_patch() ((vpx_codec_version()>>0)&0xff) + + + /*!\brief Return the version information (as a string) + * + * Returns a printable string containing the full library version number. This may + * contain additional text following the three digit version number, as to indicate + * release candidates, prerelease versions, etc. + * + */ + const char *vpx_codec_version_str(void); + + + /*!\brief Return the version information (as a string) + * + * Returns a printable "extra string". This is the component of the string returned + * by vpx_codec_version_str() following the three digit version number. + * + */ + const char *vpx_codec_version_extra_str(void); + + + /*!\brief Return the build configuration + * + * Returns a printable string containing an encoded version of the build + * configuration. This may be useful to vpx support. + * + */ + const char *vpx_codec_build_config(void); + + + /*!\brief Return the name for a given interface + * + * Returns a human readable string for name of the given codec interface. + * + * \param[in] iface Interface pointer + * + */ + const char *vpx_codec_iface_name(vpx_codec_iface_t *iface); + + + /*!\brief Convert error number to printable string + * + * Returns a human readable string for the last error returned by the + * algorithm. The returned error will be one line and will not contain + * any newline characters. + * + * + * \param[in] err Error number. + * + */ + const char *vpx_codec_err_to_string(vpx_codec_err_t err); + + + /*!\brief Retrieve error synopsis for codec context + * + * Returns a human readable string for the last error returned by the + * algorithm. The returned error will be one line and will not contain + * any newline characters. + * + * + * \param[in] ctx Pointer to this instance's context. + * + */ + const char *vpx_codec_error(vpx_codec_ctx_t *ctx); + + + /*!\brief Retrieve detailed error information for codec context + * + * Returns a human readable string providing detailed information about + * the last error. + * + * \param[in] ctx Pointer to this instance's context. + * + * \retval NULL + * No detailed information is available. + */ + const char *vpx_codec_error_detail(vpx_codec_ctx_t *ctx); + + + /* REQUIRED FUNCTIONS + * + * The following functions are required to be implemented for all codecs. + * They represent the base case functionality expected of all codecs. + */ + + /*!\brief Destroy a codec instance + * + * Destroys a codec context, freeing any associated memory buffers. + * + * \param[in] ctx Pointer to this instance's context + * + * \retval #VPX_CODEC_OK + * The codec algorithm initialized. + * \retval #VPX_CODEC_MEM_ERROR + * Memory allocation failed. + */ + vpx_codec_err_t vpx_codec_destroy(vpx_codec_ctx_t *ctx); + + + /*!\brief Get the capabilities of an algorithm. + * + * Retrieves the capabilities bitfield from the algorithm's interface. + * + * \param[in] iface Pointer to the algorithm interface + * + */ + vpx_codec_caps_t vpx_codec_get_caps(vpx_codec_iface_t *iface); + + + /*!\brief Control algorithm + * + * This function is used to exchange algorithm specific data with the codec + * instance. This can be used to implement features specific to a particular + * algorithm. + * + * This wrapper function dispatches the request to the helper function + * associated with the given ctrl_id. It tries to call this function + * transparently, but will return #VPX_CODEC_ERROR if the request could not + * be dispatched. + * + * Note that this function should not be used directly. Call the + * #vpx_codec_control wrapper macro instead. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] ctrl_id Algorithm specific control identifier + * + * \retval #VPX_CODEC_OK + * The control request was processed. + * \retval #VPX_CODEC_ERROR + * The control request was not processed. + * \retval #VPX_CODEC_INVALID_PARAM + * The data was not valid. + */ + vpx_codec_err_t vpx_codec_control_(vpx_codec_ctx_t *ctx, + int ctrl_id, + ...); +#if defined(VPX_DISABLE_CTRL_TYPECHECKS) && VPX_DISABLE_CTRL_TYPECHECKS +# define vpx_codec_control(ctx,id,data) vpx_codec_control_(ctx,id,data) +# define VPX_CTRL_USE_TYPE(id, typ) +# define VPX_CTRL_USE_TYPE_DEPRECATED(id, typ) +# define VPX_CTRL_VOID(id, typ) + +#else + /*!\brief vpx_codec_control wrapper macro + * + * This macro allows for type safe conversions across the variadic parameter + * to vpx_codec_control_(). + * + * \internal + * It works by dispatching the call to the control function through a wrapper + * function named with the id parameter. + */ +# define vpx_codec_control(ctx,id,data) vpx_codec_control_##id(ctx,id,data)\ + /**<\hideinitializer*/ + + + /*!\brief vpx_codec_control type definition macro + * + * This macro allows for type safe conversions across the variadic parameter + * to vpx_codec_control_(). It defines the type of the argument for a given + * control identifier. + * + * \internal + * It defines a static function with + * the correctly typed arguments as a wrapper to the type-unsafe internal + * function. + */ +# define VPX_CTRL_USE_TYPE(id, typ) \ + static vpx_codec_err_t \ + vpx_codec_control_##id(vpx_codec_ctx_t*, int, typ) UNUSED;\ + \ + static vpx_codec_err_t \ + vpx_codec_control_##id(vpx_codec_ctx_t *ctx, int ctrl_id, typ data) {\ + return vpx_codec_control_(ctx, ctrl_id, data);\ + } /**<\hideinitializer*/ + + + /*!\brief vpx_codec_control deprecated type definition macro + * + * Like #VPX_CTRL_USE_TYPE, but indicates that the specified control is + * deprecated and should not be used. Consult the documentation for your + * codec for more information. + * + * \internal + * It defines a static function with the correctly typed arguments as a + * wrapper to the type-unsafe internal function. + */ +# define VPX_CTRL_USE_TYPE_DEPRECATED(id, typ) \ + DECLSPEC_DEPRECATED static vpx_codec_err_t \ + vpx_codec_control_##id(vpx_codec_ctx_t*, int, typ) DEPRECATED UNUSED;\ + \ + DECLSPEC_DEPRECATED static vpx_codec_err_t \ + vpx_codec_control_##id(vpx_codec_ctx_t *ctx, int ctrl_id, typ data) {\ + return vpx_codec_control_(ctx, ctrl_id, data);\ + } /**<\hideinitializer*/ + + + /*!\brief vpx_codec_control void type definition macro + * + * This macro allows for type safe conversions across the variadic parameter + * to vpx_codec_control_(). It indicates that a given control identifier takes + * no argument. + * + * \internal + * It defines a static function without a data argument as a wrapper to the + * type-unsafe internal function. + */ +# define VPX_CTRL_VOID(id) \ + static vpx_codec_err_t \ + vpx_codec_control_##id(vpx_codec_ctx_t*, int) UNUSED;\ + \ + static vpx_codec_err_t \ + vpx_codec_control_##id(vpx_codec_ctx_t *ctx, int ctrl_id) {\ + return vpx_codec_control_(ctx, ctrl_id);\ + } /**<\hideinitializer*/ + + +#endif + + /*!@} - end defgroup codec*/ +#ifdef __cplusplus +} +#endif +#endif // VPX_VPX_CODEC_H_ + diff --git a/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_config.h b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_config.h new file mode 100644 index 0000000..2d399b3 --- /dev/null +++ b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_config.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2015 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* GENERATED FILE: DO NOT EDIT! */ + +#ifndef VPX_FRAMEWORK_HEADERS_VPX_VPX_CONFIG_H_ +#define VPX_FRAMEWORK_HEADERS_VPX_VPX_CONFIG_H_ + +#if defined __x86_64__ +#define VPX_FRAMEWORK_TARGET "x86_64-darwin13-gcc" +#include "vpx/vpx/x86_64-darwin13-gcc/vpx_config.h" +#endif + +#endif // VPX_FRAMEWORK_HEADERS_VPX_VPX_CONFIG_H_ \ No newline at end of file diff --git a/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_decoder.h b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_decoder.h new file mode 100644 index 0000000..62fd919 --- /dev/null +++ b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_decoder.h @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef VPX_VPX_DECODER_H_ +#define VPX_VPX_DECODER_H_ + +/*!\defgroup decoder Decoder Algorithm Interface + * \ingroup codec + * This abstraction allows applications using this decoder to easily support + * multiple video formats with minimal code duplication. This section describes + * the interface common to all decoders. + * @{ + */ + +/*!\file + * \brief Describes the decoder algorithm interface to applications. + * + * This file describes the interface between an application and a + * video decoder algorithm. + * + */ +#ifdef __cplusplus +extern "C" { +#endif + +#include "./vpx_codec.h" +#include "./vpx_frame_buffer.h" + + /*!\brief Current ABI version number + * + * \internal + * If this file is altered in any way that changes the ABI, this value + * must be bumped. Examples include, but are not limited to, changing + * types, removing or reassigning enums, adding/removing/rearranging + * fields to structures + */ +#define VPX_DECODER_ABI_VERSION (3 + VPX_CODEC_ABI_VERSION) /**<\hideinitializer*/ + + /*! \brief Decoder capabilities bitfield + * + * Each decoder advertises the capabilities it supports as part of its + * ::vpx_codec_iface_t interface structure. Capabilities are extra interfaces + * or functionality, and are not required to be supported by a decoder. + * + * The available flags are specified by VPX_CODEC_CAP_* defines. + */ +#define VPX_CODEC_CAP_PUT_SLICE 0x10000 /**< Will issue put_slice callbacks */ +#define VPX_CODEC_CAP_PUT_FRAME 0x20000 /**< Will issue put_frame callbacks */ +#define VPX_CODEC_CAP_POSTPROC 0x40000 /**< Can postprocess decoded frame */ +#define VPX_CODEC_CAP_ERROR_CONCEALMENT 0x80000 /**< Can conceal errors due to + packet loss */ +#define VPX_CODEC_CAP_INPUT_FRAGMENTS 0x100000 /**< Can receive encoded frames + one fragment at a time */ + + /*! \brief Initialization-time Feature Enabling + * + * Certain codec features must be known at initialization time, to allow for + * proper memory allocation. + * + * The available flags are specified by VPX_CODEC_USE_* defines. + */ +#define VPX_CODEC_CAP_FRAME_THREADING 0x200000 /**< Can support frame-based + multi-threading */ +#define VPX_CODEC_CAP_EXTERNAL_FRAME_BUFFER 0x400000 /**< Can support external + frame buffers */ + +#define VPX_CODEC_USE_POSTPROC 0x10000 /**< Postprocess decoded frame */ +#define VPX_CODEC_USE_ERROR_CONCEALMENT 0x20000 /**< Conceal errors in decoded + frames */ +#define VPX_CODEC_USE_INPUT_FRAGMENTS 0x40000 /**< The input frame should be + passed to the decoder one + fragment at a time */ +#define VPX_CODEC_USE_FRAME_THREADING 0x80000 /**< Enable frame-based + multi-threading */ + + /*!\brief Stream properties + * + * This structure is used to query or set properties of the decoded + * stream. Algorithms may extend this structure with data specific + * to their bitstream by setting the sz member appropriately. + */ + typedef struct vpx_codec_stream_info { + unsigned int sz; /**< Size of this structure */ + unsigned int w; /**< Width (or 0 for unknown/default) */ + unsigned int h; /**< Height (or 0 for unknown/default) */ + unsigned int is_kf; /**< Current frame is a keyframe */ + } vpx_codec_stream_info_t; + + /* REQUIRED FUNCTIONS + * + * The following functions are required to be implemented for all decoders. + * They represent the base case functionality expected of all decoders. + */ + + + /*!\brief Initialization Configurations + * + * This structure is used to pass init time configuration options to the + * decoder. + */ + typedef struct vpx_codec_dec_cfg { + unsigned int threads; /**< Maximum number of threads to use, default 1 */ + unsigned int w; /**< Width */ + unsigned int h; /**< Height */ + } vpx_codec_dec_cfg_t; /**< alias for struct vpx_codec_dec_cfg */ + + + /*!\brief Initialize a decoder instance + * + * Initializes a decoder context using the given interface. Applications + * should call the vpx_codec_dec_init convenience macro instead of this + * function directly, to ensure that the ABI version number parameter + * is properly initialized. + * + * If the library was configured with --disable-multithread, this call + * is not thread safe and should be guarded with a lock if being used + * in a multithreaded context. + * + * \param[in] ctx Pointer to this instance's context. + * \param[in] iface Pointer to the algorithm interface to use. + * \param[in] cfg Configuration to use, if known. May be NULL. + * \param[in] flags Bitfield of VPX_CODEC_USE_* flags + * \param[in] ver ABI version number. Must be set to + * VPX_DECODER_ABI_VERSION + * \retval #VPX_CODEC_OK + * The decoder algorithm initialized. + * \retval #VPX_CODEC_MEM_ERROR + * Memory allocation failed. + */ + vpx_codec_err_t vpx_codec_dec_init_ver(vpx_codec_ctx_t *ctx, + vpx_codec_iface_t *iface, + const vpx_codec_dec_cfg_t *cfg, + vpx_codec_flags_t flags, + int ver); + + /*!\brief Convenience macro for vpx_codec_dec_init_ver() + * + * Ensures the ABI version parameter is properly set. + */ +#define vpx_codec_dec_init(ctx, iface, cfg, flags) \ + vpx_codec_dec_init_ver(ctx, iface, cfg, flags, VPX_DECODER_ABI_VERSION) + + + /*!\brief Parse stream info from a buffer + * + * Performs high level parsing of the bitstream. Construction of a decoder + * context is not necessary. Can be used to determine if the bitstream is + * of the proper format, and to extract information from the stream. + * + * \param[in] iface Pointer to the algorithm interface + * \param[in] data Pointer to a block of data to parse + * \param[in] data_sz Size of the data buffer + * \param[in,out] si Pointer to stream info to update. The size member + * \ref MUST be properly initialized, but \ref MAY be + * clobbered by the algorithm. This parameter \ref MAY + * be NULL. + * + * \retval #VPX_CODEC_OK + * Bitstream is parsable and stream information updated + */ + vpx_codec_err_t vpx_codec_peek_stream_info(vpx_codec_iface_t *iface, + const uint8_t *data, + unsigned int data_sz, + vpx_codec_stream_info_t *si); + + + /*!\brief Return information about the current stream. + * + * Returns information about the stream that has been parsed during decoding. + * + * \param[in] ctx Pointer to this instance's context + * \param[in,out] si Pointer to stream info to update. The size member + * \ref MUST be properly initialized, but \ref MAY be + * clobbered by the algorithm. This parameter \ref MAY + * be NULL. + * + * \retval #VPX_CODEC_OK + * Bitstream is parsable and stream information updated + */ + vpx_codec_err_t vpx_codec_get_stream_info(vpx_codec_ctx_t *ctx, + vpx_codec_stream_info_t *si); + + + /*!\brief Decode data + * + * Processes a buffer of coded data. If the processing results in a new + * decoded frame becoming available, PUT_SLICE and PUT_FRAME events may be + * generated, as appropriate. Encoded data \ref MUST be passed in DTS (decode + * time stamp) order. Frames produced will always be in PTS (presentation + * time stamp) order. + * If the decoder is configured with VPX_CODEC_USE_INPUT_FRAGMENTS enabled, + * data and data_sz can contain a fragment of the encoded frame. Fragment + * \#n must contain at least partition \#n, but can also contain subsequent + * partitions (\#n+1 - \#n+i), and if so, fragments \#n+1, .., \#n+i must + * be empty. When no more data is available, this function should be called + * with NULL as data and 0 as data_sz. The memory passed to this function + * must be available until the frame has been decoded. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] data Pointer to this block of new coded data. If + * NULL, a VPX_CODEC_CB_PUT_FRAME event is posted + * for the previously decoded frame. + * \param[in] data_sz Size of the coded data, in bytes. + * \param[in] user_priv Application specific data to associate with + * this frame. + * \param[in] deadline Soft deadline the decoder should attempt to meet, + * in us. Set to zero for unlimited. + * + * \return Returns #VPX_CODEC_OK if the coded data was processed completely + * and future pictures can be decoded without error. Otherwise, + * see the descriptions of the other error codes in ::vpx_codec_err_t + * for recoverability capabilities. + */ + vpx_codec_err_t vpx_codec_decode(vpx_codec_ctx_t *ctx, + const uint8_t *data, + unsigned int data_sz, + void *user_priv, + long deadline); + + + /*!\brief Decoded frames iterator + * + * Iterates over a list of the frames available for display. The iterator + * storage should be initialized to NULL to start the iteration. Iteration is + * complete when this function returns NULL. + * + * The list of available frames becomes valid upon completion of the + * vpx_codec_decode call, and remains valid until the next call to vpx_codec_decode. + * + * \param[in] ctx Pointer to this instance's context + * \param[in,out] iter Iterator storage, initialized to NULL + * + * \return Returns a pointer to an image, if one is ready for display. Frames + * produced will always be in PTS (presentation time stamp) order. + */ + vpx_image_t *vpx_codec_get_frame(vpx_codec_ctx_t *ctx, + vpx_codec_iter_t *iter); + + + /*!\defgroup cap_put_frame Frame-Based Decoding Functions + * + * The following functions are required to be implemented for all decoders + * that advertise the VPX_CODEC_CAP_PUT_FRAME capability. Calling these functions + * for codecs that don't advertise this capability will result in an error + * code being returned, usually VPX_CODEC_ERROR + * @{ + */ + + /*!\brief put frame callback prototype + * + * This callback is invoked by the decoder to notify the application of + * the availability of decoded image data. + */ + typedef void (*vpx_codec_put_frame_cb_fn_t)(void *user_priv, + const vpx_image_t *img); + + + /*!\brief Register for notification of frame completion. + * + * Registers a given function to be called when a decoded frame is + * available. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] cb Pointer to the callback function + * \param[in] user_priv User's private data + * + * \retval #VPX_CODEC_OK + * Callback successfully registered. + * \retval #VPX_CODEC_ERROR + * Decoder context not initialized, or algorithm not capable of + * posting slice completion. + */ + vpx_codec_err_t vpx_codec_register_put_frame_cb(vpx_codec_ctx_t *ctx, + vpx_codec_put_frame_cb_fn_t cb, + void *user_priv); + + + /*!@} - end defgroup cap_put_frame */ + + /*!\defgroup cap_put_slice Slice-Based Decoding Functions + * + * The following functions are required to be implemented for all decoders + * that advertise the VPX_CODEC_CAP_PUT_SLICE capability. Calling these functions + * for codecs that don't advertise this capability will result in an error + * code being returned, usually VPX_CODEC_ERROR + * @{ + */ + + /*!\brief put slice callback prototype + * + * This callback is invoked by the decoder to notify the application of + * the availability of partially decoded image data. The + */ + typedef void (*vpx_codec_put_slice_cb_fn_t)(void *user_priv, + const vpx_image_t *img, + const vpx_image_rect_t *valid, + const vpx_image_rect_t *update); + + + /*!\brief Register for notification of slice completion. + * + * Registers a given function to be called when a decoded slice is + * available. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] cb Pointer to the callback function + * \param[in] user_priv User's private data + * + * \retval #VPX_CODEC_OK + * Callback successfully registered. + * \retval #VPX_CODEC_ERROR + * Decoder context not initialized, or algorithm not capable of + * posting slice completion. + */ + vpx_codec_err_t vpx_codec_register_put_slice_cb(vpx_codec_ctx_t *ctx, + vpx_codec_put_slice_cb_fn_t cb, + void *user_priv); + + + /*!@} - end defgroup cap_put_slice*/ + + /*!\defgroup cap_external_frame_buffer External Frame Buffer Functions + * + * The following section is required to be implemented for all decoders + * that advertise the VPX_CODEC_CAP_EXTERNAL_FRAME_BUFFER capability. + * Calling this function for codecs that don't advertise this capability + * will result in an error code being returned, usually VPX_CODEC_ERROR. + * + * \note + * Currently this only works with VP9. + * @{ + */ + + /*!\brief Pass in external frame buffers for the decoder to use. + * + * Registers functions to be called when libvpx needs a frame buffer + * to decode the current frame and a function to be called when libvpx does + * not internally reference the frame buffer. This set function must + * be called before the first call to decode or libvpx will assume the + * default behavior of allocating frame buffers internally. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] cb_get Pointer to the get callback function + * \param[in] cb_release Pointer to the release callback function + * \param[in] cb_priv Callback's private data + * + * \retval #VPX_CODEC_OK + * External frame buffers will be used by libvpx. + * \retval #VPX_CODEC_INVALID_PARAM + * One or more of the callbacks were NULL. + * \retval #VPX_CODEC_ERROR + * Decoder context not initialized, or algorithm not capable of + * using external frame buffers. + * + * \note + * When decoding VP9, the application may be required to pass in at least + * #VP9_MAXIMUM_REF_BUFFERS + #VPX_MAXIMUM_WORK_BUFFERS external frame + * buffers. + */ + vpx_codec_err_t vpx_codec_set_frame_buffer_functions( + vpx_codec_ctx_t *ctx, + vpx_get_frame_buffer_cb_fn_t cb_get, + vpx_release_frame_buffer_cb_fn_t cb_release, void *cb_priv); + + /*!@} - end defgroup cap_external_frame_buffer */ + + /*!@} - end defgroup decoder*/ +#ifdef __cplusplus +} +#endif +#endif // VPX_VPX_DECODER_H_ + diff --git a/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_encoder.h b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_encoder.h new file mode 100644 index 0000000..bf75584 --- /dev/null +++ b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_encoder.h @@ -0,0 +1,1023 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef VPX_VPX_ENCODER_H_ +#define VPX_VPX_ENCODER_H_ + +/*!\defgroup encoder Encoder Algorithm Interface + * \ingroup codec + * This abstraction allows applications using this encoder to easily support + * multiple video formats with minimal code duplication. This section describes + * the interface common to all encoders. + * @{ + */ + +/*!\file + * \brief Describes the encoder algorithm interface to applications. + * + * This file describes the interface between an application and a + * video encoder algorithm. + * + */ +#ifdef __cplusplus +extern "C" { +#endif + +#include "./vpx_codec.h" + + /*! Temporal Scalability: Maximum length of the sequence defining frame + * layer membership + */ +#define VPX_TS_MAX_PERIODICITY 16 + + /*! Temporal Scalability: Maximum number of coding layers */ +#define VPX_TS_MAX_LAYERS 5 + + /*!\deprecated Use #VPX_TS_MAX_PERIODICITY instead. */ +#define MAX_PERIODICITY VPX_TS_MAX_PERIODICITY + + /*!\deprecated Use #VPX_TS_MAX_LAYERS instead. */ +#define MAX_LAYERS VPX_TS_MAX_LAYERS + +/*! Spatial Scalability: Maximum number of coding layers */ +#define VPX_SS_MAX_LAYERS 5 + +/*! Spatial Scalability: Default number of coding layers */ +#define VPX_SS_DEFAULT_LAYERS 1 + + /*!\brief Current ABI version number + * + * \internal + * If this file is altered in any way that changes the ABI, this value + * must be bumped. Examples include, but are not limited to, changing + * types, removing or reassigning enums, adding/removing/rearranging + * fields to structures + */ +#define VPX_ENCODER_ABI_VERSION (4 + VPX_CODEC_ABI_VERSION) /**<\hideinitializer*/ + + + /*! \brief Encoder capabilities bitfield + * + * Each encoder advertises the capabilities it supports as part of its + * ::vpx_codec_iface_t interface structure. Capabilities are extra + * interfaces or functionality, and are not required to be supported + * by an encoder. + * + * The available flags are specified by VPX_CODEC_CAP_* defines. + */ +#define VPX_CODEC_CAP_PSNR 0x10000 /**< Can issue PSNR packets */ + + /*! Can output one partition at a time. Each partition is returned in its + * own VPX_CODEC_CX_FRAME_PKT, with the FRAME_IS_FRAGMENT flag set for + * every partition but the last. In this mode all frames are always + * returned partition by partition. + */ +#define VPX_CODEC_CAP_OUTPUT_PARTITION 0x20000 + +/*! Can support input images at greater than 8 bitdepth. + */ +#define VPX_CODEC_CAP_HIGHBITDEPTH 0x40000 + + /*! \brief Initialization-time Feature Enabling + * + * Certain codec features must be known at initialization time, to allow + * for proper memory allocation. + * + * The available flags are specified by VPX_CODEC_USE_* defines. + */ +#define VPX_CODEC_USE_PSNR 0x10000 /**< Calculate PSNR on each frame */ +#define VPX_CODEC_USE_OUTPUT_PARTITION 0x20000 /**< Make the encoder output one + partition at a time. */ +#define VPX_CODEC_USE_HIGHBITDEPTH 0x40000 /**< Use high bitdepth */ + + + /*!\brief Generic fixed size buffer structure + * + * This structure is able to hold a reference to any fixed size buffer. + */ + typedef struct vpx_fixed_buf { + void *buf; /**< Pointer to the data */ + size_t sz; /**< Length of the buffer, in chars */ + } vpx_fixed_buf_t; /**< alias for struct vpx_fixed_buf */ + + + /*!\brief Time Stamp Type + * + * An integer, which when multiplied by the stream's time base, provides + * the absolute time of a sample. + */ + typedef int64_t vpx_codec_pts_t; + + + /*!\brief Compressed Frame Flags + * + * This type represents a bitfield containing information about a compressed + * frame that may be useful to an application. The most significant 16 bits + * can be used by an algorithm to provide additional detail, for example to + * support frame types that are codec specific (MPEG-1 D-frames for example) + */ + typedef uint32_t vpx_codec_frame_flags_t; +#define VPX_FRAME_IS_KEY 0x1 /**< frame is the start of a GOP */ +#define VPX_FRAME_IS_DROPPABLE 0x2 /**< frame can be dropped without affecting + the stream (no future frame depends on + this one) */ +#define VPX_FRAME_IS_INVISIBLE 0x4 /**< frame should be decoded but will not + be shown */ +#define VPX_FRAME_IS_FRAGMENT 0x8 /**< this is a fragment of the encoded + frame */ + + /*!\brief Error Resilient flags + * + * These flags define which error resilient features to enable in the + * encoder. The flags are specified through the + * vpx_codec_enc_cfg::g_error_resilient variable. + */ + typedef uint32_t vpx_codec_er_flags_t; +#define VPX_ERROR_RESILIENT_DEFAULT 0x1 /**< Improve resiliency against + losses of whole frames */ +#define VPX_ERROR_RESILIENT_PARTITIONS 0x2 /**< The frame partitions are + independently decodable by the + bool decoder, meaning that + partitions can be decoded even + though earlier partitions have + been lost. Note that intra + predicition is still done over + the partition boundary. */ + + /*!\brief Encoder output packet variants + * + * This enumeration lists the different kinds of data packets that can be + * returned by calls to vpx_codec_get_cx_data(). Algorithms \ref MAY + * extend this list to provide additional functionality. + */ + enum vpx_codec_cx_pkt_kind { + VPX_CODEC_CX_FRAME_PKT, /**< Compressed video frame */ + VPX_CODEC_STATS_PKT, /**< Two-pass statistics for this frame */ + VPX_CODEC_FPMB_STATS_PKT, /**< first pass mb statistics for this frame */ + VPX_CODEC_PSNR_PKT, /**< PSNR statistics for this frame */ + // Spatial SVC is still experimental and may be removed before the next ABI + // bump. +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) + VPX_CODEC_SPATIAL_SVC_LAYER_SIZES, /**< Sizes for each layer in this frame*/ + VPX_CODEC_SPATIAL_SVC_LAYER_PSNR, /**< PSNR for each layer in this frame*/ +#endif + VPX_CODEC_CUSTOM_PKT = 256 /**< Algorithm extensions */ + }; + + + /*!\brief Encoder output packet + * + * This structure contains the different kinds of output data the encoder + * may produce while compressing a frame. + */ + typedef struct vpx_codec_cx_pkt { + enum vpx_codec_cx_pkt_kind kind; /**< packet variant */ + union { + struct { + void *buf; /**< compressed data buffer */ + size_t sz; /**< length of compressed data */ + vpx_codec_pts_t pts; /**< time stamp to show frame + (in timebase units) */ + unsigned long duration; /**< duration to show frame + (in timebase units) */ + vpx_codec_frame_flags_t flags; /**< flags for this frame */ + int partition_id; /**< the partition id + defines the decoding order + of the partitions. Only + applicable when "output partition" + mode is enabled. First partition + has id 0.*/ + + } frame; /**< data for compressed frame packet */ + vpx_fixed_buf_t twopass_stats; /**< data for two-pass packet */ + vpx_fixed_buf_t firstpass_mb_stats; /**< first pass mb packet */ + struct vpx_psnr_pkt { + unsigned int samples[4]; /**< Number of samples, total/y/u/v */ + uint64_t sse[4]; /**< sum squared error, total/y/u/v */ + double psnr[4]; /**< PSNR, total/y/u/v */ + } psnr; /**< data for PSNR packet */ + vpx_fixed_buf_t raw; /**< data for arbitrary packets */ + // Spatial SVC is still experimental and may be removed before the next + // ABI bump. +#if VPX_ENCODER_ABI_VERSION > (4 + VPX_CODEC_ABI_VERSION) + size_t layer_sizes[VPX_SS_MAX_LAYERS]; + struct vpx_psnr_pkt layer_psnr[VPX_SS_MAX_LAYERS]; +#endif + + /* This packet size is fixed to allow codecs to extend this + * interface without having to manage storage for raw packets, + * i.e., if it's smaller than 128 bytes, you can store in the + * packet list directly. + */ + char pad[128 - sizeof(enum vpx_codec_cx_pkt_kind)]; /**< fixed sz */ + } data; /**< packet data */ + } vpx_codec_cx_pkt_t; /**< alias for struct vpx_codec_cx_pkt */ + + + /*!\brief Encoder return output buffer callback + * + * This callback function, when registered, returns with packets when each + * spatial layer is encoded. + */ + // putting the definitions here for now. (agrange: find if there + // is a better place for this) + typedef void (* vpx_codec_enc_output_cx_pkt_cb_fn_t)(vpx_codec_cx_pkt_t *pkt, + void *user_data); + + /*!\brief Callback function pointer / user data pair storage */ + typedef struct vpx_codec_enc_output_cx_cb_pair { + vpx_codec_enc_output_cx_pkt_cb_fn_t output_cx_pkt; /**< Callback function */ + void *user_priv; /**< Pointer to private data */ + } vpx_codec_priv_output_cx_pkt_cb_pair_t; + + /*!\brief Rational Number + * + * This structure holds a fractional value. + */ + typedef struct vpx_rational { + int num; /**< fraction numerator */ + int den; /**< fraction denominator */ + } vpx_rational_t; /**< alias for struct vpx_rational */ + + + /*!\brief Multi-pass Encoding Pass */ + enum vpx_enc_pass { + VPX_RC_ONE_PASS, /**< Single pass mode */ + VPX_RC_FIRST_PASS, /**< First pass of multi-pass mode */ + VPX_RC_LAST_PASS /**< Final pass of multi-pass mode */ + }; + + + /*!\brief Rate control mode */ + enum vpx_rc_mode { + VPX_VBR, /**< Variable Bit Rate (VBR) mode */ + VPX_CBR, /**< Constant Bit Rate (CBR) mode */ + VPX_CQ, /**< Constrained Quality (CQ) mode */ + VPX_Q, /**< Constant Quality (Q) mode */ + }; + + + /*!\brief Keyframe placement mode. + * + * This enumeration determines whether keyframes are placed automatically by + * the encoder or whether this behavior is disabled. Older releases of this + * SDK were implemented such that VPX_KF_FIXED meant keyframes were disabled. + * This name is confusing for this behavior, so the new symbols to be used + * are VPX_KF_AUTO and VPX_KF_DISABLED. + */ + enum vpx_kf_mode { + VPX_KF_FIXED, /**< deprecated, implies VPX_KF_DISABLED */ + VPX_KF_AUTO, /**< Encoder determines optimal placement automatically */ + VPX_KF_DISABLED = 0 /**< Encoder does not place keyframes. */ + }; + + + /*!\brief Encoded Frame Flags + * + * This type indicates a bitfield to be passed to vpx_codec_encode(), defining + * per-frame boolean values. By convention, bits common to all codecs will be + * named VPX_EFLAG_*, and bits specific to an algorithm will be named + * /algo/_eflag_*. The lower order 16 bits are reserved for common use. + */ + typedef long vpx_enc_frame_flags_t; +#define VPX_EFLAG_FORCE_KF (1<<0) /**< Force this frame to be a keyframe */ + + + /*!\brief Encoder configuration structure + * + * This structure contains the encoder settings that have common representations + * across all codecs. This doesn't imply that all codecs support all features, + * however. + */ + typedef struct vpx_codec_enc_cfg { + /* + * generic settings (g) + */ + + /*!\brief Algorithm specific "usage" value + * + * Algorithms may define multiple values for usage, which may convey the + * intent of how the application intends to use the stream. If this value + * is non-zero, consult the documentation for the codec to determine its + * meaning. + */ + unsigned int g_usage; + + + /*!\brief Maximum number of threads to use + * + * For multi-threaded implementations, use no more than this number of + * threads. The codec may use fewer threads than allowed. The value + * 0 is equivalent to the value 1. + */ + unsigned int g_threads; + + + /*!\brief Bitstream profile to use + * + * Some codecs support a notion of multiple bitstream profiles. Typically + * this maps to a set of features that are turned on or off. Often the + * profile to use is determined by the features of the intended decoder. + * Consult the documentation for the codec to determine the valid values + * for this parameter, or set to zero for a sane default. + */ + unsigned int g_profile; /**< profile of bitstream to use */ + + + + /*!\brief Width of the frame + * + * This value identifies the presentation resolution of the frame, + * in pixels. Note that the frames passed as input to the encoder must + * have this resolution. Frames will be presented by the decoder in this + * resolution, independent of any spatial resampling the encoder may do. + */ + unsigned int g_w; + + + /*!\brief Height of the frame + * + * This value identifies the presentation resolution of the frame, + * in pixels. Note that the frames passed as input to the encoder must + * have this resolution. Frames will be presented by the decoder in this + * resolution, independent of any spatial resampling the encoder may do. + */ + unsigned int g_h; + + /*!\brief Bit-depth of the codec + * + * This value identifies the bit_depth of the codec, + * Only certain bit-depths are supported as identified in the + * vpx_bit_depth_t enum. + */ + vpx_bit_depth_t g_bit_depth; + + /*!\brief Bit-depth of the input frames + * + * This value identifies the bit_depth of the input frames in bits. + * Note that the frames passed as input to the encoder must have + * this bit-depth. + */ + unsigned int g_input_bit_depth; + + /*!\brief Stream timebase units + * + * Indicates the smallest interval of time, in seconds, used by the stream. + * For fixed frame rate material, or variable frame rate material where + * frames are timed at a multiple of a given clock (ex: video capture), + * the \ref RECOMMENDED method is to set the timebase to the reciprocal + * of the frame rate (ex: 1001/30000 for 29.970 Hz NTSC). This allows the + * pts to correspond to the frame number, which can be handy. For + * re-encoding video from containers with absolute time timestamps, the + * \ref RECOMMENDED method is to set the timebase to that of the parent + * container or multimedia framework (ex: 1/1000 for ms, as in FLV). + */ + struct vpx_rational g_timebase; + + + /*!\brief Enable error resilient modes. + * + * The error resilient bitfield indicates to the encoder which features + * it should enable to take measures for streaming over lossy or noisy + * links. + */ + vpx_codec_er_flags_t g_error_resilient; + + + /*!\brief Multi-pass Encoding Mode + * + * This value should be set to the current phase for multi-pass encoding. + * For single pass, set to #VPX_RC_ONE_PASS. + */ + enum vpx_enc_pass g_pass; + + + /*!\brief Allow lagged encoding + * + * If set, this value allows the encoder to consume a number of input + * frames before producing output frames. This allows the encoder to + * base decisions for the current frame on future frames. This does + * increase the latency of the encoding pipeline, so it is not appropriate + * in all situations (ex: realtime encoding). + * + * Note that this is a maximum value -- the encoder may produce frames + * sooner than the given limit. Set this value to 0 to disable this + * feature. + */ + unsigned int g_lag_in_frames; + + + /* + * rate control settings (rc) + */ + + /*!\brief Temporal resampling configuration, if supported by the codec. + * + * Temporal resampling allows the codec to "drop" frames as a strategy to + * meet its target data rate. This can cause temporal discontinuities in + * the encoded video, which may appear as stuttering during playback. This + * trade-off is often acceptable, but for many applications is not. It can + * be disabled in these cases. + * + * Note that not all codecs support this feature. All vpx VPx codecs do. + * For other codecs, consult the documentation for that algorithm. + * + * This threshold is described as a percentage of the target data buffer. + * When the data buffer falls below this percentage of fullness, a + * dropped frame is indicated. Set the threshold to zero (0) to disable + * this feature. + */ + unsigned int rc_dropframe_thresh; + + + /*!\brief Enable/disable spatial resampling, if supported by the codec. + * + * Spatial resampling allows the codec to compress a lower resolution + * version of the frame, which is then upscaled by the encoder to the + * correct presentation resolution. This increases visual quality at + * low data rates, at the expense of CPU time on the encoder/decoder. + */ + unsigned int rc_resize_allowed; + + /*!\brief Internal coded frame width. + * + * If spatial resampling is enabled this specifies the width of the + * encoded frame. + */ + unsigned int rc_scaled_width; + + /*!\brief Internal coded frame height. + * + * If spatial resampling is enabled this specifies the height of the + * encoded frame. + */ + unsigned int rc_scaled_height; + + /*!\brief Spatial resampling up watermark. + * + * This threshold is described as a percentage of the target data buffer. + * When the data buffer rises above this percentage of fullness, the + * encoder will step up to a higher resolution version of the frame. + */ + unsigned int rc_resize_up_thresh; + + + /*!\brief Spatial resampling down watermark. + * + * This threshold is described as a percentage of the target data buffer. + * When the data buffer falls below this percentage of fullness, the + * encoder will step down to a lower resolution version of the frame. + */ + unsigned int rc_resize_down_thresh; + + + /*!\brief Rate control algorithm to use. + * + * Indicates whether the end usage of this stream is to be streamed over + * a bandwidth constrained link, indicating that Constant Bit Rate (CBR) + * mode should be used, or whether it will be played back on a high + * bandwidth link, as from a local disk, where higher variations in + * bitrate are acceptable. + */ + enum vpx_rc_mode rc_end_usage; + + + /*!\brief Two-pass stats buffer. + * + * A buffer containing all of the stats packets produced in the first + * pass, concatenated. + */ + vpx_fixed_buf_t rc_twopass_stats_in; + + /*!\brief first pass mb stats buffer. + * + * A buffer containing all of the first pass mb stats packets produced + * in the first pass, concatenated. + */ + vpx_fixed_buf_t rc_firstpass_mb_stats_in; + + /*!\brief Target data rate + * + * Target bandwidth to use for this stream, in kilobits per second. + */ + unsigned int rc_target_bitrate; + + + /* + * quantizer settings + */ + + + /*!\brief Minimum (Best Quality) Quantizer + * + * The quantizer is the most direct control over the quality of the + * encoded image. The range of valid values for the quantizer is codec + * specific. Consult the documentation for the codec to determine the + * values to use. To determine the range programmatically, call + * vpx_codec_enc_config_default() with a usage value of 0. + */ + unsigned int rc_min_quantizer; + + + /*!\brief Maximum (Worst Quality) Quantizer + * + * The quantizer is the most direct control over the quality of the + * encoded image. The range of valid values for the quantizer is codec + * specific. Consult the documentation for the codec to determine the + * values to use. To determine the range programmatically, call + * vpx_codec_enc_config_default() with a usage value of 0. + */ + unsigned int rc_max_quantizer; + + + /* + * bitrate tolerance + */ + + + /*!\brief Rate control adaptation undershoot control + * + * This value, expressed as a percentage of the target bitrate, + * controls the maximum allowed adaptation speed of the codec. + * This factor controls the maximum amount of bits that can + * be subtracted from the target bitrate in order to compensate + * for prior overshoot. + * + * Valid values in the range 0-1000. + */ + unsigned int rc_undershoot_pct; + + + /*!\brief Rate control adaptation overshoot control + * + * This value, expressed as a percentage of the target bitrate, + * controls the maximum allowed adaptation speed of the codec. + * This factor controls the maximum amount of bits that can + * be added to the target bitrate in order to compensate for + * prior undershoot. + * + * Valid values in the range 0-1000. + */ + unsigned int rc_overshoot_pct; + + + /* + * decoder buffer model parameters + */ + + + /*!\brief Decoder Buffer Size + * + * This value indicates the amount of data that may be buffered by the + * decoding application. Note that this value is expressed in units of + * time (milliseconds). For example, a value of 5000 indicates that the + * client will buffer (at least) 5000ms worth of encoded data. Use the + * target bitrate (#rc_target_bitrate) to convert to bits/bytes, if + * necessary. + */ + unsigned int rc_buf_sz; + + + /*!\brief Decoder Buffer Initial Size + * + * This value indicates the amount of data that will be buffered by the + * decoding application prior to beginning playback. This value is + * expressed in units of time (milliseconds). Use the target bitrate + * (#rc_target_bitrate) to convert to bits/bytes, if necessary. + */ + unsigned int rc_buf_initial_sz; + + + /*!\brief Decoder Buffer Optimal Size + * + * This value indicates the amount of data that the encoder should try + * to maintain in the decoder's buffer. This value is expressed in units + * of time (milliseconds). Use the target bitrate (#rc_target_bitrate) + * to convert to bits/bytes, if necessary. + */ + unsigned int rc_buf_optimal_sz; + + + /* + * 2 pass rate control parameters + */ + + + /*!\brief Two-pass mode CBR/VBR bias + * + * Bias, expressed on a scale of 0 to 100, for determining target size + * for the current frame. The value 0 indicates the optimal CBR mode + * value should be used. The value 100 indicates the optimal VBR mode + * value should be used. Values in between indicate which way the + * encoder should "lean." + */ + unsigned int rc_2pass_vbr_bias_pct; /**< RC mode bias between CBR and VBR(0-100: 0->CBR, 100->VBR) */ + + + /*!\brief Two-pass mode per-GOP minimum bitrate + * + * This value, expressed as a percentage of the target bitrate, indicates + * the minimum bitrate to be used for a single GOP (aka "section") + */ + unsigned int rc_2pass_vbr_minsection_pct; + + + /*!\brief Two-pass mode per-GOP maximum bitrate + * + * This value, expressed as a percentage of the target bitrate, indicates + * the maximum bitrate to be used for a single GOP (aka "section") + */ + unsigned int rc_2pass_vbr_maxsection_pct; + + + /* + * keyframing settings (kf) + */ + + /*!\brief Keyframe placement mode + * + * This value indicates whether the encoder should place keyframes at a + * fixed interval, or determine the optimal placement automatically + * (as governed by the #kf_min_dist and #kf_max_dist parameters) + */ + enum vpx_kf_mode kf_mode; + + + /*!\brief Keyframe minimum interval + * + * This value, expressed as a number of frames, prevents the encoder from + * placing a keyframe nearer than kf_min_dist to the previous keyframe. At + * least kf_min_dist frames non-keyframes will be coded before the next + * keyframe. Set kf_min_dist equal to kf_max_dist for a fixed interval. + */ + unsigned int kf_min_dist; + + + /*!\brief Keyframe maximum interval + * + * This value, expressed as a number of frames, forces the encoder to code + * a keyframe if one has not been coded in the last kf_max_dist frames. + * A value of 0 implies all frames will be keyframes. Set kf_min_dist + * equal to kf_max_dist for a fixed interval. + */ + unsigned int kf_max_dist; + + /* + * Spatial scalability settings (ss) + */ + + /*!\brief Number of spatial coding layers. + * + * This value specifies the number of spatial coding layers to be used. + */ + unsigned int ss_number_layers; + + /*!\brief Enable auto alt reference flags for each spatial layer. + * + * These values specify if auto alt reference frame is enabled for each + * spatial layer. + */ + int ss_enable_auto_alt_ref[VPX_SS_MAX_LAYERS]; + + /*!\brief Target bitrate for each spatial layer. + * + * These values specify the target coding bitrate to be used for each + * spatial layer. + */ + unsigned int ss_target_bitrate[VPX_SS_MAX_LAYERS]; + + /*!\brief Number of temporal coding layers. + * + * This value specifies the number of temporal layers to be used. + */ + unsigned int ts_number_layers; + + /*!\brief Target bitrate for each temporal layer. + * + * These values specify the target coding bitrate to be used for each + * temporal layer. + */ + unsigned int ts_target_bitrate[VPX_TS_MAX_LAYERS]; + + /*!\brief Frame rate decimation factor for each temporal layer. + * + * These values specify the frame rate decimation factors to apply + * to each temporal layer. + */ + unsigned int ts_rate_decimator[VPX_TS_MAX_LAYERS]; + + /*!\brief Length of the sequence defining frame temporal layer membership. + * + * This value specifies the length of the sequence that defines the + * membership of frames to temporal layers. For example, if the + * ts_periodicity = 8, then the frames are assigned to coding layers with a + * repeated sequence of length 8. + */ + unsigned int ts_periodicity; + + /*!\brief Template defining the membership of frames to temporal layers. + * + * This array defines the membership of frames to temporal coding layers. + * For a 2-layer encoding that assigns even numbered frames to one temporal + * layer (0) and odd numbered frames to a second temporal layer (1) with + * ts_periodicity=8, then ts_layer_id = (0,1,0,1,0,1,0,1). + */ + unsigned int ts_layer_id[VPX_TS_MAX_PERIODICITY]; + } vpx_codec_enc_cfg_t; /**< alias for struct vpx_codec_enc_cfg */ + + /*!\brief vp9 svc extra configure parameters + * + * This defines max/min quantizers and scale factors for each layer + * + */ + typedef struct vpx_svc_parameters { + int max_quantizers[VPX_SS_MAX_LAYERS]; /**< Max Q for each layer */ + int min_quantizers[VPX_SS_MAX_LAYERS]; /**< Min Q for each layer */ + int scaling_factor_num[VPX_SS_MAX_LAYERS]; /**< Scaling factor-numerator*/ + int scaling_factor_den[VPX_SS_MAX_LAYERS]; /**< Scaling factor-denominator*/ + } vpx_svc_extra_cfg_t; + + + /*!\brief Initialize an encoder instance + * + * Initializes a encoder context using the given interface. Applications + * should call the vpx_codec_enc_init convenience macro instead of this + * function directly, to ensure that the ABI version number parameter + * is properly initialized. + * + * If the library was configured with --disable-multithread, this call + * is not thread safe and should be guarded with a lock if being used + * in a multithreaded context. + * + * \param[in] ctx Pointer to this instance's context. + * \param[in] iface Pointer to the algorithm interface to use. + * \param[in] cfg Configuration to use, if known. May be NULL. + * \param[in] flags Bitfield of VPX_CODEC_USE_* flags + * \param[in] ver ABI version number. Must be set to + * VPX_ENCODER_ABI_VERSION + * \retval #VPX_CODEC_OK + * The decoder algorithm initialized. + * \retval #VPX_CODEC_MEM_ERROR + * Memory allocation failed. + */ + vpx_codec_err_t vpx_codec_enc_init_ver(vpx_codec_ctx_t *ctx, + vpx_codec_iface_t *iface, + const vpx_codec_enc_cfg_t *cfg, + vpx_codec_flags_t flags, + int ver); + + + /*!\brief Convenience macro for vpx_codec_enc_init_ver() + * + * Ensures the ABI version parameter is properly set. + */ +#define vpx_codec_enc_init(ctx, iface, cfg, flags) \ + vpx_codec_enc_init_ver(ctx, iface, cfg, flags, VPX_ENCODER_ABI_VERSION) + + + /*!\brief Initialize multi-encoder instance + * + * Initializes multi-encoder context using the given interface. + * Applications should call the vpx_codec_enc_init_multi convenience macro + * instead of this function directly, to ensure that the ABI version number + * parameter is properly initialized. + * + * \param[in] ctx Pointer to this instance's context. + * \param[in] iface Pointer to the algorithm interface to use. + * \param[in] cfg Configuration to use, if known. May be NULL. + * \param[in] num_enc Total number of encoders. + * \param[in] flags Bitfield of VPX_CODEC_USE_* flags + * \param[in] dsf Pointer to down-sampling factors. + * \param[in] ver ABI version number. Must be set to + * VPX_ENCODER_ABI_VERSION + * \retval #VPX_CODEC_OK + * The decoder algorithm initialized. + * \retval #VPX_CODEC_MEM_ERROR + * Memory allocation failed. + */ + vpx_codec_err_t vpx_codec_enc_init_multi_ver(vpx_codec_ctx_t *ctx, + vpx_codec_iface_t *iface, + vpx_codec_enc_cfg_t *cfg, + int num_enc, + vpx_codec_flags_t flags, + vpx_rational_t *dsf, + int ver); + + + /*!\brief Convenience macro for vpx_codec_enc_init_multi_ver() + * + * Ensures the ABI version parameter is properly set. + */ +#define vpx_codec_enc_init_multi(ctx, iface, cfg, num_enc, flags, dsf) \ + vpx_codec_enc_init_multi_ver(ctx, iface, cfg, num_enc, flags, dsf, \ + VPX_ENCODER_ABI_VERSION) + + + /*!\brief Get a default configuration + * + * Initializes a encoder configuration structure with default values. Supports + * the notion of "usages" so that an algorithm may offer different default + * settings depending on the user's intended goal. This function \ref SHOULD + * be called by all applications to initialize the configuration structure + * before specializing the configuration with application specific values. + * + * \param[in] iface Pointer to the algorithm interface to use. + * \param[out] cfg Configuration buffer to populate. + * \param[in] reserved Must set to 0 for VP8 and VP9. + * + * \retval #VPX_CODEC_OK + * The configuration was populated. + * \retval #VPX_CODEC_INCAPABLE + * Interface is not an encoder interface. + * \retval #VPX_CODEC_INVALID_PARAM + * A parameter was NULL, or the usage value was not recognized. + */ + vpx_codec_err_t vpx_codec_enc_config_default(vpx_codec_iface_t *iface, + vpx_codec_enc_cfg_t *cfg, + unsigned int reserved); + + + /*!\brief Set or change configuration + * + * Reconfigures an encoder instance according to the given configuration. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] cfg Configuration buffer to use + * + * \retval #VPX_CODEC_OK + * The configuration was populated. + * \retval #VPX_CODEC_INCAPABLE + * Interface is not an encoder interface. + * \retval #VPX_CODEC_INVALID_PARAM + * A parameter was NULL, or the usage value was not recognized. + */ + vpx_codec_err_t vpx_codec_enc_config_set(vpx_codec_ctx_t *ctx, + const vpx_codec_enc_cfg_t *cfg); + + + /*!\brief Get global stream headers + * + * Retrieves a stream level global header packet, if supported by the codec. + * + * \param[in] ctx Pointer to this instance's context + * + * \retval NULL + * Encoder does not support global header + * \retval Non-NULL + * Pointer to buffer containing global header packet + */ + vpx_fixed_buf_t *vpx_codec_get_global_headers(vpx_codec_ctx_t *ctx); + + +#define VPX_DL_REALTIME (1) /**< deadline parameter analogous to + * VPx REALTIME mode. */ +#define VPX_DL_GOOD_QUALITY (1000000) /**< deadline parameter analogous to + * VPx GOOD QUALITY mode. */ +#define VPX_DL_BEST_QUALITY (0) /**< deadline parameter analogous to + * VPx BEST QUALITY mode. */ + /*!\brief Encode a frame + * + * Encodes a video frame at the given "presentation time." The presentation + * time stamp (PTS) \ref MUST be strictly increasing. + * + * The encoder supports the notion of a soft real-time deadline. Given a + * non-zero value to the deadline parameter, the encoder will make a "best + * effort" guarantee to return before the given time slice expires. It is + * implicit that limiting the available time to encode will degrade the + * output quality. The encoder can be given an unlimited time to produce the + * best possible frame by specifying a deadline of '0'. This deadline + * supercedes the VPx notion of "best quality, good quality, realtime". + * Applications that wish to map these former settings to the new deadline + * based system can use the symbols #VPX_DL_REALTIME, #VPX_DL_GOOD_QUALITY, + * and #VPX_DL_BEST_QUALITY. + * + * When the last frame has been passed to the encoder, this function should + * continue to be called, with the img parameter set to NULL. This will + * signal the end-of-stream condition to the encoder and allow it to encode + * any held buffers. Encoding is complete when vpx_codec_encode() is called + * and vpx_codec_get_cx_data() returns no data. + * + * \param[in] ctx Pointer to this instance's context + * \param[in] img Image data to encode, NULL to flush. + * \param[in] pts Presentation time stamp, in timebase units. + * \param[in] duration Duration to show frame, in timebase units. + * \param[in] flags Flags to use for encoding this frame. + * \param[in] deadline Time to spend encoding, in microseconds. (0=infinite) + * + * \retval #VPX_CODEC_OK + * The configuration was populated. + * \retval #VPX_CODEC_INCAPABLE + * Interface is not an encoder interface. + * \retval #VPX_CODEC_INVALID_PARAM + * A parameter was NULL, the image format is unsupported, etc. + */ + vpx_codec_err_t vpx_codec_encode(vpx_codec_ctx_t *ctx, + const vpx_image_t *img, + vpx_codec_pts_t pts, + unsigned long duration, + vpx_enc_frame_flags_t flags, + unsigned long deadline); + + /*!\brief Set compressed data output buffer + * + * Sets the buffer that the codec should output the compressed data + * into. This call effectively sets the buffer pointer returned in the + * next VPX_CODEC_CX_FRAME_PKT packet. Subsequent packets will be + * appended into this buffer. The buffer is preserved across frames, + * so applications must periodically call this function after flushing + * the accumulated compressed data to disk or to the network to reset + * the pointer to the buffer's head. + * + * `pad_before` bytes will be skipped before writing the compressed + * data, and `pad_after` bytes will be appended to the packet. The size + * of the packet will be the sum of the size of the actual compressed + * data, pad_before, and pad_after. The padding bytes will be preserved + * (not overwritten). + * + * Note that calling this function does not guarantee that the returned + * compressed data will be placed into the specified buffer. In the + * event that the encoded data will not fit into the buffer provided, + * the returned packet \ref MAY point to an internal buffer, as it would + * if this call were never used. In this event, the output packet will + * NOT have any padding, and the application must free space and copy it + * to the proper place. This is of particular note in configurations + * that may output multiple packets for a single encoded frame (e.g., lagged + * encoding) or if the application does not reset the buffer periodically. + * + * Applications may restore the default behavior of the codec providing + * the compressed data buffer by calling this function with a NULL + * buffer. + * + * Applications \ref MUSTNOT call this function during iteration of + * vpx_codec_get_cx_data(). + * + * \param[in] ctx Pointer to this instance's context + * \param[in] buf Buffer to store compressed data into + * \param[in] pad_before Bytes to skip before writing compressed data + * \param[in] pad_after Bytes to skip after writing compressed data + * + * \retval #VPX_CODEC_OK + * The buffer was set successfully. + * \retval #VPX_CODEC_INVALID_PARAM + * A parameter was NULL, the image format is unsupported, etc. + */ + vpx_codec_err_t vpx_codec_set_cx_data_buf(vpx_codec_ctx_t *ctx, + const vpx_fixed_buf_t *buf, + unsigned int pad_before, + unsigned int pad_after); + + + /*!\brief Encoded data iterator + * + * Iterates over a list of data packets to be passed from the encoder to the + * application. The different kinds of packets available are enumerated in + * #vpx_codec_cx_pkt_kind. + * + * #VPX_CODEC_CX_FRAME_PKT packets should be passed to the application's + * muxer. Multiple compressed frames may be in the list. + * #VPX_CODEC_STATS_PKT packets should be appended to a global buffer. + * + * The application \ref MUST silently ignore any packet kinds that it does + * not recognize or support. + * + * The data buffers returned from this function are only guaranteed to be + * valid until the application makes another call to any vpx_codec_* function. + * + * \param[in] ctx Pointer to this instance's context + * \param[in,out] iter Iterator storage, initialized to NULL + * + * \return Returns a pointer to an output data packet (compressed frame data, + * two-pass statistics, etc.) or NULL to signal end-of-list. + * + */ + const vpx_codec_cx_pkt_t *vpx_codec_get_cx_data(vpx_codec_ctx_t *ctx, + vpx_codec_iter_t *iter); + + + /*!\brief Get Preview Frame + * + * Returns an image that can be used as a preview. Shows the image as it would + * exist at the decompressor. The application \ref MUST NOT write into this + * image buffer. + * + * \param[in] ctx Pointer to this instance's context + * + * \return Returns a pointer to a preview image, or NULL if no image is + * available. + * + */ + const vpx_image_t *vpx_codec_get_preview_frame(vpx_codec_ctx_t *ctx); + + + /*!@} - end defgroup encoder*/ +#ifdef __cplusplus +} +#endif +#endif // VPX_VPX_ENCODER_H_ + diff --git a/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_frame_buffer.h b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_frame_buffer.h new file mode 100644 index 0000000..9036459 --- /dev/null +++ b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_frame_buffer.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2014 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef VPX_VPX_FRAME_BUFFER_H_ +#define VPX_VPX_FRAME_BUFFER_H_ + +/*!\file + * \brief Describes the decoder external frame buffer interface. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./vpx_integer.h" + +/*!\brief The maximum number of work buffers used by libvpx. + * Support maximum 4 threads to decode video in parallel. + * Each thread will use one work buffer. + * TODO(hkuang): Add support to set number of worker threads dynamically. + */ +#define VPX_MAXIMUM_WORK_BUFFERS 8 + +/*!\brief The maximum number of reference buffers that a VP9 encoder may use. + */ +#define VP9_MAXIMUM_REF_BUFFERS 8 + +/*!\brief External frame buffer + * + * This structure holds allocated frame buffers used by the decoder. + */ +typedef struct vpx_codec_frame_buffer { + uint8_t *data; /**< Pointer to the data buffer */ + size_t size; /**< Size of data in bytes */ + void *priv; /**< Frame's private data */ +} vpx_codec_frame_buffer_t; + +/*!\brief get frame buffer callback prototype + * + * This callback is invoked by the decoder to retrieve data for the frame + * buffer in order for the decode call to complete. The callback must + * allocate at least min_size in bytes and assign it to fb->data. The callback + * must zero out all the data allocated. Then the callback must set fb->size + * to the allocated size. The application does not need to align the allocated + * data. The callback is triggered when the decoder needs a frame buffer to + * decode a compressed image into. This function may be called more than once + * for every call to vpx_codec_decode. The application may set fb->priv to + * some data which will be passed back in the ximage and the release function + * call. |fb| is guaranteed to not be NULL. On success the callback must + * return 0. Any failure the callback must return a value less than 0. + * + * \param[in] priv Callback's private data + * \param[in] new_size Size in bytes needed by the buffer + * \param[in,out] fb Pointer to vpx_codec_frame_buffer_t + */ +typedef int (*vpx_get_frame_buffer_cb_fn_t)( + void *priv, size_t min_size, vpx_codec_frame_buffer_t *fb); + +/*!\brief release frame buffer callback prototype + * + * This callback is invoked by the decoder when the frame buffer is not + * referenced by any other buffers. |fb| is guaranteed to not be NULL. On + * success the callback must return 0. Any failure the callback must return + * a value less than 0. + * + * \param[in] priv Callback's private data + * \param[in] fb Pointer to vpx_codec_frame_buffer_t + */ +typedef int (*vpx_release_frame_buffer_cb_fn_t)( + void *priv, vpx_codec_frame_buffer_t *fb); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // VPX_VPX_FRAME_BUFFER_H_ diff --git a/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_image.h b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_image.h new file mode 100644 index 0000000..c06d351 --- /dev/null +++ b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_image.h @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +/*!\file + * \brief Describes the vpx image descriptor and associated operations + * + */ +#ifndef VPX_VPX_IMAGE_H_ +#define VPX_VPX_IMAGE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + /*!\brief Current ABI version number + * + * \internal + * If this file is altered in any way that changes the ABI, this value + * must be bumped. Examples include, but are not limited to, changing + * types, removing or reassigning enums, adding/removing/rearranging + * fields to structures + */ +#define VPX_IMAGE_ABI_VERSION (3) /**<\hideinitializer*/ + + +#define VPX_IMG_FMT_PLANAR 0x100 /**< Image is a planar format. */ +#define VPX_IMG_FMT_UV_FLIP 0x200 /**< V plane precedes U in memory. */ +#define VPX_IMG_FMT_HAS_ALPHA 0x400 /**< Image has an alpha channel. */ +#define VPX_IMG_FMT_HIGHBITDEPTH 0x800 /**< Image uses 16bit framebuffer. */ + + /*!\brief List of supported image formats */ + typedef enum vpx_img_fmt { + VPX_IMG_FMT_NONE, + VPX_IMG_FMT_RGB24, /**< 24 bit per pixel packed RGB */ + VPX_IMG_FMT_RGB32, /**< 32 bit per pixel packed 0RGB */ + VPX_IMG_FMT_RGB565, /**< 16 bit per pixel, 565 */ + VPX_IMG_FMT_RGB555, /**< 16 bit per pixel, 555 */ + VPX_IMG_FMT_UYVY, /**< UYVY packed YUV */ + VPX_IMG_FMT_YUY2, /**< YUYV packed YUV */ + VPX_IMG_FMT_YVYU, /**< YVYU packed YUV */ + VPX_IMG_FMT_BGR24, /**< 24 bit per pixel packed BGR */ + VPX_IMG_FMT_RGB32_LE, /**< 32 bit packed BGR0 */ + VPX_IMG_FMT_ARGB, /**< 32 bit packed ARGB, alpha=255 */ + VPX_IMG_FMT_ARGB_LE, /**< 32 bit packed BGRA, alpha=255 */ + VPX_IMG_FMT_RGB565_LE, /**< 16 bit per pixel, gggbbbbb rrrrrggg */ + VPX_IMG_FMT_RGB555_LE, /**< 16 bit per pixel, gggbbbbb 0rrrrrgg */ + VPX_IMG_FMT_YV12 = VPX_IMG_FMT_PLANAR | VPX_IMG_FMT_UV_FLIP | 1, /**< planar YVU */ + VPX_IMG_FMT_I420 = VPX_IMG_FMT_PLANAR | 2, + VPX_IMG_FMT_VPXYV12 = VPX_IMG_FMT_PLANAR | VPX_IMG_FMT_UV_FLIP | 3, /** < planar 4:2:0 format with vpx color space */ + VPX_IMG_FMT_VPXI420 = VPX_IMG_FMT_PLANAR | 4, + VPX_IMG_FMT_I422 = VPX_IMG_FMT_PLANAR | 5, + VPX_IMG_FMT_I444 = VPX_IMG_FMT_PLANAR | 6, + VPX_IMG_FMT_I440 = VPX_IMG_FMT_PLANAR | 7, + VPX_IMG_FMT_444A = VPX_IMG_FMT_PLANAR | VPX_IMG_FMT_HAS_ALPHA | 6, + VPX_IMG_FMT_I42016 = VPX_IMG_FMT_I420 | VPX_IMG_FMT_HIGHBITDEPTH, + VPX_IMG_FMT_I42216 = VPX_IMG_FMT_I422 | VPX_IMG_FMT_HIGHBITDEPTH, + VPX_IMG_FMT_I44416 = VPX_IMG_FMT_I444 | VPX_IMG_FMT_HIGHBITDEPTH, + VPX_IMG_FMT_I44016 = VPX_IMG_FMT_I440 | VPX_IMG_FMT_HIGHBITDEPTH + } vpx_img_fmt_t; /**< alias for enum vpx_img_fmt */ + + /*!\brief List of supported color spaces */ + typedef enum vpx_color_space { + VPX_CS_UNKNOWN = 0, /**< Unknown */ + VPX_CS_BT_601 = 1, /**< BT.601 */ + VPX_CS_BT_709 = 2, /**< BT.709 */ + VPX_CS_SMPTE_170 = 3, /**< SMPTE.170 */ + VPX_CS_SMPTE_240 = 4, /**< SMPTE.240 */ + VPX_CS_BT_2020 = 5, /**< BT.2020 */ + VPX_CS_RESERVED = 6, /**< Reserved */ + VPX_CS_SRGB = 7 /**< sRGB */ + } vpx_color_space_t; /**< alias for enum vpx_color_space */ + + /**\brief Image Descriptor */ + typedef struct vpx_image { + vpx_img_fmt_t fmt; /**< Image Format */ + vpx_color_space_t cs; /**< Color Space */ + + /* Image storage dimensions */ + unsigned int w; /**< Stored image width */ + unsigned int h; /**< Stored image height */ + unsigned int bit_depth; /**< Stored image bit-depth */ + + /* Image display dimensions */ + unsigned int d_w; /**< Displayed image width */ + unsigned int d_h; /**< Displayed image height */ + + /* Chroma subsampling info */ + unsigned int x_chroma_shift; /**< subsampling order, X */ + unsigned int y_chroma_shift; /**< subsampling order, Y */ + + /* Image data pointers. */ +#define VPX_PLANE_PACKED 0 /**< To be used for all packed formats */ +#define VPX_PLANE_Y 0 /**< Y (Luminance) plane */ +#define VPX_PLANE_U 1 /**< U (Chroma) plane */ +#define VPX_PLANE_V 2 /**< V (Chroma) plane */ +#define VPX_PLANE_ALPHA 3 /**< A (Transparency) plane */ + unsigned char *planes[4]; /**< pointer to the top left pixel for each plane */ + int stride[4]; /**< stride between rows for each plane */ + + int bps; /**< bits per sample (for packed formats) */ + + /* The following member may be set by the application to associate data + * with this image. + */ + void *user_priv; /**< may be set by the application to associate data + * with this image. */ + + /* The following members should be treated as private. */ + unsigned char *img_data; /**< private */ + int img_data_owner; /**< private */ + int self_allocd; /**< private */ + + void *fb_priv; /**< Frame buffer data associated with the image. */ + } vpx_image_t; /**< alias for struct vpx_image */ + + /**\brief Representation of a rectangle on a surface */ + typedef struct vpx_image_rect { + unsigned int x; /**< leftmost column */ + unsigned int y; /**< topmost row */ + unsigned int w; /**< width */ + unsigned int h; /**< height */ + } vpx_image_rect_t; /**< alias for struct vpx_image_rect */ + + /*!\brief Open a descriptor, allocating storage for the underlying image + * + * Returns a descriptor for storing an image of the given format. The + * storage for the descriptor is allocated on the heap. + * + * \param[in] img Pointer to storage for descriptor. If this parameter + * is NULL, the storage for the descriptor will be + * allocated on the heap. + * \param[in] fmt Format for the image + * \param[in] d_w Width of the image + * \param[in] d_h Height of the image + * \param[in] align Alignment, in bytes, of the image buffer and + * each row in the image(stride). + * + * \return Returns a pointer to the initialized image descriptor. If the img + * parameter is non-null, the value of the img parameter will be + * returned. + */ + vpx_image_t *vpx_img_alloc(vpx_image_t *img, + vpx_img_fmt_t fmt, + unsigned int d_w, + unsigned int d_h, + unsigned int align); + + /*!\brief Open a descriptor, using existing storage for the underlying image + * + * Returns a descriptor for storing an image of the given format. The + * storage for descriptor has been allocated elsewhere, and a descriptor is + * desired to "wrap" that storage. + * + * \param[in] img Pointer to storage for descriptor. If this parameter + * is NULL, the storage for the descriptor will be + * allocated on the heap. + * \param[in] fmt Format for the image + * \param[in] d_w Width of the image + * \param[in] d_h Height of the image + * \param[in] align Alignment, in bytes, of each row in the image. + * \param[in] img_data Storage to use for the image + * + * \return Returns a pointer to the initialized image descriptor. If the img + * parameter is non-null, the value of the img parameter will be + * returned. + */ + vpx_image_t *vpx_img_wrap(vpx_image_t *img, + vpx_img_fmt_t fmt, + unsigned int d_w, + unsigned int d_h, + unsigned int align, + unsigned char *img_data); + + + /*!\brief Set the rectangle identifying the displayed portion of the image + * + * Updates the displayed rectangle (aka viewport) on the image surface to + * match the specified coordinates and size. + * + * \param[in] img Image descriptor + * \param[in] x leftmost column + * \param[in] y topmost row + * \param[in] w width + * \param[in] h height + * + * \return 0 if the requested rectangle is valid, nonzero otherwise. + */ + int vpx_img_set_rect(vpx_image_t *img, + unsigned int x, + unsigned int y, + unsigned int w, + unsigned int h); + + + /*!\brief Flip the image vertically (top for bottom) + * + * Adjusts the image descriptor's pointers and strides to make the image + * be referenced upside-down. + * + * \param[in] img Image descriptor + */ + void vpx_img_flip(vpx_image_t *img); + + /*!\brief Close an image descriptor + * + * Frees all allocated storage associated with an image descriptor. + * + * \param[in] img Image descriptor + */ + void vpx_img_free(vpx_image_t *img); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // VPX_VPX_IMAGE_H_ diff --git a/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_integer.h b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_integer.h new file mode 100644 index 0000000..829c9d1 --- /dev/null +++ b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_integer.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +#ifndef VPX_VPX_INTEGER_H_ +#define VPX_VPX_INTEGER_H_ + +/* get ptrdiff_t, size_t, wchar_t, NULL */ +#include + +#if defined(_MSC_VER) +#define VPX_FORCE_INLINE __forceinline +#define VPX_INLINE __inline +#else +#define VPX_FORCE_INLINE __inline__ __attribute__(always_inline) +// TODO(jbb): Allow a way to force inline off for older compilers. +#define VPX_INLINE inline +#endif + +#if (defined(_MSC_VER) && (_MSC_VER < 1600)) || defined(VPX_EMULATE_INTTYPES) +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; + +#if (defined(_MSC_VER) && (_MSC_VER < 1600)) +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +#define INT64_MAX _I64_MAX +#define INT32_MAX _I32_MAX +#define INT32_MIN _I32_MIN +#define INT16_MAX _I16_MAX +#define INT16_MIN _I16_MIN +#endif + +#ifndef _UINTPTR_T_DEFINED +typedef size_t uintptr_t; +#endif + +#else + +/* Most platforms have the C99 standard integer types. */ + +#if defined(__cplusplus) +# if !defined(__STDC_FORMAT_MACROS) +# define __STDC_FORMAT_MACROS +# endif +# if !defined(__STDC_LIMIT_MACROS) +# define __STDC_LIMIT_MACROS +# endif +#endif // __cplusplus + +#include + +#endif + +/* VS2010 defines stdint.h, but not inttypes.h */ +#if defined(_MSC_VER) && _MSC_VER < 1800 +#define PRId64 "I64d" +#else +#include +#endif + +#endif // VPX_VPX_INTEGER_H_ diff --git a/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_version.h b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_version.h new file mode 100644 index 0000000..bce0381 --- /dev/null +++ b/local_pod_repo/toxcore/osx/vpx.framework/Headers/vpx_version.h @@ -0,0 +1,7 @@ +#define VERSION_MAJOR 1 +#define VERSION_MINOR 4 +#define VERSION_PATCH 0 +#define VERSION_EXTRA "" +#define VERSION_PACKED ((VERSION_MAJOR<<16)|(VERSION_MINOR<<8)|(VERSION_PATCH)) +#define VERSION_STRING_NOSP "v1.4.0" +#define VERSION_STRING " v1.4.0" diff --git a/local_pod_repo/toxcore/osx/vpx.framework/Headers/x86_64-darwin13-gcc/vpx_config.h b/local_pod_repo/toxcore/osx/vpx.framework/Headers/x86_64-darwin13-gcc/vpx_config.h new file mode 100644 index 0000000..ae6b66d --- /dev/null +++ b/local_pod_repo/toxcore/osx/vpx.framework/Headers/x86_64-darwin13-gcc/vpx_config.h @@ -0,0 +1,102 @@ +/* Copyright (c) 2011 The WebM project authors. All Rights Reserved. */ +/* */ +/* Use of this source code is governed by a BSD-style license */ +/* that can be found in the LICENSE file in the root of the source */ +/* tree. An additional intellectual property rights grant can be found */ +/* in the file PATENTS. All contributing project authors may */ +/* be found in the AUTHORS file in the root of the source tree. */ +/* This file automatically generated by configure. Do not edit! */ +#ifndef VPX_CONFIG_H +#define VPX_CONFIG_H +#define RESTRICT +#define INLINE inline +#define ARCH_ARM 0 +#define ARCH_MIPS 0 +#define ARCH_X86 0 +#define ARCH_X86_64 1 +#define ARCH_PPC32 0 +#define ARCH_PPC64 0 +#define HAVE_EDSP 0 +#define HAVE_MEDIA 0 +#define HAVE_NEON 0 +#define HAVE_NEON_ASM 0 +#define HAVE_MIPS32 0 +#define HAVE_DSPR2 0 +#define HAVE_MIPS64 0 +#define HAVE_MMX 1 +#define HAVE_SSE 1 +#define HAVE_SSE2 1 +#define HAVE_SSE3 1 +#define HAVE_SSSE3 1 +#define HAVE_SSE4_1 1 +#define HAVE_AVX 1 +#define HAVE_AVX2 1 +#define HAVE_ALTIVEC 0 +#define HAVE_VPX_PORTS 1 +#define HAVE_STDINT_H 1 +#define HAVE_ALT_TREE_LAYOUT 0 +#define HAVE_PTHREAD_H 1 +#define HAVE_SYS_MMAN_H 1 +#define HAVE_UNISTD_H 1 +#define CONFIG_DEPENDENCY_TRACKING 1 +#define CONFIG_EXTERNAL_BUILD 0 +#define CONFIG_INSTALL_DOCS 1 +#define CONFIG_INSTALL_BINS 1 +#define CONFIG_INSTALL_LIBS 1 +#define CONFIG_INSTALL_SRCS 0 +#define CONFIG_USE_X86INC 1 +#define CONFIG_DEBUG 0 +#define CONFIG_GPROF 0 +#define CONFIG_GCOV 0 +#define CONFIG_RVCT 0 +#define CONFIG_GCC 1 +#define CONFIG_MSVS 0 +#define CONFIG_PIC 0 +#define CONFIG_BIG_ENDIAN 0 +#define CONFIG_CODEC_SRCS 0 +#define CONFIG_DEBUG_LIBS 0 +#define CONFIG_FAST_UNALIGNED 1 +#define CONFIG_MEM_MANAGER 0 +#define CONFIG_MEM_TRACKER 0 +#define CONFIG_MEM_CHECKS 0 +#define CONFIG_DEQUANT_TOKENS 0 +#define CONFIG_DC_RECON 0 +#define CONFIG_RUNTIME_CPU_DETECT 1 +#define CONFIG_POSTPROC 1 +#define CONFIG_VP9_POSTPROC 0 +#define CONFIG_MULTITHREAD 1 +#define CONFIG_INTERNAL_STATS 0 +#define CONFIG_VP8_ENCODER 1 +#define CONFIG_VP8_DECODER 1 +#define CONFIG_VP9_ENCODER 1 +#define CONFIG_VP9_DECODER 1 +#define CONFIG_VP8 1 +#define CONFIG_VP9 1 +#define CONFIG_ENCODERS 1 +#define CONFIG_DECODERS 1 +#define CONFIG_STATIC_MSVCRT 0 +#define CONFIG_SPATIAL_RESAMPLING 1 +#define CONFIG_REALTIME_ONLY 0 +#define CONFIG_ONTHEFLY_BITPACKING 0 +#define CONFIG_ERROR_CONCEALMENT 0 +#define CONFIG_SHARED 0 +#define CONFIG_STATIC 1 +#define CONFIG_SMALL 0 +#define CONFIG_POSTPROC_VISUALIZER 0 +#define CONFIG_OS_SUPPORT 1 +#define CONFIG_UNIT_TESTS 0 +#define CONFIG_WEBM_IO 1 +#define CONFIG_LIBYUV 0 +#define CONFIG_DECODE_PERF_TESTS 0 +#define CONFIG_ENCODE_PERF_TESTS 0 +#define CONFIG_MULTI_RES_ENCODING 0 +#define CONFIG_TEMPORAL_DENOISING 1 +#define CONFIG_VP9_TEMPORAL_DENOISING 0 +#define CONFIG_COEFFICIENT_RANGE_CHECKING 0 +#define CONFIG_VP9_HIGHBITDEPTH 0 +#define CONFIG_EXPERIMENTAL 0 +#define CONFIG_SIZE_LIMIT 0 +#define CONFIG_SPATIAL_SVC 0 +#define CONFIG_FP_MB_STATS 0 +#define CONFIG_EMULATE_HARDWARE 0 +#endif /* VPX_CONFIG_H */ diff --git a/local_pod_repo/toxcore/osx/vpx.framework/vpx b/local_pod_repo/toxcore/osx/vpx.framework/vpx new file mode 100644 index 0000000..2301fa4 Binary files /dev/null and b/local_pod_repo/toxcore/osx/vpx.framework/vpx differ diff --git a/local_pod_repo/toxcore/toxcore.podspec b/local_pod_repo/toxcore/toxcore.podspec new file mode 100644 index 0000000..29db770 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore.podspec @@ -0,0 +1,43 @@ +# +# Be sure to run `pod lib lint toxcore.podspec' to ensure this is a +# valid spec and remove all comments before submitting the spec. +# +# Any lines starting with a # are optional, but encouraged +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = "toxcore" + s.version = "0.2.18" + s.summary = "Cocoapods wrapper for toxcore" + s.homepage = "https://github.com/Zoxcore/toxcore" + s.license = 'GPLv3' + s.author = { "Dmytro Vorobiov" => "d@dvor.me" } + s.source = { + :git => "https://github.com/Zoxcore/toxcore.git", + :tag => s.version.to_s, + :submodules => true + } + + s.pod_target_xcconfig = { 'ENABLE_BITCODE' => 'NO', 'OTHER_LDFLAGS' => '-read_only_relocs suppress' } + + s.ios.deployment_target = '7.0' + s.osx.deployment_target = '10.9' + s.requires_arc = true + + # Preserve the layout of headers in the toxcore directory + s.header_mappings_dir = 'toxcore' + + s.source_files = 'toxcore/toxcore/*.{m,h}', 'toxcore/toxencryptsave/*.{m,h}', 'toxcore/toxav/*.{m,h}', 'toxcore/toxcore/events/*.{m,h}' + + # s.dependency 'libopus-patched-config', '1.1' + s.dependency 'libopus-static', '1.3.1' + s.dependency 'libsodium', '~> 1.0.12' + # s.dependency 'msgpack-c' + + s.ios.vendored_frameworks = 'ios/vpx.framework' + s.osx.vendored_frameworks = 'osx/vpx.framework' + s.xcconfig = { 'FRAMEWORK_SEARCH_PATHS' => '"${PODS_ROOT}"'} + +end diff --git a/local_pod_repo/toxcore/toxcore/toxav/Makefile.inc b/local_pod_repo/toxcore/toxcore/toxav/Makefile.inc new file mode 100644 index 0000000..7b78819 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/Makefile.inc @@ -0,0 +1,50 @@ +if BUILD_AV + +lib_LTLIBRARIES += libtoxav.la + libtoxav_la_include_HEADERS = ../toxav/toxav.h + libtoxav_la_includedir = $(includedir)/tox + +libtoxav_la_SOURCES = ../toxav/rtp.h \ + ../toxav/rtp.c \ + ../toxav/msi.h \ + ../toxav/msi.c \ + ../toxav/groupav.h \ + ../toxav/groupav.c \ + ../toxav/audio.h \ + ../toxav/audio.c \ + ../toxav/video.h \ + ../toxav/video.c \ + ../toxav/bwcontroller.h \ + ../toxav/bwcontroller.c \ + ../toxav/ring_buffer.h \ + ../toxav/ring_buffer.c \ + ../toxav/toxav.h \ + ../toxav/toxav.c \ + ../toxav/toxav_old.c + +libtoxav_la_CFLAGS = -I../toxcore \ + -I../toxav \ + $(LIBSODIUM_CFLAGS) \ + $(NACL_CFLAGS) \ + $(AV_CFLAGS) \ + $(PTHREAD_CFLAGS) + +libtoxav_la_LDFLAGS = $(LT_LDFLAGS) \ + $(LIBSODIUM_LDFLAGS) \ + $(NACL_LDFLAGS) \ + $(EXTRA_LT_LDFLAGS) \ + $(WINSOCK2_LIBS) + +libtoxav_la_LIBADD = libtoxcore.la \ + $(LIBSODIUM_LIBS) \ + $(NACL_LIBS) \ + $(PTHREAD_LIBS) \ + $(AV_LIBS) + +if SET_SO_VERSION + +EXTRA_libtoxav_la_DEPENDENCIES = ../so.version + +endif + +endif diff --git a/local_pod_repo/toxcore/toxcore/toxav/audio.h b/local_pod_repo/toxcore/toxcore/toxav/audio.h new file mode 100644 index 0000000..3734987 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/audio.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ +#ifndef C_TOXCORE_TOXAV_AUDIO_H +#define C_TOXCORE_TOXAV_AUDIO_H + +#include "opus.h" +#include + +#include "toxav.h" + +#include "../toxcore/logger.h" +#include "../toxcore/util.h" +#include "rtp.h" + +#define AUDIO_JITTERBUFFER_COUNT 3 +#define AUDIO_MAX_SAMPLE_RATE 48000 +#define AUDIO_MAX_CHANNEL_COUNT 2 + +#define AUDIO_START_SAMPLE_RATE 48000 +#define AUDIO_START_BITRATE 48000 +#define AUDIO_START_CHANNEL_COUNT 2 +#define AUDIO_OPUS_PACKET_LOSS_PERC 10 +#define AUDIO_OPUS_COMPLEXITY 10 + +#define AUDIO_DECODER_START_SAMPLE_RATE 48000 +#define AUDIO_DECODER_START_CHANNEL_COUNT 1 + +#define AUDIO_MAX_FRAME_DURATION_MS 120 + +// ((sampling_rate_in_hz * frame_duration_in_ms) / 1000) * 2 // because PCM16 needs 2 bytes for 1 sample +// These are per frame and per channel. +#define AUDIO_MAX_BUFFER_SIZE_PCM16 ((AUDIO_MAX_SAMPLE_RATE * AUDIO_MAX_FRAME_DURATION_MS) / 1000) +#define AUDIO_MAX_BUFFER_SIZE_BYTES (AUDIO_MAX_BUFFER_SIZE_PCM16 * 2) + +typedef struct ACSession { + Mono_Time *mono_time; + const Logger *log; + + /* encoding */ + OpusEncoder *encoder; + uint32_t le_sample_rate; /* Last encoder sample rate */ + uint8_t le_channel_count; /* Last encoder channel count */ + uint32_t le_bit_rate; /* Last encoder bit rate */ + + /* decoding */ + OpusDecoder *decoder; + uint8_t lp_channel_count; /* Last packet channel count */ + uint32_t lp_sampling_rate; /* Last packet sample rate */ + uint32_t lp_frame_duration; /* Last packet frame duration */ + uint32_t ld_sample_rate; /* Last decoder sample rate */ + uint8_t ld_channel_count; /* Last decoder channel count */ + uint64_t ldrts; /* Last decoder reconfiguration time stamp */ + void *j_buf; + + pthread_mutex_t queue_mutex[1]; + + ToxAV *av; + uint32_t friend_number; + /* Audio frame receive callback */ + toxav_audio_receive_frame_cb *acb; + void *acb_user_data; +} ACSession; + +ACSession *ac_new(Mono_Time *mono_time, const Logger *log, ToxAV *av, uint32_t friend_number, + toxav_audio_receive_frame_cb *cb, void *cb_data); +void ac_kill(ACSession *ac); +void ac_iterate(ACSession *ac); +int ac_queue_message(Mono_Time *mono_time, void *acp, struct RTPMessage *msg); +int ac_reconfigure_encoder(ACSession *ac, uint32_t bit_rate, uint32_t sampling_rate, uint8_t channels); + +#endif // C_TOXCORE_TOXAV_AUDIO_H diff --git a/local_pod_repo/toxcore/toxcore/toxav/audio.m b/local_pod_repo/toxcore/toxcore/toxav/audio.m new file mode 100644 index 0000000..7792fc8 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/audio.m @@ -0,0 +1,504 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ +#include "audio.h" + +#include +#include +#include + +#include "rtp.h" + +#include "../toxcore/ccompat.h" +#include "../toxcore/logger.h" +#include "../toxcore/mono_time.h" + +static struct JitterBuffer *jbuf_new(uint32_t capacity); +static void jbuf_clear(struct JitterBuffer *q); +static void jbuf_free(struct JitterBuffer *q); +static int jbuf_write(const Logger *log, struct JitterBuffer *q, struct RTPMessage *m); +static struct RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success); +static OpusEncoder *create_audio_encoder(const Logger *log, uint32_t bit_rate, uint32_t sampling_rate, + uint8_t channel_count); +static bool reconfigure_audio_encoder(const Logger *log, OpusEncoder **e, uint32_t new_br, uint32_t new_sr, + uint8_t new_ch, uint32_t *old_br, uint32_t *old_sr, uint8_t *old_ch); +static bool reconfigure_audio_decoder(ACSession *ac, uint32_t sampling_rate, uint8_t channels); + + + +ACSession *ac_new(Mono_Time *mono_time, const Logger *log, ToxAV *av, uint32_t friend_number, + toxav_audio_receive_frame_cb *cb, void *cb_data) +{ + ACSession *ac = (ACSession *)calloc(1, sizeof(ACSession)); + + if (ac == nullptr) { + LOGGER_WARNING(log, "Allocation failed! Application might misbehave!"); + return nullptr; + } + + if (create_recursive_mutex(ac->queue_mutex) != 0) { + LOGGER_WARNING(log, "Failed to create recursive mutex!"); + free(ac); + return nullptr; + } + + int status; + ac->decoder = opus_decoder_create(AUDIO_DECODER_START_SAMPLE_RATE, AUDIO_DECODER_START_CHANNEL_COUNT, &status); + + if (status != OPUS_OK) { + LOGGER_ERROR(log, "Error while starting audio decoder: %s", opus_strerror(status)); + goto BASE_CLEANUP; + } + + ac->j_buf = jbuf_new(AUDIO_JITTERBUFFER_COUNT); + + if (ac->j_buf == nullptr) { + LOGGER_WARNING(log, "Jitter buffer creaton failed!"); + opus_decoder_destroy(ac->decoder); + goto BASE_CLEANUP; + } + + ac->mono_time = mono_time; + ac->log = log; + + /* Initialize encoders with default values */ + ac->encoder = create_audio_encoder(log, AUDIO_START_BITRATE, AUDIO_START_SAMPLE_RATE, AUDIO_START_CHANNEL_COUNT); + + if (ac->encoder == nullptr) { + goto DECODER_CLEANUP; + } + + ac->le_bit_rate = AUDIO_START_BITRATE; + ac->le_sample_rate = AUDIO_START_SAMPLE_RATE; + ac->le_channel_count = AUDIO_START_CHANNEL_COUNT; + + ac->ld_channel_count = AUDIO_DECODER_START_CHANNEL_COUNT; + ac->ld_sample_rate = AUDIO_DECODER_START_SAMPLE_RATE; + ac->ldrts = 0; /* Make it possible to reconfigure straight away */ + + /* These need to be set in order to properly + * do error correction with opus */ + ac->lp_frame_duration = AUDIO_MAX_FRAME_DURATION_MS; + ac->lp_sampling_rate = AUDIO_DECODER_START_SAMPLE_RATE; + ac->lp_channel_count = AUDIO_DECODER_START_CHANNEL_COUNT; + + ac->av = av; + ac->friend_number = friend_number; + ac->acb = cb; + ac->acb_user_data = cb_data; + + return ac; + +DECODER_CLEANUP: + opus_decoder_destroy(ac->decoder); + jbuf_free((struct JitterBuffer *)ac->j_buf); +BASE_CLEANUP: + pthread_mutex_destroy(ac->queue_mutex); + free(ac); + return nullptr; +} + +void ac_kill(ACSession *ac) +{ + if (ac == nullptr) { + return; + } + + opus_encoder_destroy(ac->encoder); + opus_decoder_destroy(ac->decoder); + jbuf_free((struct JitterBuffer *)ac->j_buf); + + pthread_mutex_destroy(ac->queue_mutex); + + LOGGER_DEBUG(ac->log, "Terminated audio handler: %p", (void *)ac); + free(ac); +} + +void ac_iterate(ACSession *ac) +{ + if (ac == nullptr) { + return; + } + + /* TODO: fix this and jitter buffering */ + + /* Enough space for the maximum frame size (120 ms 48 KHz stereo audio) */ + int16_t *temp_audio_buffer = (int16_t *)malloc(AUDIO_MAX_BUFFER_SIZE_PCM16 * AUDIO_MAX_CHANNEL_COUNT * sizeof(int16_t)); + + if (temp_audio_buffer == nullptr) { + LOGGER_ERROR(ac->log, "Failed to allocate memory for audio buffer"); + return; + } + + pthread_mutex_lock(ac->queue_mutex); + struct JitterBuffer *const j_buf = (struct JitterBuffer *)ac->j_buf; + + int rc = 0; + + for (struct RTPMessage *msg = jbuf_read(j_buf, &rc); msg != nullptr || rc == 2; msg = jbuf_read(j_buf, &rc)) { + pthread_mutex_unlock(ac->queue_mutex); + + if (rc == 2) { + LOGGER_DEBUG(ac->log, "OPUS correction"); + const int fs = (ac->lp_sampling_rate * ac->lp_frame_duration) / 1000; + rc = opus_decode(ac->decoder, nullptr, 0, temp_audio_buffer, fs, 1); + } else { + assert(msg->len > 4); + + /* Pick up sampling rate from packet */ + memcpy(&ac->lp_sampling_rate, msg->data, 4); + ac->lp_sampling_rate = net_ntohl(ac->lp_sampling_rate); + + ac->lp_channel_count = opus_packet_get_nb_channels(msg->data + 4); + + /* NOTE: even though OPUS supports decoding mono frames with stereo decoder and vice versa, + * it didn't work quite well. + */ + if (!reconfigure_audio_decoder(ac, ac->lp_sampling_rate, ac->lp_channel_count)) { + LOGGER_WARNING(ac->log, "Failed to reconfigure decoder!"); + free(msg); + pthread_mutex_lock(ac->queue_mutex); + continue; + } + + /* + * frame_size = opus_decode(dec, packet, len, decoded, max_size, 0); + * where + * packet is the byte array containing the compressed data + * len is the exact number of bytes contained in the packet + * decoded is the decoded audio data in opus_int16 (or float for opus_decode_float()) + * max_size is the max duration of the frame in samples (per channel) that can fit + * into the decoded_frame array + */ + rc = opus_decode(ac->decoder, msg->data + 4, msg->len - 4, temp_audio_buffer, 5760, 0); + free(msg); + } + + if (rc < 0) { + LOGGER_WARNING(ac->log, "Decoding error: %s", opus_strerror(rc)); + } else if (ac->acb != nullptr) { + ac->lp_frame_duration = (rc * 1000) / ac->lp_sampling_rate; + + ac->acb(ac->av, ac->friend_number, temp_audio_buffer, rc, ac->lp_channel_count, + ac->lp_sampling_rate, ac->acb_user_data); + } + + free(temp_audio_buffer); + + return; + } + + pthread_mutex_unlock(ac->queue_mutex); + + free(temp_audio_buffer); +} + +int ac_queue_message(Mono_Time *mono_time, void *acp, struct RTPMessage *msg) +{ + if (acp == nullptr || msg == nullptr) { + free(msg); + return -1; + } + + ACSession *ac = (ACSession *)acp; + + if ((msg->header.pt & 0x7f) == (RTP_TYPE_AUDIO + 2) % 128) { + LOGGER_WARNING(ac->log, "Got dummy!"); + free(msg); + return 0; + } + + if ((msg->header.pt & 0x7f) != RTP_TYPE_AUDIO % 128) { + LOGGER_WARNING(ac->log, "Invalid payload type!"); + free(msg); + return -1; + } + + pthread_mutex_lock(ac->queue_mutex); + const int rc = jbuf_write(ac->log, (struct JitterBuffer *)ac->j_buf, msg); + pthread_mutex_unlock(ac->queue_mutex); + + if (rc == -1) { + LOGGER_WARNING(ac->log, "Could not queue the message!"); + free(msg); + return -1; + } + + return 0; +} + +int ac_reconfigure_encoder(ACSession *ac, uint32_t bit_rate, uint32_t sampling_rate, uint8_t channels) +{ + if (ac == nullptr || !reconfigure_audio_encoder( + ac->log, &ac->encoder, bit_rate, + sampling_rate, channels, + &ac->le_bit_rate, + &ac->le_sample_rate, + &ac->le_channel_count)) { + return -1; + } + + return 0; +} + + + +struct JitterBuffer { + struct RTPMessage **queue; + uint32_t size; + uint32_t capacity; + uint16_t bottom; + uint16_t top; +}; + +static struct JitterBuffer *jbuf_new(uint32_t capacity) +{ + unsigned int size = 1; + + while (size <= (capacity * 4)) { + size *= 2; + } + + struct JitterBuffer *q = (struct JitterBuffer *)calloc(1, sizeof(struct JitterBuffer)); + + if (q == nullptr) { + return nullptr; + } + + q->queue = (struct RTPMessage **)calloc(size, sizeof(struct RTPMessage *)); + + if (q->queue == nullptr) { + free(q); + return nullptr; + } + + q->size = size; + q->capacity = capacity; + return q; +} +static void jbuf_clear(struct JitterBuffer *q) +{ + while (q->bottom != q->top) { + free(q->queue[q->bottom % q->size]); + q->queue[q->bottom % q->size] = nullptr; + ++q->bottom; + } +} +static void jbuf_free(struct JitterBuffer *q) +{ + if (q == nullptr) { + return; + } + + jbuf_clear(q); + free(q->queue); + free(q); +} +static int jbuf_write(const Logger *log, struct JitterBuffer *q, struct RTPMessage *m) +{ + const uint16_t sequnum = m->header.sequnum; + + const unsigned int num = sequnum % q->size; + + if ((uint32_t)(sequnum - q->bottom) > q->size) { + LOGGER_DEBUG(log, "Clearing filled jitter buffer: %p", (void *)q); + + jbuf_clear(q); + q->bottom = sequnum - q->capacity; + q->queue[num] = m; + q->top = sequnum + 1; + return 0; + } + + if (q->queue[num] != nullptr) { + return -1; + } + + q->queue[num] = m; + + if ((sequnum - q->bottom) >= (q->top - q->bottom)) { + q->top = sequnum + 1; + } + + return 0; +} +static struct RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success) +{ + if (q->top == q->bottom) { + *success = 0; + return nullptr; + } + + const unsigned int num = q->bottom % q->size; + + if (q->queue[num] != nullptr) { + struct RTPMessage *ret = q->queue[num]; + q->queue[num] = nullptr; + ++q->bottom; + *success = 1; + return ret; + } + + if ((uint32_t)(q->top - q->bottom) > q->capacity) { + ++q->bottom; + *success = 2; + return nullptr; + } + + *success = 0; + return nullptr; +} +static OpusEncoder *create_audio_encoder(const Logger *log, uint32_t bit_rate, uint32_t sampling_rate, + uint8_t channel_count) +{ + int status = OPUS_OK; + /* + * OPUS_APPLICATION_VOIP Process signal for improved speech intelligibility + * OPUS_APPLICATION_AUDIO Favor faithfulness to the original input + * OPUS_APPLICATION_RESTRICTED_LOWDELAY Configure the minimum possible coding delay + */ + OpusEncoder *rc = opus_encoder_create(sampling_rate, channel_count, OPUS_APPLICATION_VOIP, &status); + + if (status != OPUS_OK) { + LOGGER_ERROR(log, "Error while starting audio encoder: %s", opus_strerror(status)); + return nullptr; + } + + + /* + * Rates from 500 to 512000 bits per second are meaningful as well as the special + * values OPUS_BITRATE_AUTO and OPUS_BITRATE_MAX. The value OPUS_BITRATE_MAX can + * be used to cause the codec to use as much rate as it can, which is useful for + * controlling the rate by adjusting the output buffer size. + * + * Parameters: + * `[in]` `x` `opus_int32`: bitrate in bits per second. + */ + status = opus_encoder_ctl(rc, OPUS_SET_BITRATE(bit_rate)); + + if (status != OPUS_OK) { + LOGGER_ERROR(log, "Error while setting encoder ctl: %s", opus_strerror(status)); + goto FAILURE; + } + + + /* + * Configures the encoder's use of inband forward error correction. + * Note: + * This is only applicable to the LPC layer + * Parameters: + * `[in]` `x` `int`: FEC flag, 0 (disabled) is default + */ + /* Enable in-band forward error correction in codec */ + status = opus_encoder_ctl(rc, OPUS_SET_INBAND_FEC(1)); + + if (status != OPUS_OK) { + LOGGER_ERROR(log, "Error while setting encoder ctl: %s", opus_strerror(status)); + goto FAILURE; + } + + + /* + * Configures the encoder's expected packet loss percentage. + * Higher values with trigger progressively more loss resistant behavior in + * the encoder at the expense of quality at a given bitrate in the lossless case, + * but greater quality under loss. + * Parameters: + * `[in]` `x` `int`: Loss percentage in the range 0-100, inclusive. + */ + /* Make codec resistant to up to 10% packet loss + * NOTE This could also be adjusted on the fly, rather than hard-coded, + * with feedback from the receiving client. + */ + status = opus_encoder_ctl(rc, OPUS_SET_PACKET_LOSS_PERC(AUDIO_OPUS_PACKET_LOSS_PERC)); + + if (status != OPUS_OK) { + LOGGER_ERROR(log, "Error while setting encoder ctl: %s", opus_strerror(status)); + goto FAILURE; + } + + + /* + * Configures the encoder's computational complexity. + * + * The supported range is 0-10 inclusive with 10 representing the highest complexity. + * The default value is 10. + * + * Parameters: + * `[in]` `x` `int`: 0-10, inclusive + */ + /* Set algorithm to the highest complexity, maximizing compression */ + status = opus_encoder_ctl(rc, OPUS_SET_COMPLEXITY(AUDIO_OPUS_COMPLEXITY)); + + if (status != OPUS_OK) { + LOGGER_ERROR(log, "Error while setting encoder ctl: %s", opus_strerror(status)); + goto FAILURE; + } + + return rc; + +FAILURE: + opus_encoder_destroy(rc); + return nullptr; +} + +static bool reconfigure_audio_encoder(const Logger *log, OpusEncoder **e, uint32_t new_br, uint32_t new_sr, + uint8_t new_ch, uint32_t *old_br, uint32_t *old_sr, uint8_t *old_ch) +{ + /* Values are checked in toxav.c */ + if (*old_sr != new_sr || *old_ch != new_ch) { + OpusEncoder *new_encoder = create_audio_encoder(log, new_br, new_sr, new_ch); + + if (new_encoder == nullptr) { + return false; + } + + opus_encoder_destroy(*e); + *e = new_encoder; + } else if (*old_br == new_br) { + return true; /* Nothing changed */ + } + + const int status = opus_encoder_ctl(*e, OPUS_SET_BITRATE(new_br)); + + if (status != OPUS_OK) { + LOGGER_ERROR(log, "Error while setting encoder ctl: %s", opus_strerror(status)); + return false; + } + + *old_br = new_br; + *old_sr = new_sr; + *old_ch = new_ch; + + LOGGER_DEBUG(log, "Reconfigured audio encoder br: %d sr: %d cc:%d", new_br, new_sr, new_ch); + return true; +} + +static bool reconfigure_audio_decoder(ACSession *ac, uint32_t sampling_rate, uint8_t channels) +{ + if (sampling_rate != ac->ld_sample_rate || channels != ac->ld_channel_count) { + if (current_time_monotonic(ac->mono_time) - ac->ldrts < 500) { + return false; + } + + int status; + OpusDecoder *new_dec = opus_decoder_create(sampling_rate, channels, &status); + + if (status != OPUS_OK) { + LOGGER_ERROR(ac->log, "Error while starting audio decoder(%d %d): %s", sampling_rate, channels, opus_strerror(status)); + return false; + } + + ac->ld_sample_rate = sampling_rate; + ac->ld_channel_count = channels; + ac->ldrts = current_time_monotonic(ac->mono_time); + + opus_decoder_destroy(ac->decoder); + ac->decoder = new_dec; + + LOGGER_DEBUG(ac->log, "Reconfigured audio decoder sr: %d cc: %d", sampling_rate, channels); + } + + return true; +} diff --git a/local_pod_repo/toxcore/toxcore/toxav/bwcontroller.h b/local_pod_repo/toxcore/toxcore/toxav/bwcontroller.h new file mode 100644 index 0000000..628b385 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/bwcontroller.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ +#ifndef C_TOXCORE_TOXAV_BWCONTROLLER_H +#define C_TOXCORE_TOXAV_BWCONTROLLER_H + +#include "../toxcore/Messenger.h" +#include "../toxcore/tox.h" + +typedef struct BWController BWController; + +typedef void m_cb(BWController *bwc, uint32_t friend_number, float todo, void *user_data); + +BWController *bwc_new(Messenger *m, Tox *tox, uint32_t friendnumber, m_cb *mcb, void *mcb_user_data, + Mono_Time *bwc_mono_time); + +void bwc_kill(BWController *bwc); + +void bwc_add_lost(BWController *bwc, uint32_t bytes_lost); +void bwc_add_recv(BWController *bwc, uint32_t recv_bytes); + +#endif // C_TOXCORE_TOXAV_BWCONTROLLER_H diff --git a/local_pod_repo/toxcore/toxcore/toxav/bwcontroller.m b/local_pod_repo/toxcore/toxcore/toxav/bwcontroller.m new file mode 100644 index 0000000..be2cd6b --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/bwcontroller.m @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ +#include "bwcontroller.h" + +#include +#include +#include +#include + +#include "ring_buffer.h" + +#include "../toxcore/ccompat.h" +#include "../toxcore/logger.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/util.h" + +#define BWC_PACKET_ID 196 +#define BWC_SEND_INTERVAL_MS 950 // 0.95s +#define BWC_AVG_PKT_COUNT 20 +#define BWC_AVG_LOSS_OVER_CYCLES_COUNT 30 + +typedef struct BWCCycle { + uint32_t last_recv_timestamp; /* Last recv update time stamp */ + uint32_t last_sent_timestamp; /* Last sent update time stamp */ + uint32_t last_refresh_timestamp; /* Last refresh time stamp */ + + uint32_t lost; + uint32_t recv; +} BWCCycle; + +typedef struct BWCRcvPkt { + uint32_t packet_length_array[BWC_AVG_PKT_COUNT]; + RingBuffer *rb; +} BWCRcvPkt; + +struct BWController { + m_cb *mcb; + void *mcb_user_data; + + Messenger *m; + Tox *tox; + uint32_t friend_number; + + BWCCycle cycle; + + BWCRcvPkt rcvpkt; /* To calculate average received packet (this means split parts, not the full message!) */ + + uint32_t packet_loss_counted_cycles; + Mono_Time *bwc_mono_time; +}; + +struct BWCMessage { + uint32_t lost; + uint32_t recv; +}; + +static int bwc_handle_data(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object); +static int bwc_send_custom_lossy_packet(Tox *tox, int32_t friendnumber, const uint8_t *data, uint32_t length); +static void send_update(BWController *bwc); + +BWController *bwc_new(Messenger *m, Tox *tox, uint32_t friendnumber, m_cb *mcb, void *mcb_user_data, + Mono_Time *bwc_mono_time) +{ + BWController *retu = (BWController *)calloc(1, sizeof(BWController)); + + if (retu == nullptr) { + return nullptr; + } + + LOGGER_DEBUG(m->log, "Creating bandwidth controller"); + retu->mcb = mcb; + retu->mcb_user_data = mcb_user_data; + retu->m = m; + retu->friend_number = friendnumber; + retu->bwc_mono_time = bwc_mono_time; + const uint64_t now = current_time_monotonic(bwc_mono_time); + retu->cycle.last_sent_timestamp = now; + retu->cycle.last_refresh_timestamp = now; + retu->tox = tox; + retu->rcvpkt.rb = rb_new(BWC_AVG_PKT_COUNT); + retu->cycle.lost = 0; + retu->cycle.recv = 0; + retu->packet_loss_counted_cycles = 0; + + /* Fill with zeros */ + for (int i = 0; i < BWC_AVG_PKT_COUNT; ++i) { + rb_write(retu->rcvpkt.rb, &retu->rcvpkt.packet_length_array[i]); + } + + m_callback_rtp_packet(m, friendnumber, BWC_PACKET_ID, bwc_handle_data, retu); + return retu; +} + +void bwc_kill(BWController *bwc) +{ + if (bwc == nullptr) { + return; + } + + m_callback_rtp_packet(bwc->m, bwc->friend_number, BWC_PACKET_ID, nullptr, nullptr); + rb_kill(bwc->rcvpkt.rb); + free(bwc); +} + +void bwc_add_lost(BWController *bwc, uint32_t bytes_lost) +{ + if (bwc == nullptr) { + return; + } + + if (bytes_lost > 0) { + LOGGER_DEBUG(bwc->m->log, "BWC lost(1): %d", (int)bytes_lost); + bwc->cycle.lost += bytes_lost; + send_update(bwc); + } +} + +void bwc_add_recv(BWController *bwc, uint32_t recv_bytes) +{ + if (bwc == nullptr || recv_bytes == 0) { + return; + } + + ++bwc->packet_loss_counted_cycles; + bwc->cycle.recv += recv_bytes; + send_update(bwc); +} + +static void send_update(BWController *bwc) +{ + if (bwc->packet_loss_counted_cycles > BWC_AVG_LOSS_OVER_CYCLES_COUNT && + current_time_monotonic(bwc->bwc_mono_time) - bwc->cycle.last_sent_timestamp > BWC_SEND_INTERVAL_MS) { + bwc->packet_loss_counted_cycles = 0; + + if (bwc->cycle.lost != 0) { + LOGGER_DEBUG(bwc->m->log, "%p Sent update rcv: %u lost: %u percent: %f %%", + (void *)bwc, bwc->cycle.recv, bwc->cycle.lost, + ((double)bwc->cycle.lost / (bwc->cycle.recv + bwc->cycle.lost)) * 100.0); + uint8_t bwc_packet[sizeof(struct BWCMessage) + 1]; + size_t offset = 0; + + bwc_packet[offset] = BWC_PACKET_ID; // set packet ID + ++offset; + + offset += net_pack_u32(bwc_packet + offset, bwc->cycle.lost); + offset += net_pack_u32(bwc_packet + offset, bwc->cycle.recv); + assert(offset == sizeof(bwc_packet)); + + if (bwc_send_custom_lossy_packet(bwc->tox, bwc->friend_number, bwc_packet, sizeof(bwc_packet)) == -1) { + char *netstrerror = net_new_strerror(net_error()); + char *stdstrerror = net_new_strerror(errno); + LOGGER_WARNING(bwc->m->log, "BWC send failed (len: %u)! std error: %s, net error %s", + (unsigned)sizeof(bwc_packet), stdstrerror, netstrerror); + net_kill_strerror(stdstrerror); + net_kill_strerror(netstrerror); + } + } + + bwc->cycle.last_sent_timestamp = current_time_monotonic(bwc->bwc_mono_time); + bwc->cycle.lost = 0; + bwc->cycle.recv = 0; + } +} + +static int on_update(BWController *bwc, const struct BWCMessage *msg) +{ + LOGGER_DEBUG(bwc->m->log, "%p Got update from peer", (void *)bwc); + + /* Peers sent update too soon */ + if (bwc->cycle.last_recv_timestamp + BWC_SEND_INTERVAL_MS > current_time_monotonic(bwc->bwc_mono_time)) { + LOGGER_INFO(bwc->m->log, "%p Rejecting extra update", (void *)bwc); + return -1; + } + + bwc->cycle.last_recv_timestamp = current_time_monotonic(bwc->bwc_mono_time); + + const uint32_t lost = msg->lost; + + if (lost != 0 && bwc->mcb != nullptr) { + const uint32_t recv = msg->recv; + LOGGER_DEBUG(bwc->m->log, "recved: %u lost: %u percentage: %f %%", recv, lost, + ((double)lost / (recv + lost)) * 100.0); + bwc->mcb(bwc, bwc->friend_number, + (float)lost / (recv + lost), + bwc->mcb_user_data); + } + + return 0; +} + +/* + * return -1 on failure, 0 on success + * + */ +static int bwc_send_custom_lossy_packet(Tox *tox, int32_t friendnumber, const uint8_t *data, uint32_t length) +{ + Tox_Err_Friend_Custom_Packet error; + tox_friend_send_lossy_packet(tox, friendnumber, data, (size_t)length, &error); + + if (error == TOX_ERR_FRIEND_CUSTOM_PACKET_OK) { + return 0; + } + + return -1; +} + +static int bwc_handle_data(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object) +{ + if (length - 1 != sizeof(struct BWCMessage)) { + return -1; + } + + size_t offset = 1; // Ignore packet id. + struct BWCMessage msg; + offset += net_unpack_u32(data + offset, &msg.lost); + offset += net_unpack_u32(data + offset, &msg.recv); + assert(offset == length); + + return on_update((BWController *)object, &msg); +} diff --git a/local_pod_repo/toxcore/toxcore/toxav/groupav.h b/local_pod_repo/toxcore/toxcore/toxav/groupav.h new file mode 100644 index 0000000..c74d6da --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/groupav.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ +#ifndef C_TOXCORE_TOXAV_GROUPAV_H +#define C_TOXCORE_TOXAV_GROUPAV_H + +// Audio encoding/decoding +#include "opus.h" + +#include "../toxcore/group.h" +#include "../toxcore/tox.h" + +#define GROUP_AUDIO_PACKET_ID 192 + +// TODO(iphydf): Use this better typed one instead of the void-pointer one below. +// typedef void audio_data_cb(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, +// uint32_t samples, uint8_t channels, uint32_t sample_rate, void *userdata); +typedef void audio_data_cb(void *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, + uint32_t samples, uint8_t channels, uint32_t sample_rate, void *userdata); + +/** @brief Create and connect to a new toxav group. + * + * @return group number on success. + * @retval -1 on failure. + */ +int add_av_groupchat(const Logger *log, Tox *tox, Group_Chats *g_c, audio_data_cb *audio_callback, void *userdata); + +/** @brief Join a AV group (you need to have been invited first). + * + * @return group number on success + * @retval -1 on failure. + */ +int join_av_groupchat(const Logger *log, Tox *tox, Group_Chats *g_c, uint32_t friendnumber, const uint8_t *data, + uint16_t length, audio_data_cb *audio_callback, void *userdata); + + +/** @brief Send audio to the group chat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int group_send_audio(Group_Chats *g_c, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, + uint32_t sample_rate); + +/** @brief Enable A/V in a groupchat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int groupchat_enable_av(const Logger *log, Tox *tox, Group_Chats *g_c, uint32_t groupnumber, + audio_data_cb *audio_callback, void *userdata); + +/** @brief Disable A/V in a groupchat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int groupchat_disable_av(const Group_Chats *g_c, uint32_t groupnumber); + +/** Return whether A/V is enabled in the groupchat. */ +bool groupchat_av_enabled(const Group_Chats *g_c, uint32_t groupnumber); + +#endif // C_TOXCORE_TOXAV_GROUPAV_H diff --git a/local_pod_repo/toxcore/toxcore/toxav/groupav.m b/local_pod_repo/toxcore/toxcore/toxav/groupav.m new file mode 100644 index 0000000..583304c --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/groupav.m @@ -0,0 +1,657 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ +#include "groupav.h" + +#include +#include + +#include "../toxcore/ccompat.h" +#include "../toxcore/logger.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/tox_struct.h" +#include "../toxcore/util.h" + +#define GROUP_JBUF_SIZE 6 +#define GROUP_JBUF_DEAD_SECONDS 4 + +typedef struct Group_Audio_Packet { + uint16_t sequnum; + uint16_t length; + uint8_t *data; +} Group_Audio_Packet; + +typedef struct Group_JitterBuffer { + Group_Audio_Packet **queue; + uint32_t size; + uint32_t capacity; + uint16_t bottom; + uint16_t top; + uint64_t last_queued_time; +} Group_JitterBuffer; + +static void free_audio_packet(Group_Audio_Packet *pk) +{ + if (pk == nullptr) { + return; + } + + free(pk->data); + free(pk); +} + +static Group_JitterBuffer *create_queue(unsigned int capacity) +{ + unsigned int size = 1; + + while (size <= capacity) { + size *= 2; + } + + Group_JitterBuffer *q = (Group_JitterBuffer *)calloc(1, sizeof(Group_JitterBuffer)); + + if (q == nullptr) { + return nullptr; + } + + q->queue = (Group_Audio_Packet **)calloc(size, sizeof(Group_Audio_Packet *)); + + if (q->queue == nullptr) { + free(q); + return nullptr; + } + + q->size = size; + q->capacity = capacity; + return q; +} + +static void clear_queue(Group_JitterBuffer *q) +{ + while (q->bottom != q->top) { + const size_t idx = q->bottom % q->size; + free_audio_packet(q->queue[idx]); + q->queue[idx] = nullptr; + ++q->bottom; + } +} + +static void terminate_queue(Group_JitterBuffer *q) +{ + if (q == nullptr) { + return; + } + + clear_queue(q); + free(q->queue); + free(q); +} + +/** @retval 0 if packet was queued + * @retval -1 if it wasn't. + */ +static int queue(Group_JitterBuffer *q, const Mono_Time *mono_time, Group_Audio_Packet *pk) +{ + const uint16_t sequnum = pk->sequnum; + + const unsigned int num = sequnum % q->size; + + if (!mono_time_is_timeout(mono_time, q->last_queued_time, GROUP_JBUF_DEAD_SECONDS)) { + if ((uint32_t)(sequnum - q->bottom) > (1 << 15)) { + /* Drop old packet. */ + return -1; + } + } + + if ((uint32_t)(sequnum - q->bottom) > q->size) { + clear_queue(q); + q->bottom = sequnum - q->capacity; + q->queue[num] = pk; + q->top = sequnum + 1; + q->last_queued_time = mono_time_get(mono_time); + return 0; + } + + if (q->queue[num] != nullptr) { + return -1; + } + + q->queue[num] = pk; + + if ((sequnum - q->bottom) >= (q->top - q->bottom)) { + q->top = sequnum + 1; + } + + q->last_queued_time = mono_time_get(mono_time); + return 0; +} + +/** + * success is: + * - 0 when there is nothing to dequeue + * - 1 when there's a good packet + * - 2 when there's a lost packet + */ +static Group_Audio_Packet *dequeue(Group_JitterBuffer *q, int *success) +{ + if (q->top == q->bottom) { + *success = 0; + return nullptr; + } + + const unsigned int num = q->bottom % q->size; + + if (q->queue[num] != nullptr) { + Group_Audio_Packet *ret = q->queue[num]; + q->queue[num] = nullptr; + ++q->bottom; + *success = 1; + return ret; + } + + if ((uint32_t)(q->top - q->bottom) > q->capacity) { + ++q->bottom; + *success = 2; + return nullptr; + } + + *success = 0; + return nullptr; +} + +typedef struct Group_AV { + const Logger *log; + Tox *tox; + Group_Chats *g_c; + OpusEncoder *audio_encoder; + + unsigned int audio_channels; + unsigned int audio_sample_rate; + unsigned int audio_bitrate; + + uint16_t audio_sequnum; + + audio_data_cb *audio_data; + void *userdata; +} Group_AV; + +typedef struct Group_Peer_AV { + const Mono_Time *mono_time; + Group_JitterBuffer *buffer; + + OpusDecoder *audio_decoder; + int decoder_channels; + unsigned int last_packet_samples; +} Group_Peer_AV; + +static void kill_group_av(Group_AV *group_av) +{ + if (group_av->audio_encoder != nullptr) { + opus_encoder_destroy(group_av->audio_encoder); + } + + free(group_av); +} + +static int recreate_encoder(Group_AV *group_av) +{ + if (group_av->audio_encoder != nullptr) { + opus_encoder_destroy(group_av->audio_encoder); + group_av->audio_encoder = nullptr; + } + + int rc = OPUS_OK; + group_av->audio_encoder = opus_encoder_create(group_av->audio_sample_rate, group_av->audio_channels, + OPUS_APPLICATION_AUDIO, &rc); + + if (rc != OPUS_OK) { + LOGGER_ERROR(group_av->log, "Error while starting audio encoder: %s", opus_strerror(rc)); + group_av->audio_encoder = nullptr; + return -1; + } + + rc = opus_encoder_ctl(group_av->audio_encoder, OPUS_SET_BITRATE(group_av->audio_bitrate)); + + if (rc != OPUS_OK) { + LOGGER_ERROR(group_av->log, "Error while setting encoder ctl: %s", opus_strerror(rc)); + opus_encoder_destroy(group_av->audio_encoder); + group_av->audio_encoder = nullptr; + return -1; + } + + rc = opus_encoder_ctl(group_av->audio_encoder, OPUS_SET_COMPLEXITY(10)); + + if (rc != OPUS_OK) { + LOGGER_ERROR(group_av->log, "Error while setting encoder ctl: %s", opus_strerror(rc)); + opus_encoder_destroy(group_av->audio_encoder); + group_av->audio_encoder = nullptr; + return -1; + } + + return 0; +} + +static Group_AV *new_group_av(const Logger *log, Tox *tox, Group_Chats *g_c, audio_data_cb *audio_callback, + void *userdata) +{ + if (g_c == nullptr) { + return nullptr; + } + + Group_AV *group_av = (Group_AV *)calloc(1, sizeof(Group_AV)); + + if (group_av == nullptr) { + return nullptr; + } + + group_av->log = log; + group_av->tox = tox; + group_av->g_c = g_c; + + group_av->audio_data = audio_callback; + group_av->userdata = userdata; + + return group_av; +} + +static void group_av_peer_new(void *object, uint32_t groupnumber, uint32_t friendgroupnumber) +{ + const Group_AV *group_av = (const Group_AV *)object; + Group_Peer_AV *peer_av = (Group_Peer_AV *)calloc(1, sizeof(Group_Peer_AV)); + + if (peer_av == nullptr) { + return; + } + + peer_av->mono_time = g_mono_time(group_av->g_c); + peer_av->buffer = create_queue(GROUP_JBUF_SIZE); + + if (group_peer_set_object(group_av->g_c, groupnumber, friendgroupnumber, peer_av) == -1) { + free(peer_av); + } +} + +static void group_av_peer_delete(void *object, uint32_t groupnumber, void *peer_object) +{ + Group_Peer_AV *peer_av = (Group_Peer_AV *)peer_object; + + if (peer_av == nullptr) { + return; + } + + if (peer_av->audio_decoder != nullptr) { + opus_decoder_destroy(peer_av->audio_decoder); + } + + terminate_queue(peer_av->buffer); + free(peer_object); +} + +static void group_av_groupchat_delete(void *object, uint32_t groupnumber) +{ + if (object != nullptr) { + kill_group_av((Group_AV *)object); + } +} + +static int decode_audio_packet(Group_AV *group_av, Group_Peer_AV *peer_av, uint32_t groupnumber, + uint32_t friendgroupnumber) +{ + if (group_av == nullptr || peer_av == nullptr) { + return -1; + } + + int success; + Group_Audio_Packet *pk = dequeue(peer_av->buffer, &success); + + if (success == 0) { + return -1; + } + + int16_t *out_audio = nullptr; + int out_audio_samples = 0; + + const unsigned int sample_rate = 48000; + + if (success == 1) { + const int channels = opus_packet_get_nb_channels(pk->data); + + if (channels == OPUS_INVALID_PACKET) { + free_audio_packet(pk); + return -1; + } + + if (channels != 1 && channels != 2) { + free_audio_packet(pk); + return -1; + } + + if (channels != peer_av->decoder_channels) { + if (peer_av->audio_decoder != nullptr) { + opus_decoder_destroy(peer_av->audio_decoder); + peer_av->audio_decoder = nullptr; + } + + int rc; + peer_av->audio_decoder = opus_decoder_create(sample_rate, channels, &rc); + + if (rc != OPUS_OK) { + LOGGER_ERROR(group_av->log, "Error while starting audio decoder: %s", opus_strerror(rc)); + free_audio_packet(pk); + return -1; + } + + peer_av->decoder_channels = channels; + } + + const int num_samples = opus_decoder_get_nb_samples(peer_av->audio_decoder, pk->data, pk->length); + + out_audio = (int16_t *)malloc(num_samples * peer_av->decoder_channels * sizeof(int16_t)); + + if (out_audio == nullptr) { + free_audio_packet(pk); + return -1; + } + + out_audio_samples = opus_decode(peer_av->audio_decoder, pk->data, pk->length, out_audio, num_samples, 0); + free_audio_packet(pk); + + if (out_audio_samples <= 0) { + free(out_audio); + return -1; + } + + peer_av->last_packet_samples = out_audio_samples; + } else { + if (peer_av->audio_decoder == nullptr) { + return -1; + } + + if (peer_av->last_packet_samples == 0) { + return -1; + } + + out_audio = (int16_t *)malloc(peer_av->last_packet_samples * peer_av->decoder_channels * sizeof(int16_t)); + + if (out_audio == nullptr) { + free_audio_packet(pk); + return -1; + } + + out_audio_samples = opus_decode(peer_av->audio_decoder, nullptr, 0, out_audio, peer_av->last_packet_samples, 1); + + if (out_audio_samples <= 0) { + free(out_audio); + return -1; + } + } + + if (out_audio != nullptr) { + + if (group_av->audio_data != nullptr) { + group_av->audio_data(group_av->tox, groupnumber, friendgroupnumber, out_audio, out_audio_samples, + peer_av->decoder_channels, sample_rate, group_av->userdata); + } + + free(out_audio); + return 0; + } + + return -1; +} + +static int handle_group_audio_packet(void *object, uint32_t groupnumber, uint32_t friendgroupnumber, void *peer_object, + const uint8_t *packet, uint16_t length) +{ + if (peer_object == nullptr || object == nullptr || length <= sizeof(uint16_t)) { + return -1; + } + + Group_Peer_AV *peer_av = (Group_Peer_AV *)peer_object; + + Group_Audio_Packet *pk = (Group_Audio_Packet *)calloc(1, sizeof(Group_Audio_Packet)); + + if (pk == nullptr) { + return -1; + } + + net_unpack_u16(packet, &pk->sequnum); + pk->length = length - sizeof(uint16_t); + + pk->data = (uint8_t *)malloc(pk->length); + + if (pk->data == nullptr) { + free_audio_packet(pk); + return -1; + } + + memcpy(pk->data, packet + sizeof(uint16_t), pk->length); + + if (queue(peer_av->buffer, peer_av->mono_time, pk) == -1) { + free_audio_packet(pk); + return -1; + } + + while (decode_audio_packet((Group_AV *)object, peer_av, groupnumber, friendgroupnumber) == 0) { + continue; + } + + return 0; +} + +/** @brief Enable A/V in a groupchat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int groupchat_enable_av(const Logger *log, Tox *tox, Group_Chats *g_c, uint32_t groupnumber, + audio_data_cb *audio_callback, void *userdata) +{ + if (group_get_type(g_c, groupnumber) != GROUPCHAT_TYPE_AV + || group_get_object(g_c, groupnumber) != nullptr) { + return -1; + } + + Group_AV *group_av = new_group_av(log, tox, g_c, audio_callback, userdata); + + if (group_av == nullptr) { + return -1; + } + + if (group_set_object(g_c, groupnumber, group_av) == -1 + || callback_groupchat_peer_new(g_c, groupnumber, group_av_peer_new) == -1 + || callback_groupchat_peer_delete(g_c, groupnumber, group_av_peer_delete) == -1 + || callback_groupchat_delete(g_c, groupnumber, group_av_groupchat_delete) == -1) { + kill_group_av(group_av); + return -1; + } + + const int numpeers = group_number_peers(g_c, groupnumber, false); + + if (numpeers < 0) { + kill_group_av(group_av); + return -1; + } + + for (uint32_t i = 0; i < numpeers; ++i) { + group_av_peer_new(group_av, groupnumber, i); + } + + group_lossy_packet_registerhandler(g_c, GROUP_AUDIO_PACKET_ID, &handle_group_audio_packet); + return 0; +} + +/** @brief Disable A/V in a groupchat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int groupchat_disable_av(const Group_Chats *g_c, uint32_t groupnumber) +{ + if (group_get_type(g_c, groupnumber) != GROUPCHAT_TYPE_AV) { + return -1; + } + + Group_AV *group_av = (Group_AV *)group_get_object(g_c, groupnumber); + + if (group_av == nullptr) { + return -1; + } + + const int numpeers = group_number_peers(g_c, groupnumber, false); + + if (numpeers < 0) { + kill_group_av(group_av); + return -1; + } + + for (uint32_t i = 0; i < numpeers; ++i) { + group_av_peer_delete(group_av, groupnumber, group_peer_get_object(g_c, groupnumber, i)); + group_peer_set_object(g_c, groupnumber, i, nullptr); + } + + kill_group_av(group_av); + + if (group_set_object(g_c, groupnumber, nullptr) == -1 + || callback_groupchat_peer_new(g_c, groupnumber, nullptr) == -1 + || callback_groupchat_peer_delete(g_c, groupnumber, nullptr) == -1 + || callback_groupchat_delete(g_c, groupnumber, nullptr) == -1) { + return -1; + } + + return 0; +} + +/** Return whether A/V is enabled in the groupchat. */ +bool groupchat_av_enabled(const Group_Chats *g_c, uint32_t groupnumber) +{ + return group_get_object(g_c, groupnumber) != nullptr; +} + +/** @brief Create and connect to a new toxav group. + * + * @return group number on success. + * @retval -1 on failure. + */ +int add_av_groupchat(const Logger *log, Tox *tox, Group_Chats *g_c, audio_data_cb *audio_callback, void *userdata) +{ + const int groupnumber = add_groupchat(g_c, &tox->rng, GROUPCHAT_TYPE_AV); + + if (groupnumber == -1) { + return -1; + } + + if (groupchat_enable_av(log, tox, g_c, groupnumber, audio_callback, userdata) == -1) { + del_groupchat(g_c, groupnumber, true); + return -1; + } + + return groupnumber; +} + +/** @brief Join a AV group (you need to have been invited first). + * + * @return group number on success + * @retval -1 on failure. + */ +int join_av_groupchat(const Logger *log, Tox *tox, Group_Chats *g_c, uint32_t friendnumber, const uint8_t *data, + uint16_t length, audio_data_cb *audio_callback, void *userdata) +{ + const int groupnumber = join_groupchat(g_c, friendnumber, GROUPCHAT_TYPE_AV, data, length); + + if (groupnumber == -1) { + return -1; + } + + if (groupchat_enable_av(log, tox, g_c, groupnumber, audio_callback, userdata) == -1) { + del_groupchat(g_c, groupnumber, true); + return -1; + } + + return groupnumber; +} + +/** @brief Send an encoded audio packet to the group chat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +static int send_audio_packet(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *packet, uint16_t length) +{ + if (length == 0 || length > MAX_CRYPTO_DATA_SIZE - 1 - sizeof(uint16_t)) { + return -1; + } + + const uint16_t plen = 1 + sizeof(uint16_t) + length; + + Group_AV *const group_av = (Group_AV *)group_get_object(g_c, groupnumber); + + if (group_av == nullptr) { + return -1; + } + + uint8_t data[MAX_CRYPTO_DATA_SIZE]; + uint8_t *ptr = data; + *ptr = GROUP_AUDIO_PACKET_ID; + ++ptr; + + ptr += net_pack_u16(ptr, group_av->audio_sequnum); + memcpy(ptr, packet, length); + + if (send_group_lossy_packet(g_c, groupnumber, data, plen) == -1) { + return -1; + } + + ++group_av->audio_sequnum; + return 0; +} + +/** @brief Send audio to the group chat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int group_send_audio(Group_Chats *g_c, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, + uint32_t sample_rate) +{ + Group_AV *group_av = (Group_AV *)group_get_object(g_c, groupnumber); + + if (group_av == nullptr) { + return -1; + } + + if (channels != 1 && channels != 2) { + return -1; + } + + if (sample_rate != 8000 && sample_rate != 12000 && sample_rate != 16000 && sample_rate != 24000 + && sample_rate != 48000) { + return -1; + } + + if (group_av->audio_encoder == nullptr || group_av->audio_channels != channels + || group_av->audio_sample_rate != sample_rate) { + group_av->audio_channels = channels; + group_av->audio_sample_rate = sample_rate; + + if (channels == 1) { + group_av->audio_bitrate = 32000; // TODO(mannol): add way of adjusting bitrate + } else { + group_av->audio_bitrate = 64000; // TODO(mannol): add way of adjusting bitrate + } + + if (recreate_encoder(group_av) == -1) { + return -1; + } + } + + uint8_t encoded[1024]; + const int32_t size = opus_encode(group_av->audio_encoder, pcm, samples, encoded, sizeof(encoded)); + + if (size <= 0) { + return -1; + } + + return send_audio_packet(g_c, groupnumber, encoded, size); +} diff --git a/local_pod_repo/toxcore/toxcore/toxav/msi.h b/local_pod_repo/toxcore/toxcore/toxav/msi.h new file mode 100644 index 0000000..547f4c7 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/msi.h @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ +#ifndef C_TOXCORE_TOXAV_MSI_H +#define C_TOXCORE_TOXAV_MSI_H + +#include +#include + +#include "audio.h" +#include "video.h" + +#include "../toxcore/Messenger.h" +#include "../toxcore/logger.h" + +/** + * Error codes. + */ +typedef enum MSIError { + MSI_E_NONE, + MSI_E_INVALID_MESSAGE, + MSI_E_INVALID_PARAM, + MSI_E_INVALID_STATE, + MSI_E_STRAY_MESSAGE, + MSI_E_SYSTEM, + MSI_E_HANDLE, + MSI_E_UNDISCLOSED, /* NOTE: must be last enum otherwise parsing will not work */ +} MSIError; + +/** + * Supported capabilities + */ +typedef enum MSICapabilities { + MSI_CAP_S_AUDIO = 4, /* sending audio */ + MSI_CAP_S_VIDEO = 8, /* sending video */ + MSI_CAP_R_AUDIO = 16, /* receiving audio */ + MSI_CAP_R_VIDEO = 32, /* receiving video */ +} MSICapabilities; + + +/** + * Call state identifiers. + */ +typedef enum MSICallState { + MSI_CALL_INACTIVE, /* Default */ + MSI_CALL_ACTIVE, + MSI_CALL_REQUESTING, /* when sending call invite */ + MSI_CALL_REQUESTED, /* when getting call invite */ +} MSICallState; + +/** + * Callbacks ids that handle the states + */ +typedef enum MSICallbackID { + MSI_ON_INVITE, /* Incoming call */ + MSI_ON_START, /* Call (RTP transmission) started */ + MSI_ON_END, /* Call that was active ended */ + MSI_ON_ERROR, /* On protocol error */ + MSI_ON_PEERTIMEOUT, /* Peer timed out; stop the call */ + MSI_ON_CAPABILITIES, /* Peer requested capabilities change */ +} MSICallbackID; + +/** + * The call struct. Please do not modify outside msi.c + */ +typedef struct MSICall { + struct MSISession *session; /* Session pointer */ + + MSICallState state; + uint8_t peer_capabilities; /* Peer capabilities */ + uint8_t self_capabilities; /* Self capabilities */ + uint16_t peer_vfpsz; /* Video frame piece size */ + uint32_t friend_number; /* Index of this call in MSISession */ + MSIError error; /* Last error */ + + struct ToxAVCall *av_call; /* Pointer to av call handler */ + + struct MSICall *next; + struct MSICall *prev; +} MSICall; + + +/** + * Expected return on success is 0, if any other number is + * returned the call is considered errored and will be handled + * as such which means it will be terminated without any notice. + */ +typedef int msi_action_cb(void *av, MSICall *call); + +/** + * Control session struct. Please do not modify outside msi.c + */ +typedef struct MSISession { + /* Call handlers */ + MSICall **calls; + uint32_t calls_tail; + uint32_t calls_head; + + void *av; + Messenger *messenger; + + pthread_mutex_t mutex[1]; + + msi_action_cb *invite_callback; + msi_action_cb *start_callback; + msi_action_cb *end_callback; + msi_action_cb *error_callback; + msi_action_cb *peertimeout_callback; + msi_action_cb *capabilities_callback; +} MSISession; + +/** + * Start the control session. + */ +MSISession *msi_new(Messenger *m); +/** + * Terminate control session. NOTE: all calls will be freed + */ +int msi_kill(MSISession *session, const Logger *log); +/** + * Callback setters. + */ +void msi_callback_invite(MSISession *session, msi_action_cb *callback); +void msi_callback_start(MSISession *session, msi_action_cb *callback); +void msi_callback_end(MSISession *session, msi_action_cb *callback); +void msi_callback_error(MSISession *session, msi_action_cb *callback); +void msi_callback_peertimeout(MSISession *session, msi_action_cb *callback); +void msi_callback_capabilities(MSISession *session, msi_action_cb *callback); +/** + * Send invite request to friend_number. + */ +int msi_invite(MSISession *session, MSICall **call, uint32_t friend_number, uint8_t capabilities); +/** + * Hangup call. NOTE: `call` will be freed + */ +int msi_hangup(MSICall *call); +/** + * Answer call request. + */ +int msi_answer(MSICall *call, uint8_t capabilities); +/** + * Change capabilities of the call. + */ +int msi_change_capabilities(MSICall *call, uint8_t capabilities); + +#endif // C_TOXCORE_TOXAV_MSI_H diff --git a/local_pod_repo/toxcore/toxcore/toxav/msi.m b/local_pod_repo/toxcore/toxcore/toxav/msi.m new file mode 100644 index 0000000..9b016ad --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/msi.m @@ -0,0 +1,903 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ +#include "msi.h" + +#include +#include +#include +#include + +#include "../toxcore/ccompat.h" +#include "../toxcore/logger.h" +#include "../toxcore/util.h" + +#define MSI_MAXMSG_SIZE 256 + +/** + * Protocol: + * + * `|id [1 byte]| |size [1 byte]| |data [$size bytes]| |...{repeat}| |0 {end byte}|` + */ + +typedef enum MSIHeaderID { + ID_REQUEST = 1, + ID_ERROR, + ID_CAPABILITIES, +} MSIHeaderID; + + +typedef enum MSIRequest { + REQU_INIT, + REQU_PUSH, + REQU_POP, +} MSIRequest; + + +typedef struct MSIHeaderRequest { + MSIRequest value; + bool exists; +} MSIHeaderRequest; + +typedef struct MSIHeaderError { + MSIError value; + bool exists; +} MSIHeaderError; + +typedef struct MSIHeaderCapabilities { + uint8_t value; + bool exists; +} MSIHeaderCapabilities; + + +typedef struct MSIMessage { + MSIHeaderRequest request; + MSIHeaderError error; + MSIHeaderCapabilities capabilities; +} MSIMessage; + + +static void msg_init(MSIMessage *dest, MSIRequest request); +static int msg_parse_in(const Logger *log, MSIMessage *dest, const uint8_t *data, uint16_t length); +static uint8_t *msg_parse_header_out(MSIHeaderID id, uint8_t *dest, const void *value, uint8_t value_len, + uint16_t *length); +static int send_message(Messenger *m, uint32_t friend_number, const MSIMessage *msg); +static int send_error(Messenger *m, uint32_t friend_number, MSIError error); +static bool invoke_callback(MSICall *call, MSICallbackID cb); +static MSICall *get_call(MSISession *session, uint32_t friend_number); +static MSICall *new_call(MSISession *session, uint32_t friend_number); +static void kill_call(MSICall *call); +static void on_peer_status(Messenger *m, uint32_t friend_number, uint8_t status, void *data); +static void handle_init(MSICall *call, const MSIMessage *msg); +static void handle_push(MSICall *call, const MSIMessage *msg); +static void handle_pop(MSICall *call, const MSIMessage *msg); +static void handle_msi_packet(Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, void *object); + + +/* + * Public functions + */ + +void msi_callback_invite(MSISession *session, msi_action_cb *callback) +{ + session->invite_callback = callback; +} +void msi_callback_start(MSISession *session, msi_action_cb *callback) +{ + session->start_callback = callback; +} +void msi_callback_end(MSISession *session, msi_action_cb *callback) +{ + session->end_callback = callback; +} +void msi_callback_error(MSISession *session, msi_action_cb *callback) +{ + session->error_callback = callback; +} +void msi_callback_peertimeout(MSISession *session, msi_action_cb *callback) +{ + session->peertimeout_callback = callback; +} +void msi_callback_capabilities(MSISession *session, msi_action_cb *callback) +{ + session->capabilities_callback = callback; +} + +MSISession *msi_new(Messenger *m) +{ + if (m == nullptr) { + return nullptr; + } + + MSISession *retu = (MSISession *)calloc(1, sizeof(MSISession)); + + if (retu == nullptr) { + LOGGER_ERROR(m->log, "Allocation failed! Program might misbehave!"); + return nullptr; + } + + if (create_recursive_mutex(retu->mutex) != 0) { + LOGGER_ERROR(m->log, "Failed to init mutex! Program might misbehave"); + free(retu); + return nullptr; + } + + retu->messenger = m; + + m_callback_msi_packet(m, handle_msi_packet, retu); + + /* This is called when remote terminates session */ + m_callback_connectionstatus_internal_av(m, on_peer_status, retu); + + LOGGER_DEBUG(m->log, "New msi session: %p ", (void *)retu); + return retu; +} +int msi_kill(MSISession *session, const Logger *log) +{ + if (session == nullptr) { + LOGGER_ERROR(log, "Tried to terminate non-existing session"); + return -1; + } + + m_callback_msi_packet(session->messenger, nullptr, nullptr); + + if (pthread_mutex_trylock(session->mutex) != 0) { + LOGGER_ERROR(log, "Failed to acquire lock on msi mutex"); + return -1; + } + + if (session->calls != nullptr) { + MSIMessage msg; + msg_init(&msg, REQU_POP); + + MSICall *it = get_call(session, session->calls_head); + + while (it != nullptr) { + send_message(session->messenger, it->friend_number, &msg); + MSICall *temp_it = it; + it = it->next; + kill_call(temp_it); /* This will eventually free session->calls */ + } + } + + pthread_mutex_unlock(session->mutex); + pthread_mutex_destroy(session->mutex); + + LOGGER_DEBUG(log, "Terminated session: %p", (void *)session); + free(session); + return 0; +} +int msi_invite(MSISession *session, MSICall **call, uint32_t friend_number, uint8_t capabilities) +{ + if (session == nullptr) { + return -1; + } + + LOGGER_DEBUG(session->messenger->log, "Session: %p Inviting friend: %u", (void *)session, friend_number); + + if (pthread_mutex_trylock(session->mutex) != 0) { + LOGGER_ERROR(session->messenger->log, "Failed to acquire lock on msi mutex"); + return -1; + } + + if (get_call(session, friend_number) != nullptr) { + LOGGER_ERROR(session->messenger->log, "Already in a call"); + pthread_mutex_unlock(session->mutex); + return -1; + } + + MSICall *temp = new_call(session, friend_number); + + if (temp == nullptr) { + pthread_mutex_unlock(session->mutex); + return -1; + } + + temp->self_capabilities = capabilities; + + MSIMessage msg; + msg_init(&msg, REQU_INIT); + + msg.capabilities.exists = true; + msg.capabilities.value = capabilities; + + send_message(temp->session->messenger, temp->friend_number, &msg); + + temp->state = MSI_CALL_REQUESTING; + + *call = temp; + + LOGGER_DEBUG(session->messenger->log, "Invite sent"); + pthread_mutex_unlock(session->mutex); + return 0; +} +int msi_hangup(MSICall *call) +{ + if (call == nullptr || call->session == nullptr) { + return -1; + } + + MSISession *session = call->session; + + LOGGER_DEBUG(session->messenger->log, "Session: %p Hanging up call with friend: %u", (void *)call->session, + call->friend_number); + + if (pthread_mutex_trylock(session->mutex) != 0) { + LOGGER_ERROR(session->messenger->log, "Failed to acquire lock on msi mutex"); + return -1; + } + + if (call->state == MSI_CALL_INACTIVE) { + LOGGER_ERROR(session->messenger->log, "Call is in invalid state!"); + pthread_mutex_unlock(session->mutex); + return -1; + } + + MSIMessage msg; + msg_init(&msg, REQU_POP); + + send_message(session->messenger, call->friend_number, &msg); + + kill_call(call); + pthread_mutex_unlock(session->mutex); + return 0; +} +int msi_answer(MSICall *call, uint8_t capabilities) +{ + if (call == nullptr || call->session == nullptr) { + return -1; + } + + MSISession *session = call->session; + + LOGGER_DEBUG(session->messenger->log, "Session: %p Answering call from: %u", (void *)call->session, + call->friend_number); + + if (pthread_mutex_trylock(session->mutex) != 0) { + LOGGER_ERROR(session->messenger->log, "Failed to acquire lock on msi mutex"); + return -1; + } + + if (call->state != MSI_CALL_REQUESTED) { + /* Though sending in invalid state will not cause anything weird + * Its better to not do it like a maniac */ + LOGGER_ERROR(session->messenger->log, "Call is in invalid state!"); + pthread_mutex_unlock(session->mutex); + return -1; + } + + call->self_capabilities = capabilities; + + MSIMessage msg; + msg_init(&msg, REQU_PUSH); + + msg.capabilities.exists = true; + msg.capabilities.value = capabilities; + + send_message(session->messenger, call->friend_number, &msg); + + call->state = MSI_CALL_ACTIVE; + pthread_mutex_unlock(session->mutex); + + return 0; +} +int msi_change_capabilities(MSICall *call, uint8_t capabilities) +{ + if (call == nullptr || call->session == nullptr) { + return -1; + } + + MSISession *session = call->session; + + LOGGER_DEBUG(session->messenger->log, "Session: %p Trying to change capabilities to friend %u", (void *)call->session, + call->friend_number); + + if (pthread_mutex_trylock(session->mutex) != 0) { + LOGGER_ERROR(session->messenger->log, "Failed to acquire lock on msi mutex"); + return -1; + } + + if (call->state != MSI_CALL_ACTIVE) { + LOGGER_ERROR(session->messenger->log, "Call is in invalid state!"); + pthread_mutex_unlock(session->mutex); + return -1; + } + + call->self_capabilities = capabilities; + + MSIMessage msg; + msg_init(&msg, REQU_PUSH); + + msg.capabilities.exists = true; + msg.capabilities.value = capabilities; + + send_message(call->session->messenger, call->friend_number, &msg); + + pthread_mutex_unlock(session->mutex); + return 0; +} + + +/** + * Private functions + */ +static void msg_init(MSIMessage *dest, MSIRequest request) +{ + memset(dest, 0, sizeof(*dest)); + dest->request.exists = true; + dest->request.value = request; +} + +static bool check_size(const Logger *log, const uint8_t *bytes, int *constraint, uint8_t size) +{ + *constraint -= 2 + size; + + if (*constraint < 1) { + LOGGER_ERROR(log, "Read over length!"); + return false; + } + + if (bytes[1] != size) { + LOGGER_ERROR(log, "Invalid data size!"); + return false; + } + + return true; +} + +/** Assumes size == 1 */ +static bool check_enum_high(const Logger *log, const uint8_t *bytes, uint8_t enum_high) +{ + if (bytes[2] > enum_high) { + LOGGER_ERROR(log, "Failed enum high limit!"); + return false; + } + + return true; +} + + +static int msg_parse_in(const Logger *log, MSIMessage *dest, const uint8_t *data, uint16_t length) +{ + /* Parse raw data received from socket into MSIMessage struct */ + + assert(dest != nullptr); + + if (length == 0 || data[length - 1] != 0) { /* End byte must have value 0 */ + LOGGER_ERROR(log, "Invalid end byte"); + return -1; + } + + memset(dest, 0, sizeof(*dest)); + + const uint8_t *it = data; + int size_constraint = length; + + while (*it != 0) {/* until end byte is hit */ + switch (*it) { + case ID_REQUEST: { + if (!check_size(log, it, &size_constraint, 1) || + !check_enum_high(log, it, REQU_POP)) { + return -1; + } + + dest->request.value = (MSIRequest)it[2]; + dest->request.exists = true; + it += 3; + break; + } + + case ID_ERROR: { + if (!check_size(log, it, &size_constraint, 1) || + !check_enum_high(log, it, MSI_E_UNDISCLOSED)) { + return -1; + } + + dest->error.value = (MSIError)it[2]; + dest->error.exists = true; + it += 3; + break; + } + + case ID_CAPABILITIES: { + if (!check_size(log, it, &size_constraint, 1)) { + return -1; + } + + dest->capabilities.value = it[2]; + dest->capabilities.exists = true; + it += 3; + break; + } + + default: { + LOGGER_ERROR(log, "Invalid id byte"); + return -1; + } + } + } + + if (!dest->request.exists) { + LOGGER_ERROR(log, "Invalid request field!"); + return -1; + } + + return 0; +} +static uint8_t *msg_parse_header_out(MSIHeaderID id, uint8_t *dest, const void *value, uint8_t value_len, + uint16_t *length) +{ + /* Parse a single header for sending */ + assert(dest != nullptr); + assert(value != nullptr); + assert(value_len != 0); + + *dest = id; + ++dest; + *dest = value_len; + ++dest; + + memcpy(dest, value, value_len); + + *length += 2 + value_len; + + return dest + value_len; /* Set to next position ready to be written */ +} +static int send_message(Messenger *m, uint32_t friend_number, const MSIMessage *msg) +{ + /* Parse and send message */ + assert(m != nullptr); + + uint8_t parsed [MSI_MAXMSG_SIZE]; + + uint8_t *it = parsed; + uint16_t size = 0; + + if (msg->request.exists) { + uint8_t cast = msg->request.value; + it = msg_parse_header_out(ID_REQUEST, it, &cast, + sizeof(cast), &size); + } else { + LOGGER_DEBUG(m->log, "Must have request field"); + return -1; + } + + if (msg->error.exists) { + uint8_t cast = msg->error.value; + it = msg_parse_header_out(ID_ERROR, it, &cast, + sizeof(cast), &size); + } + + if (msg->capabilities.exists) { + it = msg_parse_header_out(ID_CAPABILITIES, it, &msg->capabilities.value, + sizeof(msg->capabilities.value), &size); + } + + if (it == parsed) { + LOGGER_WARNING(m->log, "Parsing message failed; empty message"); + return -1; + } + + *it = 0; + ++size; + + if (m_msi_packet(m, friend_number, parsed, size)) { + LOGGER_DEBUG(m->log, "Sent message"); + return 0; + } + + return -1; +} +static int send_error(Messenger *m, uint32_t friend_number, MSIError error) +{ + /* Send error message */ + assert(m != nullptr); + + LOGGER_DEBUG(m->log, "Sending error: %d to friend: %d", error, friend_number); + + MSIMessage msg; + msg_init(&msg, REQU_POP); + + msg.error.exists = true; + msg.error.value = error; + + send_message(m, friend_number, &msg); + return 0; +} +static int invoke_callback_inner(MSICall *call, MSICallbackID id) +{ + MSISession *session = call->session; + LOGGER_DEBUG(session->messenger->log, "invoking callback function: %d", id); + + switch (id) { + case MSI_ON_INVITE: + return session->invite_callback(session->av, call); + + case MSI_ON_START: + return session->start_callback(session->av, call); + + case MSI_ON_END: + return session->end_callback(session->av, call); + + case MSI_ON_ERROR: + return session->error_callback(session->av, call); + + case MSI_ON_PEERTIMEOUT: + return session->peertimeout_callback(session->av, call); + + case MSI_ON_CAPABILITIES: + return session->capabilities_callback(session->av, call); + } + + LOGGER_FATAL(session->messenger->log, "invalid callback id: %d", id); + return -1; +} +static bool invoke_callback(MSICall *call, MSICallbackID cb) +{ + assert(call != nullptr); + + if (invoke_callback_inner(call, cb) != 0) { + LOGGER_WARNING(call->session->messenger->log, + "Callback state handling failed, sending error"); + + /* If no callback present or error happened while handling, + * an error message will be sent to friend + */ + if (call->error == MSI_E_NONE) { + call->error = MSI_E_HANDLE; + } + + return false; + } + + return true; +} +static MSICall *get_call(MSISession *session, uint32_t friend_number) +{ + assert(session != nullptr); + + if (session->calls == nullptr || session->calls_tail < friend_number) { + return nullptr; + } + + return session->calls[friend_number]; +} +static MSICall *new_call(MSISession *session, uint32_t friend_number) +{ + assert(session != nullptr); + + MSICall *rc = (MSICall *)calloc(1, sizeof(MSICall)); + + if (rc == nullptr) { + return nullptr; + } + + rc->session = session; + rc->friend_number = friend_number; + + if (session->calls == nullptr) { /* Creating */ + session->calls = (MSICall **)calloc(friend_number + 1, sizeof(MSICall *)); + + if (session->calls == nullptr) { + free(rc); + return nullptr; + } + + session->calls_tail = friend_number; + session->calls_head = friend_number; + } else if (session->calls_tail < friend_number) { /* Appending */ + MSICall **tmp = (MSICall **)realloc(session->calls, sizeof(MSICall *) * (friend_number + 1)); + + if (tmp == nullptr) { + free(rc); + return nullptr; + } + + session->calls = tmp; + + /* Set fields in between to null */ + for (uint32_t i = session->calls_tail + 1; i < friend_number; ++i) { + session->calls[i] = nullptr; + } + + rc->prev = session->calls[session->calls_tail]; + session->calls[session->calls_tail]->next = rc; + + session->calls_tail = friend_number; + } else if (session->calls_head > friend_number) { /* Inserting at front */ + rc->next = session->calls[session->calls_head]; + session->calls[session->calls_head]->prev = rc; + session->calls_head = friend_number; + } + + session->calls[friend_number] = rc; + return rc; +} +static void kill_call(MSICall *call) +{ + /* Assume that session mutex is locked */ + if (call == nullptr) { + return; + } + + MSISession *session = call->session; + + LOGGER_DEBUG(session->messenger->log, "Killing call: %p", (void *)call); + + MSICall *prev = call->prev; + MSICall *next = call->next; + + if (prev != nullptr) { + prev->next = next; + } else if (next != nullptr) { + session->calls_head = next->friend_number; + } else { + goto CLEAR_CONTAINER; + } + + if (next != nullptr) { + next->prev = prev; + } else if (prev != nullptr) { + session->calls_tail = prev->friend_number; + } else { + goto CLEAR_CONTAINER; + } + + session->calls[call->friend_number] = nullptr; + free(call); + return; + +CLEAR_CONTAINER: + session->calls_head = 0; + session->calls_tail = 0; + free(session->calls); + free(call); + session->calls = nullptr; +} +static void on_peer_status(Messenger *m, uint32_t friend_number, uint8_t status, void *data) +{ + if (status != 0) { + // Friend is online. + return; + } + + MSISession *session = (MSISession *)data; + LOGGER_DEBUG(m->log, "Friend %d is now offline", friend_number); + + pthread_mutex_lock(session->mutex); + MSICall *call = get_call(session, friend_number); + + if (call == nullptr) { + pthread_mutex_unlock(session->mutex); + return; + } + + invoke_callback(call, MSI_ON_PEERTIMEOUT); /* Failure is ignored */ + kill_call(call); + pthread_mutex_unlock(session->mutex); +} +static bool try_handle_init(MSICall *call, const MSIMessage *msg) +{ + assert(call != nullptr); + LOGGER_DEBUG(call->session->messenger->log, + "Session: %p Handling 'init' friend: %d", (void *)call->session, call->friend_number); + + if (!msg->capabilities.exists) { + LOGGER_WARNING(call->session->messenger->log, "Session: %p Invalid capabilities on 'init'", (void *)call->session); + call->error = MSI_E_INVALID_MESSAGE; + return false; + } + + switch (call->state) { + case MSI_CALL_INACTIVE: { + /* Call requested */ + call->peer_capabilities = msg->capabilities.value; + call->state = MSI_CALL_REQUESTED; + + if (!invoke_callback(call, MSI_ON_INVITE)) { + return false; + } + + break; + } + + case MSI_CALL_ACTIVE: { + /* If peer sent init while the call is already + * active it's probable that he is trying to + * re-call us while the call is not terminated + * on our side. We can assume that in this case + * we can automatically answer the re-call. + */ + + LOGGER_INFO(call->session->messenger->log, "Friend is recalling us"); + + MSIMessage out_msg; + msg_init(&out_msg, REQU_PUSH); + + out_msg.capabilities.exists = true; + out_msg.capabilities.value = call->self_capabilities; + + send_message(call->session->messenger, call->friend_number, &out_msg); + + /* If peer changed capabilities during re-call they will + * be handled accordingly during the next step + */ + break; + } + + case MSI_CALL_REQUESTED: // fall-through + case MSI_CALL_REQUESTING: { + LOGGER_WARNING(call->session->messenger->log, "Session: %p Invalid state on 'init'", (void *)call->session); + call->error = MSI_E_INVALID_STATE; + return false; + } + } + + return true; +} +static void handle_init(MSICall *call, const MSIMessage *msg) +{ + assert(call != nullptr); + LOGGER_DEBUG(call->session->messenger->log, + "Session: %p Handling 'init' friend: %d", (void *)call->session, call->friend_number); + + if (!try_handle_init(call, msg)) { + send_error(call->session->messenger, call->friend_number, call->error); + kill_call(call); + } +} +static void handle_push(MSICall *call, const MSIMessage *msg) +{ + assert(call != nullptr); + + LOGGER_DEBUG(call->session->messenger->log, "Session: %p Handling 'push' friend: %d", (void *)call->session, + call->friend_number); + + if (!msg->capabilities.exists) { + LOGGER_WARNING(call->session->messenger->log, "Session: %p Invalid capabilities on 'push'", (void *)call->session); + call->error = MSI_E_INVALID_MESSAGE; + goto FAILURE; + } + + switch (call->state) { + case MSI_CALL_ACTIVE: { + if (call->peer_capabilities != msg->capabilities.value) { + /* Only act if capabilities changed */ + LOGGER_INFO(call->session->messenger->log, "Friend is changing capabilities to: %u", msg->capabilities.value); + + call->peer_capabilities = msg->capabilities.value; + + if (!invoke_callback(call, MSI_ON_CAPABILITIES)) { + goto FAILURE; + } + } + + break; + } + + case MSI_CALL_REQUESTING: { + LOGGER_INFO(call->session->messenger->log, "Friend answered our call"); + + /* Call started */ + call->peer_capabilities = msg->capabilities.value; + call->state = MSI_CALL_ACTIVE; + + if (!invoke_callback(call, MSI_ON_START)) { + goto FAILURE; + } + + break; + } + + case MSI_CALL_INACTIVE: // fall-through + case MSI_CALL_REQUESTED: { + /* Pushes during initialization state are ignored */ + LOGGER_WARNING(call->session->messenger->log, "Ignoring invalid push"); + break; + } + } + + return; + +FAILURE: + send_error(call->session->messenger, call->friend_number, call->error); + kill_call(call); +} +static void handle_pop(MSICall *call, const MSIMessage *msg) +{ + assert(call != nullptr); + + LOGGER_DEBUG(call->session->messenger->log, "Session: %p Handling 'pop', friend id: %d", (void *)call->session, + call->friend_number); + + /* callback errors are ignored */ + + if (msg->error.exists) { + LOGGER_WARNING(call->session->messenger->log, "Friend detected an error: %d", msg->error.value); + call->error = msg->error.value; + invoke_callback(call, MSI_ON_ERROR); + } else { + switch (call->state) { + case MSI_CALL_INACTIVE: { + LOGGER_FATAL(call->session->messenger->log, "Handling what should be impossible case"); + } + + case MSI_CALL_ACTIVE: { + /* Hangup */ + LOGGER_INFO(call->session->messenger->log, "Friend hung up on us"); + invoke_callback(call, MSI_ON_END); + break; + } + + case MSI_CALL_REQUESTING: { + /* Reject */ + LOGGER_INFO(call->session->messenger->log, "Friend rejected our call"); + invoke_callback(call, MSI_ON_END); + break; + } + + case MSI_CALL_REQUESTED: { + /* Cancel */ + LOGGER_INFO(call->session->messenger->log, "Friend canceled call invite"); + invoke_callback(call, MSI_ON_END); + break; + } + } + } + + kill_call(call); +} +static void handle_msi_packet(Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, void *object) +{ + LOGGER_DEBUG(m->log, "Got msi message"); + + MSISession *session = (MSISession *)object; + MSIMessage msg; + + if (msg_parse_in(m->log, &msg, data, length) == -1) { + LOGGER_WARNING(m->log, "Error parsing message"); + send_error(m, friend_number, MSI_E_INVALID_MESSAGE); + return; + } + + LOGGER_DEBUG(m->log, "Successfully parsed message"); + + pthread_mutex_lock(session->mutex); + MSICall *call = get_call(session, friend_number); + + if (call == nullptr) { + if (msg.request.value != REQU_INIT) { + send_error(m, friend_number, MSI_E_STRAY_MESSAGE); + pthread_mutex_unlock(session->mutex); + return; + } + + call = new_call(session, friend_number); + + if (call == nullptr) { + send_error(m, friend_number, MSI_E_SYSTEM); + pthread_mutex_unlock(session->mutex); + return; + } + } + + switch (msg.request.value) { + case REQU_INIT: { + handle_init(call, &msg); + break; + } + + case REQU_PUSH: { + handle_push(call, &msg); + break; + } + + case REQU_POP: { + handle_pop(call, &msg); /* always kills the call */ + break; + } + } + + pthread_mutex_unlock(session->mutex); +} diff --git a/local_pod_repo/toxcore/toxcore/toxav/ring_buffer.h b/local_pod_repo/toxcore/toxcore/toxav/ring_buffer.h new file mode 100644 index 0000000..775bf50 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/ring_buffer.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + * Copyright © 2013 plutooo + */ +#ifndef C_TOXCORE_TOXAV_RING_BUFFER_H +#define C_TOXCORE_TOXAV_RING_BUFFER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Ring buffer */ +typedef struct RingBuffer RingBuffer; +bool rb_full(const RingBuffer *b); +bool rb_empty(const RingBuffer *b); +void *rb_write(RingBuffer *b, void *p); +bool rb_read(RingBuffer *b, void **p); +RingBuffer *rb_new(int size); +void rb_kill(RingBuffer *b); +uint16_t rb_size(const RingBuffer *b); +uint16_t rb_data(const RingBuffer *b, void **dest); + +#ifdef __cplusplus +} +#endif + +#endif // C_TOXCORE_TOXAV_RING_BUFFER_H diff --git a/local_pod_repo/toxcore/toxcore/toxav/ring_buffer.m b/local_pod_repo/toxcore/toxcore/toxav/ring_buffer.m new file mode 100644 index 0000000..703c2a5 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/ring_buffer.m @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + * Copyright © 2013 plutooo + */ +#include "ring_buffer.h" + +#include + +#include "../toxcore/ccompat.h" + +struct RingBuffer { + uint16_t size; /* Max size */ + uint16_t start; + uint16_t end; + void **data; +}; + +bool rb_full(const RingBuffer *b) +{ + return (b->end + 1) % b->size == b->start; +} + +bool rb_empty(const RingBuffer *b) +{ + return b->end == b->start; +} + +/** + * @retval NULL on success + * @return input value "p" on failure, so caller can free on failed rb_write + */ +void *rb_write(RingBuffer *b, void *p) +{ + if (b == nullptr) { + return p; + } + + void *rc = nullptr; + + if ((b->end + 1) % b->size == b->start) { /* full */ + rc = b->data[b->start]; + } + + b->data[b->end] = p; + b->end = (b->end + 1) % b->size; + + if (b->end == b->start) { + b->start = (b->start + 1) % b->size; + } + + return rc; +} + +bool rb_read(RingBuffer *b, void **p) +{ + if (b->end == b->start) { /* Empty */ + *p = nullptr; + return false; + } + + *p = b->data[b->start]; + b->start = (b->start + 1) % b->size; + return true; +} + +RingBuffer *rb_new(int size) +{ + RingBuffer *buf = (RingBuffer *)calloc(1, sizeof(RingBuffer)); + + if (buf == nullptr) { + return nullptr; + } + + buf->size = size + 1; /* include empty elem */ + buf->data = (void **)calloc(buf->size, sizeof(void *)); + + if (buf->data == nullptr) { + free(buf); + return nullptr; + } + + return buf; +} + +void rb_kill(RingBuffer *b) +{ + if (b != nullptr) { + free(b->data); + free(b); + } +} + +uint16_t rb_size(const RingBuffer *b) +{ + if (rb_empty(b)) { + return 0; + } + + return + b->end > b->start ? + b->end - b->start : + (b->size - b->start) + b->end; +} + +uint16_t rb_data(const RingBuffer *b, void **dest) +{ + uint16_t i; + + for (i = 0; i < rb_size(b); ++i) { + dest[i] = b->data[(b->start + i) % b->size]; + } + + return i; +} diff --git a/local_pod_repo/toxcore/toxcore/toxav/rtp.h b/local_pod_repo/toxcore/toxcore/toxav/rtp.h new file mode 100644 index 0000000..6144c63 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/rtp.h @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ +#ifndef C_TOXCORE_TOXAV_RTP_H +#define C_TOXCORE_TOXAV_RTP_H + +#include + +#include "bwcontroller.h" + +#include "../toxcore/Messenger.h" +#include "../toxcore/logger.h" +#include "../toxcore/tox.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * RTPHeader serialised size in bytes. + */ +#define RTP_HEADER_SIZE 80 + +/** + * Number of 32 bit padding fields between @ref RTPHeader::offset_lower and + * everything before it. + */ +#define RTP_PADDING_FIELDS 11 + +/** + * Payload type identifier. Also used as rtp callback prefix. + */ +typedef enum RTP_Type { + RTP_TYPE_AUDIO = 192, + RTP_TYPE_VIDEO = 193, +} RTP_Type; + +/** + * A bit mask (up to 64 bits) specifying features of the current frame affecting + * the behaviour of the decoder. + */ +typedef enum RTPFlags { + /** + * Support frames larger than 64KiB. The full 32 bit length and offset are + * set in @ref RTPHeader::data_length_full and @ref RTPHeader::offset_full. + */ + RTP_LARGE_FRAME = 1 << 0, + /** + * Whether the packet is part of a key frame. + */ + RTP_KEY_FRAME = 1 << 1, +} RTPFlags; + + +struct RTPHeader { + /* Standard RTP header */ + unsigned ve: 2; /* Version has only 2 bits! */ + unsigned pe: 1; /* Padding */ + unsigned xe: 1; /* Extra header */ + unsigned cc: 4; /* Contributing sources count */ + + unsigned ma: 1; /* Marker */ + unsigned pt: 7; /* Payload type */ + + uint16_t sequnum; + uint32_t timestamp; + uint32_t ssrc; + + /* Non-standard Tox-specific fields */ + + /** + * Bit mask of `RTPFlags` setting features of the current frame. + */ + uint64_t flags; + + /** + * The full 32 bit data offset of the current data chunk. The @ref + * offset_lower data member contains the lower 16 bits of this value. For + * frames smaller than 64KiB, @ref offset_full and @ref offset_lower are + * equal. + */ + uint32_t offset_full; + /** + * The full 32 bit payload length without header and packet id. + */ + uint32_t data_length_full; + /** + * Only the receiver uses this field (why do we have this?). + */ + uint32_t received_length_full; + + /** + * Data offset of the current part (lower bits). + */ + uint16_t offset_lower; + /** + * Total message length (lower bits). + */ + uint16_t data_length_lower; +}; + + +struct RTPMessage { + /** + * This is used in the old code that doesn't deal with large frames, i.e. + * the audio code or receiving code for old 16 bit messages. We use it to + * record the number of bytes received so far in a multi-part message. The + * multi-part message in the old code is stored in `RTPSession::mp`. + */ + uint16_t len; + + struct RTPHeader header; + uint8_t data[]; +}; + +#define USED_RTP_WORKBUFFER_COUNT 3 + +/** + * One slot in the work buffer list. Represents one frame that is currently + * being assembled. + */ +struct RTPWorkBuffer { + /** + * Whether this slot contains a key frame. This is true iff + * `buf->header.flags & RTP_KEY_FRAME`. + */ + bool is_keyframe; + /** + * The number of bytes received so far, regardless of which pieces. I.e. we + * could have received the first 1000 bytes and the last 1000 bytes with + * 4000 bytes in the middle still to come, and this number would be 2000. + */ + uint32_t received_len; + /** + * The message currently being assembled. + */ + struct RTPMessage *buf; +}; + +struct RTPWorkBufferList { + int8_t next_free_entry; + struct RTPWorkBuffer work_buffer[USED_RTP_WORKBUFFER_COUNT]; +}; + +#define DISMISS_FIRST_LOST_VIDEO_PACKET_COUNT 10 + +typedef int rtp_m_cb(Mono_Time *mono_time, void *cs, struct RTPMessage *msg); + +/** + * RTP control session. + */ +typedef struct RTPSession { + uint8_t payload_type; + uint16_t sequnum; /* Sending sequence number */ + uint16_t rsequnum; /* Receiving sequence number */ + uint32_t rtimestamp; + uint32_t ssrc; // this seems to be unused!? + struct RTPMessage *mp; /* Expected parted message */ + struct RTPWorkBufferList *work_buffer_list; + uint8_t first_packets_counter; /* dismiss first few lost video packets */ + Messenger *m; + Tox *tox; + uint32_t friend_number; + BWController *bwc; + void *cs; + rtp_m_cb *mcb; +} RTPSession; + + +/** + * Serialise an RTPHeader to bytes to be sent over the network. + * + * @param rdata A byte array of length RTP_HEADER_SIZE. Does not need to be + * initialised. All RTP_HEADER_SIZE bytes will be initialised after a call + * to this function. + * @param header The RTPHeader to serialise. + */ +size_t rtp_header_pack(uint8_t *rdata, const struct RTPHeader *header); + +/** + * Deserialise an RTPHeader from bytes received over the network. + * + * @param data A byte array of length RTP_HEADER_SIZE. + * @param header The RTPHeader to write the unpacked values to. + */ +size_t rtp_header_unpack(const uint8_t *data, struct RTPHeader *header); + +RTPSession *rtp_new(int payload_type, Messenger *m, Tox *tox, uint32_t friendnumber, + BWController *bwc, void *cs, rtp_m_cb *mcb); +void rtp_kill(RTPSession *session); +int rtp_allow_receiving(RTPSession *session); +int rtp_stop_receiving(RTPSession *session); +/** + * Send a frame of audio or video data, chunked in @ref RTPMessage instances. + * + * @param session The A/V session to send the data for. + * @param data A byte array of length @p length. + * @param length The number of bytes to send from @p data. + * @param is_keyframe Whether this video frame is a key frame. If it is an + * audio frame, this parameter is ignored. + */ +int rtp_send_data(RTPSession *session, const uint8_t *data, uint32_t length, + bool is_keyframe, const Logger *log); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // C_TOXCORE_TOXAV_RTP_H diff --git a/local_pod_repo/toxcore/toxcore/toxav/rtp.m b/local_pod_repo/toxcore/toxcore/toxav/rtp.m new file mode 100644 index 0000000..878a70c --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/rtp.m @@ -0,0 +1,871 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ +#include "rtp.h" + +#include +#include +#include +#include + +#include "bwcontroller.h" + +#include "../toxcore/Messenger.h" +#include "../toxcore/ccompat.h" +#include "../toxcore/logger.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/util.h" + +/** + * The number of milliseconds we want to keep a keyframe in the buffer for, + * even though there are no free slots for incoming frames. + */ +#define VIDEO_KEEP_KEYFRAME_IN_BUFFER_FOR_MS 15 + + +/** + * return -1 on failure, 0 on success + * + */ +static int rtp_send_custom_lossy_packet(Tox *tox, int32_t friendnumber, const uint8_t *data, uint32_t length) +{ + Tox_Err_Friend_Custom_Packet error; + tox_friend_send_lossy_packet(tox, friendnumber, data, (size_t)length, &error); + + if (error == TOX_ERR_FRIEND_CUSTOM_PACKET_OK) { + return 0; + } + + return -1; +} + +// allocate_len is NOT including header! +static struct RTPMessage *new_message(const struct RTPHeader *header, size_t allocate_len, const uint8_t *data, + uint16_t data_length) +{ + assert(allocate_len >= data_length); + struct RTPMessage *msg = (struct RTPMessage *)calloc(1, sizeof(struct RTPMessage) + allocate_len); + + if (msg == nullptr) { + return nullptr; + } + + msg->len = data_length; // result without header + msg->header = *header; + memcpy(msg->data, data, msg->len); + return msg; +} + +/** + * Instruct the caller to clear slot 0. + */ +#define GET_SLOT_RESULT_DROP_OLDEST_SLOT (-1) + +/** + * Instruct the caller to drop the incoming packet. + */ +#define GET_SLOT_RESULT_DROP_INCOMING (-2) + +/** + * Find the next free slot in work_buffer for the incoming data packet. + * + * - If the data packet belongs to a frame that's already in the work_buffer then + * use that slot. + * - If there is no free slot return GET_SLOT_RESULT_DROP_OLDEST_SLOT. + * - If the data packet is too old return GET_SLOT_RESULT_DROP_INCOMING. + * + * If there is a keyframe being assembled in slot 0, keep it a bit longer and + * do not kick it out right away if all slots are full instead kick out the new + * incoming interframe. + */ +static int8_t get_slot(const Logger *log, struct RTPWorkBufferList *wkbl, bool is_keyframe, + const struct RTPHeader *header, bool is_multipart) +{ + if (is_multipart) { + // This RTP message is part of a multipart frame, so we try to find an + // existing slot with the previous parts of the frame in it. + for (uint8_t i = 0; i < wkbl->next_free_entry; ++i) { + const struct RTPWorkBuffer *slot = &wkbl->work_buffer[i]; + + if ((slot->buf->header.sequnum == header->sequnum) && (slot->buf->header.timestamp == header->timestamp)) { + // Sequence number and timestamp match, so this slot belongs to + // the same frame. + // + // In reality, these will almost certainly either both match or + // both not match. Only if somehow there were 65535 frames + // between, the timestamp will matter. + return i; + } + } + } + + // The message may or may not be part of a multipart frame. + // + // If it is part of a multipart frame, then this is an entirely new frame + // for which we did not have a slot *or* the frame is so old that its slot + // has been evicted by now. + // + // |----------- time -----------> + // _________________ + // slot 0 | | + // ----------------- + // _________________ + // slot 1 | | + // ----------------- + // ____________ + // slot 2 | | -> frame too old, drop + // ------------ + // + // + // + // |----------- time -----------> + // _________________ + // slot 0 | | + // ----------------- + // _________________ + // slot 1 | | + // ----------------- + // ____________ + // slot 2 | | -> ok, start filling in a new slot + // ------------ + + // If there is a free slot: + if (wkbl->next_free_entry < USED_RTP_WORKBUFFER_COUNT) { + // If there is at least one filled slot: + if (wkbl->next_free_entry > 0) { + // Get the most recently filled slot. + const struct RTPWorkBuffer *slot = &wkbl->work_buffer[wkbl->next_free_entry - 1]; + + // If the incoming packet is older than our newest slot, drop it. + // This is the first situation in the above diagram. + if (slot->buf->header.timestamp > header->timestamp) { + LOGGER_DEBUG(log, "workbuffer:2:timestamp too old"); + return GET_SLOT_RESULT_DROP_INCOMING; + } + } + + // Not all slots are filled, and the packet is newer than our most + // recent slot, so it's a new frame we want to start assembling. This is + // the second situation in the above diagram. + return wkbl->next_free_entry; + } + + // If the incoming frame is a key frame, then stop assembling the oldest + // slot, regardless of whether there was a keyframe in that or not. + if (is_keyframe) { + return GET_SLOT_RESULT_DROP_OLDEST_SLOT; + } + + // The incoming slot is not a key frame, so we look at slot 0 to see what to + // do next. + const struct RTPWorkBuffer *slot = &wkbl->work_buffer[0]; + + // The incoming frame is not a key frame, but the existing slot 0 is also + // not a keyframe, so we stop assembling the existing frame and make space + // for the new one. + if (!slot->is_keyframe) { + return GET_SLOT_RESULT_DROP_OLDEST_SLOT; + } + + // If this key frame is fully received, we also stop assembling and clear + // slot 0. This also means sending the frame to the decoder. + if (slot->received_len == slot->buf->header.data_length_full) { + return GET_SLOT_RESULT_DROP_OLDEST_SLOT; + } + + // This is a key frame, not fully received yet, but it's already much older + // than the incoming frame, so we stop assembling it and send whatever part + // we did receive to the decoder. + if (slot->buf->header.timestamp + VIDEO_KEEP_KEYFRAME_IN_BUFFER_FOR_MS <= header->timestamp) { + return GET_SLOT_RESULT_DROP_OLDEST_SLOT; + } + + // This is a key frame, it's not too old yet, so we keep it in its slot for + // a little longer. + LOGGER_INFO(log, "keep KEYFRAME in workbuffer"); + return GET_SLOT_RESULT_DROP_INCOMING; +} + +/** + * Returns an assembled frame (as much data as we currently have for this frame, + * some pieces may be missing) + * + * If there are no frames ready, we return NULL. If this function returns + * non-NULL, it transfers ownership of the message to the caller, i.e. the + * caller is responsible for storing it elsewhere or calling `free()`. + */ +static struct RTPMessage *process_frame(const Logger *log, struct RTPWorkBufferList *wkbl, uint8_t slot_id) +{ + assert(wkbl->next_free_entry >= 0); + + if (wkbl->next_free_entry == 0) { + // There are no frames in any slot. + return nullptr; + } + + // Slot 0 contains a key frame, slot_id points at an interframe that is + // relative to that key frame, so we don't use it yet. + if (wkbl->work_buffer[0].is_keyframe && slot_id != 0) { + LOGGER_DEBUG(log, "process_frame:KEYFRAME waiting in slot 0"); + return nullptr; + } + + // Either slot_id is 0 and slot 0 is a key frame, or there is no key frame + // in slot 0 (and slot_id is anything). + struct RTPWorkBuffer *const slot = &wkbl->work_buffer[slot_id]; + + // Move ownership of the frame out of the slot into m_new. + struct RTPMessage *const m_new = slot->buf; + slot->buf = nullptr; + + assert(wkbl->next_free_entry >= 1 && wkbl->next_free_entry <= USED_RTP_WORKBUFFER_COUNT); + + if (slot_id != wkbl->next_free_entry - 1) { + // The slot is not the last slot, so we created a gap. We move all the + // entries after it one step up. + for (uint8_t i = slot_id; i < wkbl->next_free_entry - 1; ++i) { + // Move entry (i+1) into entry (i). + wkbl->work_buffer[i] = wkbl->work_buffer[i + 1]; + } + } + + // We now have a free entry at the end of the array. + --wkbl->next_free_entry; + + // Clear the newly freed entry. + const struct RTPWorkBuffer empty = {0}; + wkbl->work_buffer[wkbl->next_free_entry] = empty; + + // Move ownership of the frame to the caller. + return m_new; +} + +/** + * @param log A logger. + * @param wkbl The list of in-progress frames, i.e. all the slots. + * @param slot_id The slot we want to fill the data into. + * @param is_keyframe Whether the data is part of a key frame. + * @param header The RTP header from the incoming packet. + * @param incoming_data The pure payload without header. + * @param incoming_data_length The length in bytes of the incoming data payload. + */ +static bool fill_data_into_slot(const Logger *log, struct RTPWorkBufferList *wkbl, const uint8_t slot_id, + bool is_keyframe, const struct RTPHeader *header, + const uint8_t *incoming_data, uint16_t incoming_data_length) +{ + // We're either filling the data into an existing slot, or in a new one that + // is the next free entry. + assert(slot_id <= wkbl->next_free_entry); + struct RTPWorkBuffer *const slot = &wkbl->work_buffer[slot_id]; + + assert(header != nullptr); + assert(is_keyframe == (bool)((header->flags & RTP_KEY_FRAME) != 0)); + + if (slot->received_len == 0) { + assert(slot->buf == nullptr); + + // No data for this slot has been received, yet, so we create a new + // message for it with enough memory for the entire frame. + struct RTPMessage *msg = (struct RTPMessage *)calloc(1, sizeof(struct RTPMessage) + header->data_length_full); + + if (msg == nullptr) { + LOGGER_ERROR(log, "Out of memory while trying to allocate for frame of size %u", + (unsigned)header->data_length_full); + // Out of memory: throw away the incoming data. + return false; + } + + // Unused in the new video receiving code, as it's 16 bit and can't hold + // the full length of large frames. Instead, we use slot->received_len. + msg->len = 0; + msg->header = *header; + + slot->buf = msg; + slot->is_keyframe = is_keyframe; + slot->received_len = 0; + + assert(wkbl->next_free_entry < USED_RTP_WORKBUFFER_COUNT); + ++wkbl->next_free_entry; + } + + // We already checked this when we received the packet, but we rely on it + // here, so assert again. + assert(header->offset_full < header->data_length_full); + + // Copy the incoming chunk of data into the correct position in the full + // frame data array. + memcpy( + slot->buf->data + header->offset_full, + incoming_data, + incoming_data_length + ); + + // Update the total received length of this slot. + slot->received_len += incoming_data_length; + + // Update received length also in the header of the message, for later use. + slot->buf->header.received_length_full = slot->received_len; + + return slot->received_len == header->data_length_full; +} + +static void update_bwc_values(const Logger *log, RTPSession *session, const struct RTPMessage *msg) +{ + if (session->first_packets_counter < DISMISS_FIRST_LOST_VIDEO_PACKET_COUNT) { + ++session->first_packets_counter; + } else { + const uint32_t data_length_full = msg->header.data_length_full; // without header + const uint32_t received_length_full = msg->header.received_length_full; // without header + bwc_add_recv(session->bwc, data_length_full); + + if (received_length_full < data_length_full) { + LOGGER_DEBUG(log, "BWC: full length=%u received length=%d", data_length_full, received_length_full); + bwc_add_lost(session->bwc, data_length_full - received_length_full); + } + } +} + +/** + * Handle a single RTP video packet. + * + * The packet may or may not be part of a multipart frame. This function will + * find out and handle it appropriately. + * + * @param session The current RTP session with: + * + * session->mcb == vc_queue_message() // this function is called from here + * session->mp == struct RTPMessage * + * session->cs == call->video.second // == VCSession created by vc_new() call + * + * @param header The RTP header deserialised from the packet. + * @param incoming_data The packet data *not* header, i.e. this is the actual + * payload. + * @param incoming_data_length The packet length *not* including header, i.e. + * this is the actual payload length. + * @param log A logger. + * + * @retval -1 on error. + * @retval 0 on success. + */ +static int handle_video_packet(RTPSession *session, const struct RTPHeader *header, + const uint8_t *incoming_data, uint16_t incoming_data_length, const Logger *log) +{ + // Full frame length in bytes. The frame may be split into multiple packets, + // but this value is the complete assembled frame size. + const uint32_t full_frame_length = header->data_length_full; + + // Current offset in the frame. If this is the first packet of a multipart + // frame or it's not a multipart frame, then this value is 0. + const uint32_t offset = header->offset_full; // without header + + // The sender tells us whether this is a key frame. + const bool is_keyframe = (header->flags & RTP_KEY_FRAME) != 0; + + LOGGER_DEBUG(log, "-- handle_video_packet -- full lens=%u len=%u offset=%u is_keyframe=%s", + (unsigned)incoming_data_length, (unsigned)full_frame_length, (unsigned)offset, is_keyframe ? "K" : "."); + LOGGER_DEBUG(log, "wkbl->next_free_entry:003=%d", session->work_buffer_list->next_free_entry); + + const bool is_multipart = full_frame_length != incoming_data_length; + + /* The message was sent in single part */ + int8_t slot_id = get_slot(log, session->work_buffer_list, is_keyframe, header, is_multipart); + LOGGER_DEBUG(log, "slot num=%d", slot_id); + + // get_slot told us to drop the packet, so we ignore it. + if (slot_id == GET_SLOT_RESULT_DROP_INCOMING) { + return -1; + } + + // get_slot said there is no free slot. + if (slot_id == GET_SLOT_RESULT_DROP_OLDEST_SLOT) { + LOGGER_DEBUG(log, "there was no free slot, so we process the oldest frame"); + // We now own the frame. + struct RTPMessage *m_new = process_frame(log, session->work_buffer_list, 0); + + // The process_frame function returns NULL if there is no slot 0, i.e. + // the work buffer list is completely empty. It can't be empty, because + // get_slot just told us it's full, so process_frame must return non-null. + assert(m_new != nullptr); + + LOGGER_DEBUG(log, "-- handle_video_packet -- CALLBACK-001a b0=%d b1=%d", (int)m_new->data[0], (int)m_new->data[1]); + update_bwc_values(log, session, m_new); + // Pass ownership of m_new to the callback. + session->mcb(session->m->mono_time, session->cs, m_new); + // Now we no longer own m_new. + m_new = nullptr; + + // Now we must have a free slot, so we either get that slot, i.e. >= 0, + // or get told to drop the incoming packet if it's too old. + slot_id = get_slot(log, session->work_buffer_list, is_keyframe, header, /* is_multipart */false); + + if (slot_id == GET_SLOT_RESULT_DROP_INCOMING) { + // The incoming frame is too old, so we drop it. + return -1; + } + } + + // We must have a valid slot here. + assert(slot_id >= 0); + + LOGGER_DEBUG(log, "fill_data_into_slot.1"); + + // fill in this part into the slot buffer at the correct offset + if (!fill_data_into_slot( + log, + session->work_buffer_list, + slot_id, + is_keyframe, + header, + incoming_data, + incoming_data_length)) { + // Memory allocation failed. Return error. + return -1; + } + + struct RTPMessage *m_new = process_frame(log, session->work_buffer_list, slot_id); + + if (m_new != nullptr) { + LOGGER_DEBUG(log, "-- handle_video_packet -- CALLBACK-003a b0=%d b1=%d", (int)m_new->data[0], (int)m_new->data[1]); + update_bwc_values(log, session, m_new); + session->mcb(session->m->mono_time, session->cs, m_new); + + m_new = nullptr; + } + + return 0; +} + +/** + * @retval -1 on error. + * @retval 0 on success. + */ +static int handle_rtp_packet(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object) +{ + RTPSession *session = (RTPSession *)object; + + if (session == nullptr || length < RTP_HEADER_SIZE + 1) { + LOGGER_WARNING(m->log, "No session or invalid length of received buffer!"); + return -1; + } + + // Get the packet type. + const uint8_t packet_type = data[0]; + ++data; + --length; + + // Unpack the header. + struct RTPHeader header; + rtp_header_unpack(data, &header); + + if (header.pt != packet_type % 128) { + LOGGER_WARNING(m->log, "RTPHeader packet type and Tox protocol packet type did not agree: %d != %d", + header.pt, packet_type % 128); + return -1; + } + + if (header.pt != session->payload_type % 128) { + LOGGER_WARNING(m->log, "RTPHeader packet type does not match this session's payload type: %d != %d", + header.pt, session->payload_type % 128); + return -1; + } + + if ((header.flags & RTP_LARGE_FRAME) != 0 && header.offset_full >= header.data_length_full) { + LOGGER_ERROR(m->log, "Invalid video packet: frame offset (%u) >= full frame length (%u)", + (unsigned)header.offset_full, (unsigned)header.data_length_full); + return -1; + } + + if (header.offset_lower >= header.data_length_lower) { + LOGGER_ERROR(m->log, "Invalid old protocol video packet: frame offset (%u) >= full frame length (%u)", + (unsigned)header.offset_lower, (unsigned)header.data_length_lower); + return -1; + } + + LOGGER_DEBUG(m->log, "header.pt %d, video %d", (uint8_t)header.pt, RTP_TYPE_VIDEO % 128); + + // The sender uses the new large-frame capable protocol and is sending a + // video packet. + if ((header.flags & RTP_LARGE_FRAME) != 0 && header.pt == (RTP_TYPE_VIDEO % 128)) { + return handle_video_packet(session, &header, data + RTP_HEADER_SIZE, length - RTP_HEADER_SIZE, m->log); + } + + // everything below here is for the old 16 bit protocol ------------------ + + if (header.data_length_lower == length - RTP_HEADER_SIZE) { + /* The message is sent in single part */ + + /* Message is not late; pick up the latest parameters */ + session->rsequnum = header.sequnum; + session->rtimestamp = header.timestamp; + bwc_add_recv(session->bwc, length); + + /* Invoke processing of active multiparted message */ + if (session->mp != nullptr) { + session->mcb(session->m->mono_time, session->cs, session->mp); + session->mp = nullptr; + } + + /* The message came in the allowed time; + */ + + return session->mcb(session->m->mono_time, session->cs, new_message(&header, length - RTP_HEADER_SIZE, + data + RTP_HEADER_SIZE, length - RTP_HEADER_SIZE)); + } + + /* The message is sent in multiple parts */ + + if (session->mp != nullptr) { + /* There are 2 possible situations in this case: + * 1) being that we got the part of already processing message. + * 2) being that we got the part of a new/old message. + * + * We handle them differently as we only allow a single multiparted + * processing message + */ + if (session->mp->header.sequnum == header.sequnum && + session->mp->header.timestamp == header.timestamp) { + /* First case */ + + /* Make sure we have enough allocated memory */ + if (session->mp->header.data_length_lower - session->mp->len < length - RTP_HEADER_SIZE || + session->mp->header.data_length_lower <= header.offset_lower) { + /* There happened to be some corruption on the stream; + * continue wihtout this part + */ + return 0; + } + + memcpy(session->mp->data + header.offset_lower, data + RTP_HEADER_SIZE, + length - RTP_HEADER_SIZE); + session->mp->len += length - RTP_HEADER_SIZE; + bwc_add_recv(session->bwc, length); + + if (session->mp->len == session->mp->header.data_length_lower) { + /* Received a full message; now push it for the further + * processing. + */ + session->mcb(session->m->mono_time, session->cs, session->mp); + session->mp = nullptr; + } + } else { + /* Second case */ + if (session->mp->header.timestamp > header.timestamp) { + /* The received message part is from the old message; + * discard it. + */ + return 0; + } + + /* Push the previous message for processing */ + session->mcb(session->m->mono_time, session->cs, session->mp); + + session->mp = nullptr; + goto NEW_MULTIPARTED; + } + } else { + /* In this case threat the message as if it was received in order + */ + /* This is also a point for new multiparted messages */ +NEW_MULTIPARTED: + + /* Message is not late; pick up the latest parameters */ + session->rsequnum = header.sequnum; + session->rtimestamp = header.timestamp; + bwc_add_recv(session->bwc, length); + + /* Store message. + */ + session->mp = new_message(&header, header.data_length_lower, data + RTP_HEADER_SIZE, length - RTP_HEADER_SIZE); + + if (session->mp != nullptr) { + memmove(session->mp->data + header.offset_lower, session->mp->data, session->mp->len); + } else { + LOGGER_WARNING(m->log, "new_message() returned a null pointer"); + return -1; + } + } + + return 0; +} + +size_t rtp_header_pack(uint8_t *const rdata, const struct RTPHeader *header) +{ + uint8_t *p = rdata; + *p = (header->ve & 3) << 6 + | (header->pe & 1) << 5 + | (header->xe & 1) << 4 + | (header->cc & 0xf); + ++p; + *p = (header->ma & 1) << 7 + | (header->pt & 0x7f); + ++p; + + p += net_pack_u16(p, header->sequnum); + p += net_pack_u32(p, header->timestamp); + p += net_pack_u32(p, header->ssrc); + p += net_pack_u64(p, header->flags); + p += net_pack_u32(p, header->offset_full); + p += net_pack_u32(p, header->data_length_full); + p += net_pack_u32(p, header->received_length_full); + + for (size_t i = 0; i < RTP_PADDING_FIELDS; ++i) { + p += net_pack_u32(p, 0); + } + + p += net_pack_u16(p, header->offset_lower); + p += net_pack_u16(p, header->data_length_lower); + assert(p == rdata + RTP_HEADER_SIZE); + return p - rdata; +} + +size_t rtp_header_unpack(const uint8_t *data, struct RTPHeader *header) +{ + const uint8_t *p = data; + header->ve = (*p >> 6) & 3; + header->pe = (*p >> 5) & 1; + header->xe = (*p >> 4) & 1; + header->cc = *p & 0xf; + ++p; + + header->ma = (*p >> 7) & 1; + header->pt = *p & 0x7f; + ++p; + + p += net_unpack_u16(p, &header->sequnum); + p += net_unpack_u32(p, &header->timestamp); + p += net_unpack_u32(p, &header->ssrc); + p += net_unpack_u64(p, &header->flags); + p += net_unpack_u32(p, &header->offset_full); + p += net_unpack_u32(p, &header->data_length_full); + p += net_unpack_u32(p, &header->received_length_full); + + p += sizeof(uint32_t) * RTP_PADDING_FIELDS; + + p += net_unpack_u16(p, &header->offset_lower); + p += net_unpack_u16(p, &header->data_length_lower); + assert(p == data + RTP_HEADER_SIZE); + return p - data; +} + +RTPSession *rtp_new(int payload_type, Messenger *m, Tox *tox, uint32_t friendnumber, + BWController *bwc, void *cs, rtp_m_cb *mcb) +{ + assert(mcb != nullptr); + assert(cs != nullptr); + assert(m != nullptr); + + RTPSession *session = (RTPSession *)calloc(1, sizeof(RTPSession)); + + if (session == nullptr) { + LOGGER_WARNING(m->log, "Alloc failed! Program might misbehave!"); + return nullptr; + } + + session->work_buffer_list = (struct RTPWorkBufferList *)calloc(1, sizeof(struct RTPWorkBufferList)); + + if (session->work_buffer_list == nullptr) { + LOGGER_ERROR(m->log, "out of memory while allocating work buffer list"); + free(session); + return nullptr; + } + + // First entry is free. + session->work_buffer_list->next_free_entry = 0; + + session->ssrc = payload_type == RTP_TYPE_VIDEO ? 0 : random_u32(m->rng); + session->payload_type = payload_type; + session->m = m; + session->tox = tox; + session->friend_number = friendnumber; + + // set NULL just in case + session->mp = nullptr; + session->first_packets_counter = 1; + + /* Also set payload type as prefix */ + session->bwc = bwc; + session->cs = cs; + session->mcb = mcb; + + if (-1 == rtp_allow_receiving(session)) { + LOGGER_WARNING(m->log, "Failed to start rtp receiving mode"); + free(session->work_buffer_list); + free(session); + return nullptr; + } + + return session; +} + +void rtp_kill(RTPSession *session) +{ + if (session == nullptr) { + return; + } + + LOGGER_DEBUG(session->m->log, "Terminated RTP session: %p", (void *)session); + rtp_stop_receiving(session); + + LOGGER_DEBUG(session->m->log, "Terminated RTP session V3 work_buffer_list->next_free_entry: %d", + (int)session->work_buffer_list->next_free_entry); + + for (int8_t i = 0; i < session->work_buffer_list->next_free_entry; ++i) { + free(session->work_buffer_list->work_buffer[i].buf); + } + free(session->work_buffer_list); + free(session); +} + +int rtp_allow_receiving(RTPSession *session) +{ + if (session == nullptr) { + return -1; + } + + if (m_callback_rtp_packet(session->m, session->friend_number, session->payload_type, + handle_rtp_packet, session) == -1) { + LOGGER_WARNING(session->m->log, "Failed to register rtp receive handler"); + return -1; + } + + LOGGER_DEBUG(session->m->log, "Started receiving on session: %p", (void *)session); + return 0; +} + +int rtp_stop_receiving(RTPSession *session) +{ + if (session == nullptr) { + return -1; + } + + m_callback_rtp_packet(session->m, session->friend_number, session->payload_type, nullptr, nullptr); + + LOGGER_DEBUG(session->m->log, "Stopped receiving on session: %p", (void *)session); + return 0; +} + +/** + * Send a frame of audio or video data, chunked in @ref RTPMessage instances. + * + * @param session The A/V session to send the data for. + * @param data A byte array of length @p length. + * @param length The number of bytes to send from @p data. + * @param is_keyframe Whether this video frame is a key frame. If it is an + * audio frame, this parameter is ignored. + */ +int rtp_send_data(RTPSession *session, const uint8_t *data, uint32_t length, + bool is_keyframe, const Logger *log) +{ + if (session == nullptr) { + LOGGER_ERROR(log, "No session!"); + return -1; + } + + struct RTPHeader header = {0}; + + header.ve = 2; // this is unused in toxav + + header.pe = 0; + + header.xe = 0; + + header.cc = 0; + + header.ma = 0; + + header.pt = session->payload_type % 128; + + header.sequnum = session->sequnum; + + header.timestamp = current_time_monotonic(session->m->mono_time); + + header.ssrc = session->ssrc; + + header.offset_lower = 0; + + // here the highest bits gets stripped anyway, no need to do keyframe bit magic here! + header.data_length_lower = length; + + if (session->payload_type == RTP_TYPE_VIDEO) { + header.flags = RTP_LARGE_FRAME; + } + + uint16_t length_safe = (uint16_t)length; + + if (length > UINT16_MAX) { + length_safe = UINT16_MAX; + } + + header.data_length_lower = length_safe; + header.data_length_full = length; // without header + header.offset_lower = 0; + header.offset_full = 0; + + if (is_keyframe) { + header.flags |= RTP_KEY_FRAME; + } + + VLA(uint8_t, rdata, length + RTP_HEADER_SIZE + 1); + memset(rdata, 0, SIZEOF_VLA(rdata)); + rdata[0] = session->payload_type; // packet id == payload_type + + if (MAX_CRYPTO_DATA_SIZE > (length + RTP_HEADER_SIZE + 1)) { + /* + * The length is lesser than the maximum allowed length (including header) + * Send the packet in single piece. + */ + rtp_header_pack(rdata + 1, &header); + memcpy(rdata + 1 + RTP_HEADER_SIZE, data, length); + + if (-1 == rtp_send_custom_lossy_packet(session->tox, session->friend_number, rdata, SIZEOF_VLA(rdata))) { + char *netstrerror = net_new_strerror(net_error()); + LOGGER_WARNING(session->m->log, "RTP send failed (len: %u)! net error: %s", + (unsigned)SIZEOF_VLA(rdata), netstrerror); + net_kill_strerror(netstrerror); + } + } else { + /* + * The length is greater than the maximum allowed length (including header) + * Send the packet in multiple pieces. + */ + uint32_t sent = 0; + uint16_t piece = MAX_CRYPTO_DATA_SIZE - (RTP_HEADER_SIZE + 1); + + while ((length - sent) + RTP_HEADER_SIZE + 1 > MAX_CRYPTO_DATA_SIZE) { + rtp_header_pack(rdata + 1, &header); + memcpy(rdata + 1 + RTP_HEADER_SIZE, data + sent, piece); + + if (-1 == rtp_send_custom_lossy_packet(session->tox, session->friend_number, + rdata, piece + RTP_HEADER_SIZE + 1)) { + char *netstrerror = net_new_strerror(net_error()); + LOGGER_WARNING(session->m->log, "RTP send failed (len: %d)! net error: %s", + piece + RTP_HEADER_SIZE + 1, netstrerror); + net_kill_strerror(netstrerror); + } + + sent += piece; + header.offset_lower = sent; + header.offset_full = sent; // raw data offset, without any header + } + + /* Send remaining */ + piece = length - sent; + + if (piece != 0) { + rtp_header_pack(rdata + 1, &header); + memcpy(rdata + 1 + RTP_HEADER_SIZE, data + sent, piece); + + if (-1 == rtp_send_custom_lossy_packet(session->tox, session->friend_number, rdata, + piece + RTP_HEADER_SIZE + 1)) { + char *netstrerror = net_new_strerror(net_error()); + LOGGER_WARNING(session->m->log, "RTP send failed (len: %d)! net error: %s", + piece + RTP_HEADER_SIZE + 1, netstrerror); + net_kill_strerror(netstrerror); + } + } + } + + ++session->sequnum; + return 0; +} diff --git a/local_pod_repo/toxcore/toxcore/toxav/toxav.h b/local_pod_repo/toxcore/toxcore/toxav/toxav.h new file mode 100644 index 0000000..86e54a6 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/toxav.h @@ -0,0 +1,849 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ + +/** @file + * @brief Public audio/video API for Tox clients. + * + * This API can handle multiple calls. Each call has its state, in very rare + * occasions the library can change the state of the call without apps knowledge. + * + * @section av_events Events and callbacks + * + * As in Core API, events are handled by callbacks. One callback can be + * registered per event. All events have a callback function type named + * `toxav_{event}_cb` and a function to register it named `toxav_callback_{event}`. + * Passing a NULL callback will result in no callback being registered for that + * event. Only one callback per event can be registered, so if a client needs + * multiple event listeners, it needs to implement the dispatch functionality + * itself. Unlike Core API, lack of some event handlers will cause the the + * library to drop calls before they are started. Hanging up call from a + * callback causes undefined behaviour. + * + * @section av_threading Threading implications + * + * Only toxav_iterate is thread-safe, all other functions must run from the + * tox thread. + * + * Important exceptions are the `*_iterate` and `*_iterate_interval` + * functions. You have to choose either the single thread or the multi thread + * functions and read their documentation. + * + * A common way to run ToxAV (multiple or single instance) is to have a thread, + * separate from tox instance thread, running a simple toxav_iterate loop, + * sleeping for `toxav_iteration_interval * milliseconds` on each iteration. + * + * An important thing to note is that events are triggered from both tox and + * toxav thread (see above). Audio and video receive frame events are triggered + * from toxav thread while all the other events are triggered from tox thread. + * + * Tox thread has priority with mutex mechanisms. Any api function can + * fail if mutexes are held by tox thread in which case they will set SYNC + * error code. + * + * @subsection av_multi_threading Separate audio and video threads + * + * ToxAV supports either a single thread for audio and video or decoding and + * encoding them in separate threads. You have to choose one mode and can not + * mix function calls to those different modes. + * + * For best results use the multi-threaded mode and run the audio thread with + * higher priority than the video thread. This prioritizes audio over video. + */ +#ifndef C_TOXCORE_TOXAV_TOXAV_H +#define C_TOXCORE_TOXAV_TOXAV_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * External Tox type. + */ +#ifndef TOX_DEFINED +#define TOX_DEFINED +typedef struct Tox Tox; +#endif /* TOX_DEFINED */ + +/** + * @brief The ToxAV instance type. + * + * Each ToxAV instance can be bound to only one Tox instance, and Tox instance + * can have only one ToxAV instance. One must make sure to close ToxAV instance + * prior closing Tox instance otherwise undefined behaviour occurs. Upon + * closing of ToxAV instance, all active calls will be forcibly terminated + * without notifying peers. + */ +typedef struct ToxAV ToxAV; + + +/** @{ + * @brief Creation and destruction + */ + +typedef enum Toxav_Err_New { + + /** + * The function returned successfully. + */ + TOXAV_ERR_NEW_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOXAV_ERR_NEW_NULL, + + /** + * Memory allocation failure while trying to allocate structures required for + * the A/V session. + */ + TOXAV_ERR_NEW_MALLOC, + + /** + * Attempted to create a second session for the same Tox instance. + */ + TOXAV_ERR_NEW_MULTIPLE, + +} Toxav_Err_New; + + +/** + * Start new A/V session. There can only be only one session per Tox instance. + */ +ToxAV *toxav_new(Tox *tox, Toxav_Err_New *error); + +/** + * Releases all resources associated with the A/V session. + * + * If any calls were ongoing, these will be forcibly terminated without + * notifying peers. After calling this function, no other functions may be + * called and the av pointer becomes invalid. + */ +void toxav_kill(ToxAV *av); + +/** + * Returns the Tox instance the A/V object was created for. + */ +Tox *toxav_get_tox(const ToxAV *av); + +/** @} */ + + +/** @{ + * @brief A/V event loop, single thread + */ + +/** + * Returns the interval in milliseconds when the next toxav_iterate call should + * be. If no call is active at the moment, this function returns 200. + * This function MUST be called from the same thread as toxav_iterate. + */ +uint32_t toxav_iteration_interval(const ToxAV *av); + +/** + * Main loop for the session. This function needs to be called in intervals of + * `toxav_iteration_interval()` milliseconds. It is best called in the separate + * thread from tox_iterate. + */ +void toxav_iterate(ToxAV *av); + +/** @} */ + + +/** @{ + * @brief A/V event loop, multiple threads + */ + +/** + * Returns the interval in milliseconds when the next toxav_audio_iterate call + * should be. If no call is active at the moment, this function returns 200. + * This function MUST be called from the same thread as toxav_audio_iterate. + */ +uint32_t toxav_audio_iteration_interval(const ToxAV *av); + +/** + * Main loop for the session. This function needs to be called in intervals of + * `toxav_audio_iteration_interval()` milliseconds. It is best called in a + * separate thread from tox_iterate and toxav_video_iterate. The thread calling + * this function should have higher priority than the one calling + * toxav_video_iterate to prioritize audio over video. + */ +void toxav_audio_iterate(ToxAV *av); + +/** + * Returns the interval in milliseconds when the next toxav_video_iterate call + * should be. If no call is active at the moment, this function returns 200. + * This function MUST be called from the same thread as toxav_video_iterate. + */ +uint32_t toxav_video_iteration_interval(const ToxAV *av); + +/** + * Main loop for the session. This function needs to be called in intervals of + * `toxav_video_iteration_interval()` milliseconds. It is best called in a + * separate thread from tox_iterate and toxav_audio_iterate. The thread calling + * this function should have lower priority than the one calling + * toxav_audio_iterate to prioritize audio over video. + */ +void toxav_video_iterate(ToxAV *av); + +/** @} */ + + +/** @{ + * @brief Call setup + */ + +typedef enum Toxav_Err_Call { + + /** + * The function returned successfully. + */ + TOXAV_ERR_CALL_OK, + + /** + * A resource allocation error occurred while trying to create the structures + * required for the call. + */ + TOXAV_ERR_CALL_MALLOC, + + /** + * Synchronization error occurred. + */ + TOXAV_ERR_CALL_SYNC, + + /** + * The friend number did not designate a valid friend. + */ + TOXAV_ERR_CALL_FRIEND_NOT_FOUND, + + /** + * The friend was valid, but not currently connected. + */ + TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED, + + /** + * Attempted to call a friend while already in an audio or video call with + * them. + */ + TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL, + + /** + * Audio or video bit rate is invalid. + */ + TOXAV_ERR_CALL_INVALID_BIT_RATE, + +} Toxav_Err_Call; + + +/** + * Call a friend. This will start ringing the friend. + * + * It is the client's responsibility to stop ringing after a certain timeout, + * if such behaviour is desired. If the client does not stop ringing, the + * library will not stop until the friend is disconnected. Audio and video + * receiving are both enabled by default. + * + * @param friend_number The friend number of the friend that should be called. + * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable + * audio sending. + * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable + * video sending. + */ +bool toxav_call(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, + Toxav_Err_Call *error); + +/** + * The function type for the call callback. + * + * @param friend_number The friend number from which the call is incoming. + * @param audio_enabled True if friend is sending audio. + * @param video_enabled True if friend is sending video. + */ +typedef void toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data); + + +/** + * Set the callback for the `call` event. Pass NULL to unset. + * + */ +void toxav_callback_call(ToxAV *av, toxav_call_cb *callback, void *user_data); + +typedef enum Toxav_Err_Answer { + + /** + * The function returned successfully. + */ + TOXAV_ERR_ANSWER_OK, + + /** + * Synchronization error occurred. + */ + TOXAV_ERR_ANSWER_SYNC, + + /** + * Failed to initialize codecs for call session. Note that codec initiation + * will fail if there is no receive callback registered for either audio or + * video. + */ + TOXAV_ERR_ANSWER_CODEC_INITIALIZATION, + + /** + * The friend number did not designate a valid friend. + */ + TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND, + + /** + * The friend was valid, but they are not currently trying to initiate a call. + * This is also returned if this client is already in a call with the friend. + */ + TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING, + + /** + * Audio or video bit rate is invalid. + */ + TOXAV_ERR_ANSWER_INVALID_BIT_RATE, + +} Toxav_Err_Answer; + + +/** + * Accept an incoming call. + * + * If answering fails for any reason, the call will still be pending and it is + * possible to try and answer it later. Audio and video receiving are both + * enabled by default. + * + * @param friend_number The friend number of the friend that is calling. + * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable + * audio sending. + * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable + * video sending. + */ +bool toxav_answer(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, + Toxav_Err_Answer *error); + +/** @} */ + + +/** @{ + * @brief Call state graph + */ + +enum Toxav_Friend_Call_State { + + /** + * The empty bit mask. None of the bits specified below are set. + */ + TOXAV_FRIEND_CALL_STATE_NONE = 0, + + /** + * Set by the AV core if an error occurred on the remote end or if friend + * timed out. This is the final state after which no more state + * transitions can occur for the call. This call state will never be triggered + * in combination with other call states. + */ + TOXAV_FRIEND_CALL_STATE_ERROR = 1, + + /** + * The call has finished. This is the final state after which no more state + * transitions can occur for the call. This call state will never be + * triggered in combination with other call states. + */ + TOXAV_FRIEND_CALL_STATE_FINISHED = 2, + + /** + * The flag that marks that friend is sending audio. + */ + TOXAV_FRIEND_CALL_STATE_SENDING_A = 4, + + /** + * The flag that marks that friend is sending video. + */ + TOXAV_FRIEND_CALL_STATE_SENDING_V = 8, + + /** + * The flag that marks that friend is receiving audio. + */ + TOXAV_FRIEND_CALL_STATE_ACCEPTING_A = 16, + + /** + * The flag that marks that friend is receiving video. + */ + TOXAV_FRIEND_CALL_STATE_ACCEPTING_V = 32, + +}; + + +/** + * The function type for the call_state callback. + * + * @param friend_number The friend number for which the call state changed. + * @param state The bitmask of the new call state which is guaranteed to be + * different than the previous state. The state is set to 0 when the call is + * paused. The bitmask represents all the activities currently performed by the + * friend. + */ +typedef void toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data); + + +/** + * Set the callback for the `call_state` event. Pass NULL to unset. + * + */ +void toxav_callback_call_state(ToxAV *av, toxav_call_state_cb *callback, void *user_data); + +/** @} */ + + +/** @{ + * @brief Call control + */ + +typedef enum Toxav_Call_Control { + + /** + * Resume a previously paused call. Only valid if the pause was caused by this + * client, if not, this control is ignored. Not valid before the call is accepted. + */ + TOXAV_CALL_CONTROL_RESUME, + + /** + * Put a call on hold. Not valid before the call is accepted. + */ + TOXAV_CALL_CONTROL_PAUSE, + + /** + * Reject a call if it was not answered, yet. Cancel a call after it was + * answered. + */ + TOXAV_CALL_CONTROL_CANCEL, + + /** + * Request that the friend stops sending audio. Regardless of the friend's + * compliance, this will cause the audio_receive_frame event to stop being + * triggered on receiving an audio frame from the friend. + */ + TOXAV_CALL_CONTROL_MUTE_AUDIO, + + /** + * Calling this control will notify client to start sending audio again. + */ + TOXAV_CALL_CONTROL_UNMUTE_AUDIO, + + /** + * Request that the friend stops sending video. Regardless of the friend's + * compliance, this will cause the video_receive_frame event to stop being + * triggered on receiving a video frame from the friend. + */ + TOXAV_CALL_CONTROL_HIDE_VIDEO, + + /** + * Calling this control will notify client to start sending video again. + */ + TOXAV_CALL_CONTROL_SHOW_VIDEO, + +} Toxav_Call_Control; + + +typedef enum Toxav_Err_Call_Control { + + /** + * The function returned successfully. + */ + TOXAV_ERR_CALL_CONTROL_OK, + + /** + * Synchronization error occurred. + */ + TOXAV_ERR_CALL_CONTROL_SYNC, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND, + + /** + * This client is currently not in a call with the friend. Before the call is + * answered, only CANCEL is a valid control. + */ + TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL, + + /** + * Happens if user tried to pause an already paused call or if trying to + * resume a call that is not paused. + */ + TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION, + +} Toxav_Err_Call_Control; + + +/** + * Sends a call control command to a friend. + * + * @param friend_number The friend number of the friend this client is in a call + * with. + * @param control The control command to send. + * + * @return true on success. + */ +bool toxav_call_control(ToxAV *av, uint32_t friend_number, Toxav_Call_Control control, Toxav_Err_Call_Control *error); + +/** @} */ + + +/** @{ + * @brief Controlling bit rates + */ + +typedef enum Toxav_Err_Bit_Rate_Set { + + /** + * The function returned successfully. + */ + TOXAV_ERR_BIT_RATE_SET_OK, + + /** + * Synchronization error occurred. + */ + TOXAV_ERR_BIT_RATE_SET_SYNC, + + /** + * The bit rate passed was not one of the supported values. + */ + TOXAV_ERR_BIT_RATE_SET_INVALID_BIT_RATE, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND, + + /** + * This client is currently not in a call with the friend. + */ + TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL, + +} Toxav_Err_Bit_Rate_Set; + +/** @} */ + + + +/** @{ + * @brief A/V sending + */ + +typedef enum Toxav_Err_Send_Frame { + + /** + * The function returned successfully. + */ + TOXAV_ERR_SEND_FRAME_OK, + + /** + * In case of video, one of Y, U, or V was NULL. In case of audio, the samples + * data pointer was NULL. + */ + TOXAV_ERR_SEND_FRAME_NULL, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND, + + /** + * This client is currently not in a call with the friend. + */ + TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL, + + /** + * Synchronization error occurred. + */ + TOXAV_ERR_SEND_FRAME_SYNC, + + /** + * One of the frame parameters was invalid. E.g. the resolution may be too + * small or too large, or the audio sampling rate may be unsupported. + */ + TOXAV_ERR_SEND_FRAME_INVALID, + + /** + * Either friend turned off audio or video receiving or we turned off sending + * for the said payload. + */ + TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED, + + /** + * Failed to push frame through rtp interface. + */ + TOXAV_ERR_SEND_FRAME_RTP_FAILED, + +} Toxav_Err_Send_Frame; + + +/** + * Send an audio frame to a friend. + * + * The expected format of the PCM data is: `[s1c1][s1c2][...][s2c1][s2c2][...]...` + * Meaning: sample 1 for channel 1, sample 1 for channel 2, ... + * For mono audio, this has no meaning, every sample is subsequent. For stereo, + * this means the expected format is LRLRLR... with samples for left and right + * alternating. + * + * @param friend_number The friend number of the friend to which to send an + * audio frame. + * @param pcm An array of audio samples. The size of this array must be + * `sample_count * channels`. + * @param sample_count Number of samples in this frame. Valid numbers here are + * `((sample rate) * (audio length) / 1000)`, where audio length can be + * 2.5, 5, 10, 20, 40 or 60 millseconds. + * @param channels Number of audio channels. Supported values are 1 and 2. + * @param sampling_rate Audio sampling rate used in this frame. Valid sampling + * rates are 8000, 12000, 16000, 24000, or 48000. + */ +bool toxav_audio_send_frame(ToxAV *av, uint32_t friend_number, const int16_t *pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate, Toxav_Err_Send_Frame *error); + +/** + * Set the bit rate to be used in subsequent video frames. + * + * @param friend_number The friend number of the friend for which to set the + * bit rate. + * @param bit_rate The new audio bit rate in Kb/sec. Set to 0 to disable. + * + * @return true on success. + */ +bool toxav_audio_set_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, Toxav_Err_Bit_Rate_Set *error); + +/** + * The function type for the audio_bit_rate callback. The event is triggered + * when the network becomes too saturated for current bit rates at which + * point core suggests new bit rates. + * + * @param friend_number The friend number of the friend for which to set the + * bit rate. + * @param audio_bit_rate Suggested maximum audio bit rate in Kb/sec. + */ +typedef void toxav_audio_bit_rate_cb(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, void *user_data); + + +/** + * Set the callback for the `audio_bit_rate` event. Pass NULL to unset. + * + */ +void toxav_callback_audio_bit_rate(ToxAV *av, toxav_audio_bit_rate_cb *callback, void *user_data); + +/** + * Send a video frame to a friend. + * + * Y - plane should be of size: `height * width` + * U - plane should be of size: `(height/2) * (width/2)` + * V - plane should be of size: `(height/2) * (width/2)` + * + * @param friend_number The friend number of the friend to which to send a video + * frame. + * @param width Width of the frame in pixels. + * @param height Height of the frame in pixels. + * @param y Y (Luminance) plane data. + * @param u U (Chroma) plane data. + * @param v V (Chroma) plane data. + */ +bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, + const uint8_t *u, const uint8_t *v, Toxav_Err_Send_Frame *error); + +/** + * Set the bit rate to be used in subsequent video frames. + * + * @param friend_number The friend number of the friend for which to set the + * bit rate. + * @param bit_rate The new video bit rate in Kb/sec. Set to 0 to disable. + * + * @return true on success. + */ +bool toxav_video_set_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, Toxav_Err_Bit_Rate_Set *error); + +/** + * The function type for the video_bit_rate callback. The event is triggered + * when the network becomes too saturated for current bit rates at which + * point core suggests new bit rates. + * + * @param friend_number The friend number of the friend for which to set the + * bit rate. + * @param video_bit_rate Suggested maximum video bit rate in Kb/sec. + */ +typedef void toxav_video_bit_rate_cb(ToxAV *av, uint32_t friend_number, uint32_t video_bit_rate, void *user_data); + + +/** + * Set the callback for the `video_bit_rate` event. Pass NULL to unset. + * + */ +void toxav_callback_video_bit_rate(ToxAV *av, toxav_video_bit_rate_cb *callback, void *user_data); + +/** @} */ + + +/** @{ + * @brief A/V receiving + */ + +/** + * The function type for the audio_receive_frame callback. The callback can be + * called multiple times per single iteration depending on the amount of queued + * frames in the buffer. The received format is the same as in send function. + * + * @param friend_number The friend number of the friend who sent an audio frame. + * @param pcm An array of audio samples (`sample_count * channels` elements). + * @param sample_count The number of audio samples per channel in the PCM array. + * @param channels Number of audio channels. + * @param sampling_rate Sampling rate used in this frame. + * + */ +typedef void toxav_audio_receive_frame_cb(ToxAV *av, uint32_t friend_number, const int16_t *pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate, void *user_data); + + +/** + * Set the callback for the `audio_receive_frame` event. Pass NULL to unset. + * + */ +void toxav_callback_audio_receive_frame(ToxAV *av, toxav_audio_receive_frame_cb *callback, void *user_data); + +/** + * The function type for the video_receive_frame callback. + * + * The size of plane data is derived from width and height as documented + * below. + * + * Strides represent padding for each plane that may or may not be present. + * You must handle strides in your image processing code. Strides are + * negative if the image is bottom-up hence why you MUST `abs()` it when + * calculating plane buffer size. + * + * @param friend_number The friend number of the friend who sent a video frame. + * @param width Width of the frame in pixels. + * @param height Height of the frame in pixels. + * @param y Luminosity plane. `Size = MAX(width, abs(ystride)) * height`. + * @param u U chroma plane. `Size = MAX(width/2, abs(ustride)) * (height/2)`. + * @param v V chroma plane. `Size = MAX(width/2, abs(vstride)) * (height/2)`. + * + * @param ystride Luminosity plane stride. + * @param ustride U chroma plane stride. + * @param vstride V chroma plane stride. + */ +typedef void toxav_video_receive_frame_cb(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, + const uint8_t *y, const uint8_t *u, const uint8_t *v, int32_t ystride, int32_t ustride, int32_t vstride, + void *user_data); + + +/** + * Set the callback for the `video_receive_frame` event. Pass NULL to unset. + * + */ +void toxav_callback_video_receive_frame(ToxAV *av, toxav_video_receive_frame_cb *callback, void *user_data); + +/*** + * NOTE Compatibility with old toxav group calls. TODO(iphydf): remove + * + * TODO(iphydf): Use proper new API guidelines for these. E.g. don't use inline + * function types, don't have per-callback userdata, especially don't have one + * userdata per group. + */ + +// TODO(iphydf): Use this better typed one instead of the void-pointer one below. +typedef void toxav_group_audio_cb(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, + uint32_t samples, uint8_t channels, uint32_t sample_rate, void *user_data); + +typedef void toxav_audio_data_cb(void *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, + uint32_t samples, uint8_t channels, uint32_t sample_rate, void *userdata); + +/** @brief Create a new toxav group. + * + * @return group number on success. + * @retval -1 on failure. + * + * Note that total size of pcm in bytes is equal to `samples * channels * sizeof(int16_t)`. + */ +int toxav_add_av_groupchat(Tox *tox, toxav_audio_data_cb *audio_callback, void *userdata); + +/** @brief Join a AV group (you need to have been invited first). + * + * @return group number on success. + * @retval -1 on failure. + * + * Note that total size of pcm in bytes is equal to `samples * channels * sizeof(int16_t)`. + */ +int toxav_join_av_groupchat(Tox *tox, uint32_t friendnumber, const uint8_t *data, uint16_t length, + toxav_audio_data_cb *audio_callback, void *userdata); + +/** @brief Send audio to the group chat. + * + * @retval 0 on success. + * @retval -1 on failure. + * + * Note that total size of pcm in bytes is equal to `samples * channels * sizeof(int16_t)`. + * + * Valid number of samples are `(sample rate) * (audio length) / 1000` + * (Valid values for audio length are: 2.5, 5, 10, 20, 40 or 60 ms) + * Valid number of channels are 1 or 2. + * Valid sample rates are 8000, 12000, 16000, 24000, or 48000. + * + * Recommended values are: samples = 960, channels = 1, sample_rate = 48000 + */ +int toxav_group_send_audio(Tox *tox, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, + uint32_t sample_rate); + +/** @brief Enable A/V in a groupchat. + * + * A/V must be enabled on a groupchat for audio to be sent to it and for + * received audio to be handled. + * + * An A/V group created with `toxav_add_av_groupchat` or `toxav_join_av_groupchat` + * will start with A/V enabled. + * + * An A/V group loaded from a savefile will start with A/V disabled. + * + * @retval 0 on success. + * @retval -1 on failure. + * + * Note that total size of pcm in bytes is equal to `samples * channels * sizeof(int16_t)`. + */ +int toxav_groupchat_enable_av(Tox *tox, uint32_t groupnumber, + toxav_audio_data_cb *audio_callback, void *userdata); + +/** @brief Disable A/V in a groupchat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int toxav_groupchat_disable_av(Tox *tox, uint32_t groupnumber); + +/** @brief Return whether A/V is enabled in the groupchat. */ +bool toxav_groupchat_av_enabled(Tox *tox, uint32_t groupnumber); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +//!TOKSTYLE- +#ifndef DOXYGEN_IGNORE + +typedef Toxav_Err_Call TOXAV_ERR_CALL; +typedef Toxav_Err_New TOXAV_ERR_NEW; +typedef Toxav_Err_Answer TOXAV_ERR_ANSWER; +typedef Toxav_Err_Call_Control TOXAV_ERR_CALL_CONTROL; +typedef Toxav_Err_Bit_Rate_Set TOXAV_ERR_BIT_RATE_SET; +typedef Toxav_Err_Send_Frame TOXAV_ERR_SEND_FRAME; +typedef Toxav_Call_Control TOXAV_CALL_CONTROL; +typedef enum Toxav_Friend_Call_State TOXAV_FRIEND_CALL_STATE; + +#endif +//!TOKSTYLE+ + +#endif // C_TOXCORE_TOXAV_TOXAV_H diff --git a/local_pod_repo/toxcore/toxcore/toxav/toxav.m b/local_pod_repo/toxcore/toxcore/toxav/toxav.m new file mode 100644 index 0000000..4b14aa8 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/toxav.m @@ -0,0 +1,1503 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ +#include "toxav.h" + +#include +#include +#include +#include +#include + +#include "msi.h" +#include "rtp.h" + +#include "../toxcore/Messenger.h" +#include "../toxcore/ccompat.h" +#include "../toxcore/logger.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/tox_struct.h" +#include "../toxcore/util.h" + +// TODO(zoff99): don't hardcode this, let the application choose it +// VPX Info: Time to spend encoding, in microseconds (it's a *soft* deadline) +#define WANTED_MAX_ENCODER_FPS 40 +#define MAX_ENCODE_TIME_US (1000000 / WANTED_MAX_ENCODER_FPS) // to allow x fps + +#define VIDEO_SEND_X_KEYFRAMES_FIRST 7 // force the first n frames to be keyframes! + +/* + * VPX_DL_REALTIME (1) deadline parameter analogous to VPx REALTIME mode. + * VPX_DL_GOOD_QUALITY (1000000) deadline parameter analogous to VPx GOOD QUALITY mode. + * VPX_DL_BEST_QUALITY (0) deadline parameter analogous to VPx BEST QUALITY mode. + */ + +// iteration interval that is used when no call is active +#define IDLE_ITERATION_INTERVAL_MS 200 + +typedef struct ToxAVCall { + ToxAV *av; + + pthread_mutex_t mutex_audio[1]; + RTPSession *audio_rtp; + ACSession *audio; + + pthread_mutex_t mutex_video[1]; + RTPSession *video_rtp; + VCSession *video; + + BWController *bwc; + + bool active; + MSICall *msi_call; + uint32_t friend_number; + + uint32_t audio_bit_rate; /* Sending audio bit rate */ + uint32_t video_bit_rate; /* Sending video bit rate */ + + /** Required for monitoring changes in states */ + uint8_t previous_self_capabilities; + + pthread_mutex_t toxav_call_mutex[1]; + + struct ToxAVCall *prev; + struct ToxAVCall *next; +} ToxAVCall; + + +/** Decode time statistics */ +typedef struct DecodeTimeStats { + /** Measure count */ + int32_t count; + /** Last cycle total */ + int32_t total; + /** Average decoding time in ms */ + int32_t average; + + /** Calculated iteration interval */ + uint32_t interval; +} DecodeTimeStats; + +struct ToxAV { + Tox *tox; + Messenger *m; + MSISession *msi; + + /* Two-way storage: first is array of calls and second is list of calls with head and tail */ + ToxAVCall **calls; + uint32_t calls_tail; + uint32_t calls_head; + pthread_mutex_t mutex[1]; + + /* Call callback */ + toxav_call_cb *ccb; + void *ccb_user_data; + /* Call state callback */ + toxav_call_state_cb *scb; + void *scb_user_data; + /* Audio frame receive callback */ + toxav_audio_receive_frame_cb *acb; + void *acb_user_data; + /* Video frame receive callback */ + toxav_video_receive_frame_cb *vcb; + void *vcb_user_data; + /* Bit rate control callback */ + toxav_audio_bit_rate_cb *abcb; + void *abcb_user_data; + /* Bit rate control callback */ + toxav_video_bit_rate_cb *vbcb; + void *vbcb_user_data; + + /* keep track of decode times for audio and video */ + DecodeTimeStats audio_stats; + DecodeTimeStats video_stats; + /** ToxAV's own mono_time instance */ + Mono_Time *toxav_mono_time; +}; + +static void callback_bwc(BWController *bwc, uint32_t friend_number, float loss, void *user_data); + +static int callback_invite(void *toxav_inst, MSICall *call); +static int callback_start(void *toxav_inst, MSICall *call); +static int callback_end(void *toxav_inst, MSICall *call); +static int callback_error(void *toxav_inst, MSICall *call); +static int callback_capabilites(void *toxav_inst, MSICall *call); + +static bool audio_bit_rate_invalid(uint32_t bit_rate); +static bool video_bit_rate_invalid(uint32_t bit_rate); +static bool invoke_call_state_callback(ToxAV *av, uint32_t friend_number, uint32_t state); +static ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, Toxav_Err_Call *error); +static ToxAVCall *call_get(ToxAV *av, uint32_t friend_number); +static ToxAVCall *call_remove(ToxAVCall *call); +static bool call_prepare_transmission(ToxAVCall *call); +static void call_kill_transmission(ToxAVCall *call); + +/** + * @brief initialize d with default values + * @param d struct to be initialized, must not be nullptr + */ +static void init_decode_time_stats(DecodeTimeStats *d) +{ + assert(d != nullptr); + d->count = 0; + d->total = 0; + d->average = 0; + d->interval = IDLE_ITERATION_INTERVAL_MS; +} + +ToxAV *toxav_new(Tox *tox, Toxav_Err_New *error) +{ + Toxav_Err_New rc = TOXAV_ERR_NEW_OK; + ToxAV *av = nullptr; + + if (tox == nullptr) { + rc = TOXAV_ERR_NEW_NULL; + goto RETURN; + } + + // TODO(iphydf): Don't rely on toxcore internals. + Messenger *m; + m = tox->m; + + if (m->msi_packet != nullptr) { + rc = TOXAV_ERR_NEW_MULTIPLE; + goto RETURN; + } + + av = (ToxAV *)calloc(1, sizeof(ToxAV)); + + if (av == nullptr) { + LOGGER_WARNING(m->log, "Allocation failed!"); + rc = TOXAV_ERR_NEW_MALLOC; + goto RETURN; + } + + if (create_recursive_mutex(av->mutex) != 0) { + LOGGER_WARNING(m->log, "Mutex creation failed!"); + rc = TOXAV_ERR_NEW_MALLOC; + goto RETURN; + } + + av->tox = tox; + av->m = m; + av->toxav_mono_time = mono_time_new(nullptr, nullptr); + av->msi = msi_new(av->m); + + if (av->msi == nullptr) { + pthread_mutex_destroy(av->mutex); + rc = TOXAV_ERR_NEW_MALLOC; + goto RETURN; + } + + init_decode_time_stats(&av->audio_stats); + init_decode_time_stats(&av->video_stats); + av->msi->av = av; + + msi_callback_invite(av->msi, callback_invite); + msi_callback_start(av->msi, callback_start); + msi_callback_end(av->msi, callback_end); + msi_callback_error(av->msi, callback_error); + msi_callback_peertimeout(av->msi, callback_error); + msi_callback_capabilities(av->msi, callback_capabilites); + +RETURN: + + if (error != nullptr) { + *error = rc; + } + + if (rc != TOXAV_ERR_NEW_OK) { + free(av); + av = nullptr; + } + + return av; +} +void toxav_kill(ToxAV *av) +{ + if (av == nullptr) { + return; + } + + pthread_mutex_lock(av->mutex); + + /* To avoid possible deadlocks */ + while (av->msi != nullptr && msi_kill(av->msi, av->m->log) != 0) { + pthread_mutex_unlock(av->mutex); + pthread_mutex_lock(av->mutex); + } + + /* Msi kill will hang up all calls so just clean these calls */ + if (av->calls != nullptr) { + ToxAVCall *it = call_get(av, av->calls_head); + + while (it != nullptr) { + call_kill_transmission(it); + it->msi_call = nullptr; /* msi_kill() frees the call's msi_call handle; which causes #278 */ + it = call_remove(it); /* This will eventually free av->calls */ + } + } + + mono_time_free(av->toxav_mono_time); + + pthread_mutex_unlock(av->mutex); + pthread_mutex_destroy(av->mutex); + + free(av); +} +Tox *toxav_get_tox(const ToxAV *av) +{ + return av->tox; +} + +uint32_t toxav_audio_iteration_interval(const ToxAV *av) +{ + return av->calls != nullptr ? av->audio_stats.interval : IDLE_ITERATION_INTERVAL_MS; +} + +uint32_t toxav_video_iteration_interval(const ToxAV *av) +{ + return av->calls != nullptr ? av->video_stats.interval : IDLE_ITERATION_INTERVAL_MS; +} + +uint32_t toxav_iteration_interval(const ToxAV *av) +{ + return min_u32(toxav_audio_iteration_interval(av), + toxav_video_iteration_interval(av)); +} + +/** + * @brief calc_interval Calculates the needed iteration interval based on previous decode times + * @param av ToxAV struct to work on + * @param stats Statistics to update + * @param frame_time the duration of the current frame in ms + * @param start_time the timestamp when decoding of this frame started + */ +static void calc_interval(ToxAV *av, DecodeTimeStats *stats, int32_t frame_time, uint64_t start_time) +{ + stats->interval = frame_time < stats->average ? 0 : (frame_time - stats->average); + stats->total += current_time_monotonic(av->m->mono_time) - start_time; + + if (++stats->count == 3) { + stats->average = stats->total / 3 + 5; /* NOTE: Magic Offset for precision */ + stats->count = 0; + stats->total = 0; + } +} + +/** + * @brief common iterator function for audio and video calls + * @param av pointer to ToxAV structure of current instance + * @param audio if true, iterate audio, video else + */ +static void iterate_common(ToxAV *av, bool audio) +{ + pthread_mutex_lock(av->mutex); + + if (av->calls == nullptr) { + pthread_mutex_unlock(av->mutex); + return; + } + + const uint64_t start = current_time_monotonic(av->toxav_mono_time); + // time until the first audio or video frame is over + int32_t frame_time = IDLE_ITERATION_INTERVAL_MS; + + for (ToxAVCall *i = av->calls[av->calls_head]; i != nullptr; i = i->next) { + if (!i->active) { + continue; + } + + pthread_mutex_lock(i->toxav_call_mutex); + pthread_mutex_unlock(av->mutex); + + if (audio) { + ac_iterate(i->audio); + + if ((i->msi_call->self_capabilities & MSI_CAP_R_AUDIO) != 0 && + (i->msi_call->peer_capabilities & MSI_CAP_S_AUDIO) != 0) { + frame_time = min_s32(i->audio->lp_frame_duration, frame_time); + } + } else { + vc_iterate(i->video); + + if ((i->msi_call->self_capabilities & MSI_CAP_R_VIDEO) != 0 && + (i->msi_call->peer_capabilities & MSI_CAP_S_VIDEO) != 0) { + pthread_mutex_lock(i->video->queue_mutex); + frame_time = min_s32(i->video->lcfd, frame_time); + pthread_mutex_unlock(i->video->queue_mutex); + } + } + + const uint32_t fid = i->friend_number; + + pthread_mutex_unlock(i->toxav_call_mutex); + pthread_mutex_lock(av->mutex); + + /* In case this call is popped from container stop iteration */ + if (call_get(av, fid) != i) { + break; + } + } + + DecodeTimeStats *stats = audio ? &av->audio_stats : &av->video_stats; + calc_interval(av, stats, frame_time, start); + pthread_mutex_unlock(av->mutex); +} +void toxav_audio_iterate(ToxAV *av) +{ + iterate_common(av, true); +} + +void toxav_video_iterate(ToxAV *av) +{ + iterate_common(av, false); +} + +void toxav_iterate(ToxAV *av) +{ + toxav_audio_iterate(av); + toxav_video_iterate(av); +} + +bool toxav_call(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, + Toxav_Err_Call *error) +{ + Toxav_Err_Call rc = TOXAV_ERR_CALL_OK; + ToxAVCall *call; + + pthread_mutex_lock(av->mutex); + + if ((audio_bit_rate != 0 && audio_bit_rate_invalid(audio_bit_rate)) + || (video_bit_rate != 0 && video_bit_rate_invalid(video_bit_rate))) { + rc = TOXAV_ERR_CALL_INVALID_BIT_RATE; + goto RETURN; + } + + call = call_new(av, friend_number, &rc); + + if (call == nullptr) { + goto RETURN; + } + + call->audio_bit_rate = audio_bit_rate; + call->video_bit_rate = video_bit_rate; + + call->previous_self_capabilities = MSI_CAP_R_AUDIO | MSI_CAP_R_VIDEO; + + call->previous_self_capabilities |= audio_bit_rate > 0 ? MSI_CAP_S_AUDIO : 0; + call->previous_self_capabilities |= video_bit_rate > 0 ? MSI_CAP_S_VIDEO : 0; + + if (msi_invite(av->msi, &call->msi_call, friend_number, call->previous_self_capabilities) != 0) { + call_remove(call); + rc = TOXAV_ERR_CALL_SYNC; + goto RETURN; + } + + call->msi_call->av_call = call; + +RETURN: + pthread_mutex_unlock(av->mutex); + + if (error != nullptr) { + *error = rc; + } + + return rc == TOXAV_ERR_CALL_OK; +} +void toxav_callback_call(ToxAV *av, toxav_call_cb *callback, void *user_data) +{ + pthread_mutex_lock(av->mutex); + av->ccb = callback; + av->ccb_user_data = user_data; + pthread_mutex_unlock(av->mutex); +} +bool toxav_answer(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, + Toxav_Err_Answer *error) +{ + pthread_mutex_lock(av->mutex); + + Toxav_Err_Answer rc = TOXAV_ERR_ANSWER_OK; + ToxAVCall *call; + + if (!m_friend_exists(av->m, friend_number)) { + rc = TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND; + goto RETURN; + } + + if ((audio_bit_rate != 0 && audio_bit_rate_invalid(audio_bit_rate)) + || (video_bit_rate != 0 && video_bit_rate_invalid(video_bit_rate)) + ) { + rc = TOXAV_ERR_ANSWER_INVALID_BIT_RATE; + goto RETURN; + } + + call = call_get(av, friend_number); + + if (call == nullptr) { + rc = TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING; + goto RETURN; + } + + if (!call_prepare_transmission(call)) { + rc = TOXAV_ERR_ANSWER_CODEC_INITIALIZATION; + goto RETURN; + } + + call->audio_bit_rate = audio_bit_rate; + call->video_bit_rate = video_bit_rate; + + call->previous_self_capabilities = MSI_CAP_R_AUDIO | MSI_CAP_R_VIDEO; + + call->previous_self_capabilities |= audio_bit_rate > 0 ? MSI_CAP_S_AUDIO : 0; + call->previous_self_capabilities |= video_bit_rate > 0 ? MSI_CAP_S_VIDEO : 0; + + if (msi_answer(call->msi_call, call->previous_self_capabilities) != 0) { + rc = TOXAV_ERR_ANSWER_SYNC; + } + +RETURN: + pthread_mutex_unlock(av->mutex); + + if (error != nullptr) { + *error = rc; + } + + return rc == TOXAV_ERR_ANSWER_OK; +} +void toxav_callback_call_state(ToxAV *av, toxav_call_state_cb *callback, void *user_data) +{ + pthread_mutex_lock(av->mutex); + av->scb = callback; + av->scb_user_data = user_data; + pthread_mutex_unlock(av->mutex); +} +static Toxav_Err_Call_Control call_control_handle_resume(const ToxAVCall *call) +{ + /* Only act if paused and had media transfer active before */ + if (call->msi_call->self_capabilities != 0 || call->previous_self_capabilities == 0) { + return TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; + } + + if (msi_change_capabilities(call->msi_call, call->previous_self_capabilities) == -1) { + return TOXAV_ERR_CALL_CONTROL_SYNC; + } + + rtp_allow_receiving(call->audio_rtp); + rtp_allow_receiving(call->video_rtp); + + return TOXAV_ERR_CALL_CONTROL_OK; +} +static Toxav_Err_Call_Control call_control_handle_pause(ToxAVCall *call) +{ + /* Only act if not already paused */ + if (call->msi_call->self_capabilities == 0) { + return TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; + } + + call->previous_self_capabilities = call->msi_call->self_capabilities; + + if (msi_change_capabilities(call->msi_call, 0) == -1) { + return TOXAV_ERR_CALL_CONTROL_SYNC; + } + + rtp_stop_receiving(call->audio_rtp); + rtp_stop_receiving(call->video_rtp); + + return TOXAV_ERR_CALL_CONTROL_OK; +} +static Toxav_Err_Call_Control call_control_handle_cancel(ToxAVCall *call) +{ + /* Hang up */ + pthread_mutex_lock(call->toxav_call_mutex); + + if (msi_hangup(call->msi_call) != 0) { + pthread_mutex_unlock(call->toxav_call_mutex); + return TOXAV_ERR_CALL_CONTROL_SYNC; + } + + call->msi_call = nullptr; + pthread_mutex_unlock(call->toxav_call_mutex); + + /* No matter the case, terminate the call */ + call_kill_transmission(call); + call_remove(call); + + return TOXAV_ERR_CALL_CONTROL_OK; +} +static Toxav_Err_Call_Control call_control_handle_mute_audio(const ToxAVCall *call) +{ + if ((call->msi_call->self_capabilities & MSI_CAP_R_AUDIO) == 0) { + return TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; + } + + if (msi_change_capabilities(call->msi_call, call-> + msi_call->self_capabilities ^ MSI_CAP_R_AUDIO) == -1) { + return TOXAV_ERR_CALL_CONTROL_SYNC; + + } + + rtp_stop_receiving(call->audio_rtp); + return TOXAV_ERR_CALL_CONTROL_OK; +} +static Toxav_Err_Call_Control call_control_handle_unmute_audio(const ToxAVCall *call) +{ + if ((call->msi_call->self_capabilities ^ MSI_CAP_R_AUDIO) == 0) { + return TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; + } + + if (msi_change_capabilities(call->msi_call, call-> + msi_call->self_capabilities | MSI_CAP_R_AUDIO) == -1) { + return TOXAV_ERR_CALL_CONTROL_SYNC; + } + + rtp_allow_receiving(call->audio_rtp); + return TOXAV_ERR_CALL_CONTROL_OK; +} +static Toxav_Err_Call_Control call_control_handle_hide_video(const ToxAVCall *call) +{ + if ((call->msi_call->self_capabilities & MSI_CAP_R_VIDEO) == 0) { + return TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; + } + + if (msi_change_capabilities(call->msi_call, call-> + msi_call->self_capabilities ^ MSI_CAP_R_VIDEO) == -1) { + return TOXAV_ERR_CALL_CONTROL_SYNC; + } + + rtp_stop_receiving(call->video_rtp); + return TOXAV_ERR_CALL_CONTROL_OK; +} +static Toxav_Err_Call_Control call_control_handle_show_video(const ToxAVCall *call) +{ + if ((call->msi_call->self_capabilities ^ MSI_CAP_R_VIDEO) == 0) { + return TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; + } + + if (msi_change_capabilities(call->msi_call, call-> + msi_call->self_capabilities | MSI_CAP_R_VIDEO) == -1) { + return TOXAV_ERR_CALL_CONTROL_SYNC; + } + + rtp_allow_receiving(call->video_rtp); + return TOXAV_ERR_CALL_CONTROL_OK; +} +static Toxav_Err_Call_Control call_control_handle(ToxAVCall *call, Toxav_Call_Control control) +{ + switch (control) { + case TOXAV_CALL_CONTROL_RESUME: + return call_control_handle_resume(call); + + case TOXAV_CALL_CONTROL_PAUSE: + return call_control_handle_pause(call); + + case TOXAV_CALL_CONTROL_CANCEL: + return call_control_handle_cancel(call); + + case TOXAV_CALL_CONTROL_MUTE_AUDIO: + return call_control_handle_mute_audio(call); + + case TOXAV_CALL_CONTROL_UNMUTE_AUDIO: + return call_control_handle_unmute_audio(call); + + case TOXAV_CALL_CONTROL_HIDE_VIDEO: + return call_control_handle_hide_video(call); + + case TOXAV_CALL_CONTROL_SHOW_VIDEO: + return call_control_handle_show_video(call); + } + + return TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; +} +static Toxav_Err_Call_Control call_control(ToxAV *av, uint32_t friend_number, Toxav_Call_Control control) +{ + if (!m_friend_exists(av->m, friend_number)) { + return TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND; + } + + ToxAVCall *call = call_get(av, friend_number); + + if (call == nullptr || (!call->active && control != TOXAV_CALL_CONTROL_CANCEL)) { + return TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL; + } + + return call_control_handle(call, control); +} +bool toxav_call_control(ToxAV *av, uint32_t friend_number, Toxav_Call_Control control, Toxav_Err_Call_Control *error) +{ + pthread_mutex_lock(av->mutex); + + const Toxav_Err_Call_Control rc = call_control(av, friend_number, control); + + pthread_mutex_unlock(av->mutex); + + if (error != nullptr) { + *error = rc; + } + + return rc == TOXAV_ERR_CALL_CONTROL_OK; +} +bool toxav_audio_set_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, + Toxav_Err_Bit_Rate_Set *error) +{ + Toxav_Err_Bit_Rate_Set rc = TOXAV_ERR_BIT_RATE_SET_OK; + ToxAVCall *call; + + if (!m_friend_exists(av->m, friend_number)) { + rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND; + goto RETURN; + } + + if (bit_rate > 0 && audio_bit_rate_invalid(bit_rate)) { + rc = TOXAV_ERR_BIT_RATE_SET_INVALID_BIT_RATE; + goto RETURN; + } + + pthread_mutex_lock(av->mutex); + call = call_get(av, friend_number); + + if (call == nullptr || !call->active || call->msi_call->state != MSI_CALL_ACTIVE) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL; + goto RETURN; + } + + LOGGER_DEBUG(av->m->log, "Setting new audio bitrate to: %d", bit_rate); + + if (call->audio_bit_rate == bit_rate) { + LOGGER_DEBUG(av->m->log, "Audio bitrate already set to: %d", bit_rate); + } else if (bit_rate == 0) { + LOGGER_DEBUG(av->m->log, "Turned off audio sending"); + + if (msi_change_capabilities(call->msi_call, call->msi_call-> + self_capabilities ^ MSI_CAP_S_AUDIO) != 0) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_BIT_RATE_SET_SYNC; + goto RETURN; + } + + /* Audio sending is turned off; notify peer */ + call->audio_bit_rate = 0; + } else { + pthread_mutex_lock(call->toxav_call_mutex); + + if (call->audio_bit_rate == 0) { + LOGGER_DEBUG(av->m->log, "Turned on audio sending"); + + /* The audio has been turned off before this */ + if (msi_change_capabilities(call->msi_call, call-> + msi_call->self_capabilities | MSI_CAP_S_AUDIO) != 0) { + pthread_mutex_unlock(call->toxav_call_mutex); + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_BIT_RATE_SET_SYNC; + goto RETURN; + } + } else { + LOGGER_DEBUG(av->m->log, "Set new audio bit rate %d", bit_rate); + } + + call->audio_bit_rate = bit_rate; + pthread_mutex_unlock(call->toxav_call_mutex); + } + + pthread_mutex_unlock(av->mutex); +RETURN: + + if (error != nullptr) { + *error = rc; + } + + return rc == TOXAV_ERR_BIT_RATE_SET_OK; +} +bool toxav_video_set_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, + Toxav_Err_Bit_Rate_Set *error) +{ + Toxav_Err_Bit_Rate_Set rc = TOXAV_ERR_BIT_RATE_SET_OK; + ToxAVCall *call; + + if (!m_friend_exists(av->m, friend_number)) { + rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND; + goto RETURN; + } + + if (bit_rate > 0 && video_bit_rate_invalid(bit_rate)) { + rc = TOXAV_ERR_BIT_RATE_SET_INVALID_BIT_RATE; + goto RETURN; + } + + pthread_mutex_lock(av->mutex); + call = call_get(av, friend_number); + + if (call == nullptr || !call->active || call->msi_call->state != MSI_CALL_ACTIVE) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL; + goto RETURN; + } + + LOGGER_DEBUG(av->m->log, "Setting new video bitrate to: %d", bit_rate); + + if (call->video_bit_rate == bit_rate) { + LOGGER_DEBUG(av->m->log, "Video bitrate already set to: %d", bit_rate); + } else if (bit_rate == 0) { + LOGGER_DEBUG(av->m->log, "Turned off video sending"); + + /* Video sending is turned off; notify peer */ + if (msi_change_capabilities(call->msi_call, call->msi_call-> + self_capabilities ^ MSI_CAP_S_VIDEO) != 0) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_BIT_RATE_SET_SYNC; + goto RETURN; + } + + call->video_bit_rate = 0; + } else { + pthread_mutex_lock(call->toxav_call_mutex); + + if (call->video_bit_rate == 0) { + LOGGER_DEBUG(av->m->log, "Turned on video sending"); + + /* The video has been turned off before this */ + if (msi_change_capabilities(call->msi_call, call-> + msi_call->self_capabilities | MSI_CAP_S_VIDEO) != 0) { + pthread_mutex_unlock(call->toxav_call_mutex); + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_BIT_RATE_SET_SYNC; + goto RETURN; + } + } else { + LOGGER_DEBUG(av->m->log, "Set new video bit rate %d", bit_rate); + } + + call->video_bit_rate = bit_rate; + pthread_mutex_unlock(call->toxav_call_mutex); + } + + pthread_mutex_unlock(av->mutex); +RETURN: + + if (error != nullptr) { + *error = rc; + } + + return rc == TOXAV_ERR_BIT_RATE_SET_OK; +} +void toxav_callback_audio_bit_rate(ToxAV *av, toxav_audio_bit_rate_cb *callback, void *user_data) +{ + pthread_mutex_lock(av->mutex); + av->abcb = callback; + av->abcb_user_data = user_data; + pthread_mutex_unlock(av->mutex); +} +void toxav_callback_video_bit_rate(ToxAV *av, toxav_video_bit_rate_cb *callback, void *user_data) +{ + pthread_mutex_lock(av->mutex); + av->vbcb = callback; + av->vbcb_user_data = user_data; + pthread_mutex_unlock(av->mutex); +} +bool toxav_audio_send_frame(ToxAV *av, uint32_t friend_number, const int16_t *pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate, Toxav_Err_Send_Frame *error) +{ + Toxav_Err_Send_Frame rc = TOXAV_ERR_SEND_FRAME_OK; + ToxAVCall *call; + + if (!m_friend_exists(av->m, friend_number)) { + rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND; + goto RETURN; + } + + if (pthread_mutex_trylock(av->mutex) != 0) { + rc = TOXAV_ERR_SEND_FRAME_SYNC; + goto RETURN; + } + + call = call_get(av, friend_number); + + if (call == nullptr || !call->active || call->msi_call->state != MSI_CALL_ACTIVE) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL; + goto RETURN; + } + + if (call->audio_bit_rate == 0 || + (call->msi_call->self_capabilities & MSI_CAP_S_AUDIO) == 0 || + (call->msi_call->peer_capabilities & MSI_CAP_R_AUDIO) == 0) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED; + goto RETURN; + } + + pthread_mutex_lock(call->mutex_audio); + pthread_mutex_unlock(av->mutex); + + if (pcm == nullptr) { + pthread_mutex_unlock(call->mutex_audio); + rc = TOXAV_ERR_SEND_FRAME_NULL; + goto RETURN; + } + + if (channels > 2) { + pthread_mutex_unlock(call->mutex_audio); + rc = TOXAV_ERR_SEND_FRAME_INVALID; + goto RETURN; + } + + { /* Encode and send */ + if (ac_reconfigure_encoder(call->audio, call->audio_bit_rate * 1000, sampling_rate, channels) != 0) { + pthread_mutex_unlock(call->mutex_audio); + rc = TOXAV_ERR_SEND_FRAME_INVALID; + goto RETURN; + } + + VLA(uint8_t, dest, sample_count + sizeof(sampling_rate)); /* This is more than enough always */ + + sampling_rate = net_htonl(sampling_rate); + memcpy(dest, &sampling_rate, sizeof(sampling_rate)); + const int vrc = opus_encode(call->audio->encoder, pcm, sample_count, + dest + sizeof(sampling_rate), SIZEOF_VLA(dest) - sizeof(sampling_rate)); + + if (vrc < 0) { + LOGGER_WARNING(av->m->log, "Failed to encode frame %s", opus_strerror(vrc)); + pthread_mutex_unlock(call->mutex_audio); + rc = TOXAV_ERR_SEND_FRAME_INVALID; + goto RETURN; + } + + if (rtp_send_data(call->audio_rtp, dest, vrc + sizeof(sampling_rate), false, av->m->log) != 0) { + LOGGER_WARNING(av->m->log, "Failed to send audio packet"); + rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; + } + } + + pthread_mutex_unlock(call->mutex_audio); + +RETURN: + + if (error != nullptr) { + *error = rc; + } + + return rc == TOXAV_ERR_SEND_FRAME_OK; +} + +static Toxav_Err_Send_Frame send_frames(const Logger *log, ToxAVCall *call) +{ + vpx_codec_iter_t iter = nullptr; + + for (const vpx_codec_cx_pkt_t *pkt = vpx_codec_get_cx_data(call->video->encoder, &iter); + pkt != nullptr; + pkt = vpx_codec_get_cx_data(call->video->encoder, &iter)) { + if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) { + continue; + } + + const bool is_keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0; + + // https://www.webmproject.org/docs/webm-sdk/structvpx__codec__cx__pkt.html + // pkt->data.frame.sz -> size_t + const uint32_t frame_length_in_bytes = pkt->data.frame.sz; + + const int res = rtp_send_data( + call->video_rtp, + (const uint8_t *)pkt->data.frame.buf, + frame_length_in_bytes, + is_keyframe, + log); + + LOGGER_DEBUG(log, "+ _sending_FRAME_TYPE_==%s bytes=%d frame_len=%d", is_keyframe ? "K" : ".", + (int)pkt->data.frame.sz, (int)frame_length_in_bytes); + const uint8_t *const buf = (const uint8_t *)pkt->data.frame.buf; + LOGGER_DEBUG(log, "+ _sending_FRAME_ b0=%d b1=%d", buf[0], buf[1]); + + if (res < 0) { + char *netstrerror = net_new_strerror(net_error()); + LOGGER_WARNING(log, "Could not send video frame: %s", netstrerror); + net_kill_strerror(netstrerror); + return TOXAV_ERR_SEND_FRAME_RTP_FAILED; + } + } + + return TOXAV_ERR_SEND_FRAME_OK; +} + +bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, + const uint8_t *u, const uint8_t *v, Toxav_Err_Send_Frame *error) +{ + Toxav_Err_Send_Frame rc = TOXAV_ERR_SEND_FRAME_OK; + ToxAVCall *call; + + int vpx_encode_flags = 0; + + if (!m_friend_exists(av->m, friend_number)) { + rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND; + goto RETURN; + } + + if (pthread_mutex_trylock(av->mutex) != 0) { + rc = TOXAV_ERR_SEND_FRAME_SYNC; + goto RETURN; + } + + call = call_get(av, friend_number); + + if (call == nullptr || !call->active || call->msi_call->state != MSI_CALL_ACTIVE) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL; + goto RETURN; + } + + if (call->video_bit_rate == 0 || + (call->msi_call->self_capabilities & MSI_CAP_S_VIDEO) == 0 || + (call->msi_call->peer_capabilities & MSI_CAP_R_VIDEO) == 0) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED; + goto RETURN; + } + + pthread_mutex_lock(call->mutex_video); + pthread_mutex_unlock(av->mutex); + + if (y == nullptr || u == nullptr || v == nullptr) { + pthread_mutex_unlock(call->mutex_video); + rc = TOXAV_ERR_SEND_FRAME_NULL; + goto RETURN; + } + + if (vc_reconfigure_encoder(call->video, call->video_bit_rate * 1000, width, height, -1) != 0) { + pthread_mutex_unlock(call->mutex_video); + rc = TOXAV_ERR_SEND_FRAME_INVALID; + goto RETURN; + } + + if (call->video_rtp->ssrc < VIDEO_SEND_X_KEYFRAMES_FIRST) { + // Key frame flag for first frames + vpx_encode_flags = VPX_EFLAG_FORCE_KF; + LOGGER_DEBUG(av->m->log, "I_FRAME_FLAG:%d only-i-frame mode", call->video_rtp->ssrc); + + ++call->video_rtp->ssrc; + } else if (call->video_rtp->ssrc == VIDEO_SEND_X_KEYFRAMES_FIRST) { + // normal keyframe placement + vpx_encode_flags = 0; + LOGGER_DEBUG(av->m->log, "I_FRAME_FLAG:%d normal mode", call->video_rtp->ssrc); + + ++call->video_rtp->ssrc; + } + + // we start with I-frames (full frames) and then switch to normal mode later + + { /* Encode */ + vpx_image_t img; + img.w = 0; + img.h = 0; + img.d_w = 0; + img.d_h = 0; + vpx_img_alloc(&img, VPX_IMG_FMT_I420, width, height, 0); + + /* I420 "It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes." + * http://fourcc.org/yuv.php#IYUV + */ + memcpy(img.planes[VPX_PLANE_Y], y, width * height); + memcpy(img.planes[VPX_PLANE_U], u, (width / 2) * (height / 2)); + memcpy(img.planes[VPX_PLANE_V], v, (width / 2) * (height / 2)); + + const vpx_codec_err_t vrc = vpx_codec_encode(call->video->encoder, &img, + call->video->frame_counter, 1, vpx_encode_flags, MAX_ENCODE_TIME_US); + + vpx_img_free(&img); + + if (vrc != VPX_CODEC_OK) { + pthread_mutex_unlock(call->mutex_video); + LOGGER_ERROR(av->m->log, "Could not encode video frame: %s", vpx_codec_err_to_string(vrc)); + rc = TOXAV_ERR_SEND_FRAME_INVALID; + goto RETURN; + } + } + + ++call->video->frame_counter; + + rc = send_frames(av->m->log, call); + + pthread_mutex_unlock(call->mutex_video); + +RETURN: + + if (error != nullptr) { + *error = rc; + } + + return rc == TOXAV_ERR_SEND_FRAME_OK; +} + +void toxav_callback_audio_receive_frame(ToxAV *av, toxav_audio_receive_frame_cb *callback, void *user_data) +{ + pthread_mutex_lock(av->mutex); + av->acb = callback; + av->acb_user_data = user_data; + pthread_mutex_unlock(av->mutex); +} + +void toxav_callback_video_receive_frame(ToxAV *av, toxav_video_receive_frame_cb *callback, void *user_data) +{ + pthread_mutex_lock(av->mutex); + av->vcb = callback; + av->vcb_user_data = user_data; + pthread_mutex_unlock(av->mutex); +} + +/******************************************************************************* + * + * :: Internal + * + ******************************************************************************/ +static void callback_bwc(BWController *bwc, uint32_t friend_number, float loss, void *user_data) +{ + /* Callback which is called when the internal measure mechanism reported packet loss. + * We report suggested lowered bitrate to an app. If app is sending both audio and video, + * we will report lowered bitrate for video only because in that case video probably + * takes more than 90% bandwidth. Otherwise, we report lowered bitrate on audio. + * The application may choose to disable video totally if the stream is too bad. + */ + + ToxAVCall *call = (ToxAVCall *)user_data; + assert(call != nullptr); + + LOGGER_DEBUG(call->av->m->log, "Reported loss of %f%%", (double)loss * 100); + + /* if less than 10% data loss we do nothing! */ + if (loss < 0.1F) { + return; + } + + pthread_mutex_lock(call->av->mutex); + + if (call->video_bit_rate != 0) { + if (call->av->vbcb == nullptr) { + pthread_mutex_unlock(call->av->mutex); + LOGGER_WARNING(call->av->m->log, "No callback to report loss on"); + return; + } + + call->av->vbcb(call->av, friend_number, + call->video_bit_rate - (call->video_bit_rate * loss), + call->av->vbcb_user_data); + } else if (call->audio_bit_rate != 0) { + if (call->av->abcb == nullptr) { + pthread_mutex_unlock(call->av->mutex); + LOGGER_WARNING(call->av->m->log, "No callback to report loss on"); + return; + } + + call->av->abcb(call->av, friend_number, + call->audio_bit_rate - (call->audio_bit_rate * loss), + call->av->abcb_user_data); + } + + pthread_mutex_unlock(call->av->mutex); +} +static int callback_invite(void *toxav_inst, MSICall *call) +{ + ToxAV *toxav = (ToxAV *)toxav_inst; + pthread_mutex_lock(toxav->mutex); + + ToxAVCall *av_call = call_new(toxav, call->friend_number, nullptr); + + if (av_call == nullptr) { + LOGGER_WARNING(toxav->m->log, "Failed to initialize call..."); + pthread_mutex_unlock(toxav->mutex); + return -1; + } + + call->av_call = av_call; + av_call->msi_call = call; + + if (toxav->ccb != nullptr) { + toxav->ccb(toxav, call->friend_number, call->peer_capabilities & MSI_CAP_S_AUDIO, + call->peer_capabilities & MSI_CAP_S_VIDEO, toxav->ccb_user_data); + } else { + /* No handler to capture the call request, send failure */ + pthread_mutex_unlock(toxav->mutex); + return -1; + } + + pthread_mutex_unlock(toxav->mutex); + return 0; +} +static int callback_start(void *toxav_inst, MSICall *call) +{ + ToxAV *toxav = (ToxAV *)toxav_inst; + pthread_mutex_lock(toxav->mutex); + + ToxAVCall *av_call = call_get(toxav, call->friend_number); + + if (av_call == nullptr) { + /* Should this ever happen? */ + pthread_mutex_unlock(toxav->mutex); + return -1; + } + + if (!call_prepare_transmission(av_call)) { + callback_error(toxav_inst, call); + pthread_mutex_unlock(toxav->mutex); + return -1; + } + + if (!invoke_call_state_callback(toxav, call->friend_number, call->peer_capabilities)) { + callback_error(toxav_inst, call); + pthread_mutex_unlock(toxav->mutex); + return -1; + } + + pthread_mutex_unlock(toxav->mutex); + return 0; +} +static int callback_end(void *toxav_inst, MSICall *call) +{ + ToxAV *toxav = (ToxAV *)toxav_inst; + pthread_mutex_lock(toxav->mutex); + + invoke_call_state_callback(toxav, call->friend_number, TOXAV_FRIEND_CALL_STATE_FINISHED); + + if (call->av_call != nullptr) { + call_kill_transmission(call->av_call); + call_remove(call->av_call); + } + + pthread_mutex_unlock(toxav->mutex); + return 0; +} +static int callback_error(void *toxav_inst, MSICall *call) +{ + ToxAV *toxav = (ToxAV *)toxav_inst; + pthread_mutex_lock(toxav->mutex); + + invoke_call_state_callback(toxav, call->friend_number, TOXAV_FRIEND_CALL_STATE_ERROR); + + if (call->av_call != nullptr) { + call_kill_transmission(call->av_call); + call_remove(call->av_call); + } + + pthread_mutex_unlock(toxav->mutex); + return 0; +} +static int callback_capabilites(void *toxav_inst, MSICall *call) +{ + ToxAV *toxav = (ToxAV *)toxav_inst; + pthread_mutex_lock(toxav->mutex); + + if ((call->peer_capabilities & MSI_CAP_S_AUDIO) != 0) { + rtp_allow_receiving(call->av_call->audio_rtp); + } else { + rtp_stop_receiving(call->av_call->audio_rtp); + } + + if ((call->peer_capabilities & MSI_CAP_S_VIDEO) != 0) { + rtp_allow_receiving(call->av_call->video_rtp); + } else { + rtp_stop_receiving(call->av_call->video_rtp); + } + + invoke_call_state_callback(toxav, call->friend_number, call->peer_capabilities); + + pthread_mutex_unlock(toxav->mutex); + return 0; +} +static bool audio_bit_rate_invalid(uint32_t bit_rate) +{ + /* Opus RFC 6716 section-2.1.1 dictates the following: + * Opus supports all bit rates from 6 kbit/s to 510 kbit/s. + */ + return bit_rate < 6 || bit_rate > 510; +} +static bool video_bit_rate_invalid(uint32_t bit_rate) +{ + /* https://www.webmproject.org/docs/webm-sdk/structvpx__codec__enc__cfg.html shows the following: + * unsigned int rc_target_bitrate + * the range of uint varies from platform to platform + * though, uint32_t should be large enough to store bitrates, + * we may want to prevent from passing overflowed bitrates to libvpx + * more in detail, it's the case where bit_rate is larger than uint, but smaller than uint32_t + */ + return bit_rate > UINT32_MAX; +} +static bool invoke_call_state_callback(ToxAV *av, uint32_t friend_number, uint32_t state) +{ + if (av->scb != nullptr) { + av->scb(av, friend_number, state, av->scb_user_data); + } else { + return false; + } + + return true; +} + +static ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, Toxav_Err_Call *error) +{ + /* Assumes mutex locked */ + Toxav_Err_Call rc = TOXAV_ERR_CALL_OK; + ToxAVCall *call = nullptr; + + if (!m_friend_exists(av->m, friend_number)) { + rc = TOXAV_ERR_CALL_FRIEND_NOT_FOUND; + goto RETURN; + } + + if (m_get_friend_connectionstatus(av->m, friend_number) < 1) { + rc = TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED; + goto RETURN; + } + + if (call_get(av, friend_number) != nullptr) { + rc = TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL; + goto RETURN; + } + + call = (ToxAVCall *)calloc(1, sizeof(ToxAVCall)); + + if (call == nullptr) { + rc = TOXAV_ERR_CALL_MALLOC; + goto RETURN; + } + + call->av = av; + call->friend_number = friend_number; + + if (create_recursive_mutex(call->toxav_call_mutex) != 0) { + free(call); + call = nullptr; + rc = TOXAV_ERR_CALL_MALLOC; + goto RETURN; + } + + if (av->calls == nullptr) { /* Creating */ + av->calls = (ToxAVCall **)calloc(friend_number + 1, sizeof(ToxAVCall *)); + + if (av->calls == nullptr) { + pthread_mutex_destroy(call->toxav_call_mutex); + free(call); + call = nullptr; + rc = TOXAV_ERR_CALL_MALLOC; + goto RETURN; + } + + av->calls_tail = friend_number; + av->calls_head = friend_number; + } else if (av->calls_tail < friend_number) { /* Appending */ + ToxAVCall **tmp = (ToxAVCall **)realloc(av->calls, sizeof(ToxAVCall *) * (friend_number + 1)); + + if (tmp == nullptr) { + pthread_mutex_destroy(call->toxav_call_mutex); + free(call); + call = nullptr; + rc = TOXAV_ERR_CALL_MALLOC; + goto RETURN; + } + + av->calls = tmp; + + /* Set fields in between to null */ + for (uint32_t i = av->calls_tail + 1; i < friend_number; ++i) { + av->calls[i] = nullptr; + } + + call->prev = av->calls[av->calls_tail]; + av->calls[av->calls_tail]->next = call; + + av->calls_tail = friend_number; + } else if (av->calls_head > friend_number) { /* Inserting at front */ + call->next = av->calls[av->calls_head]; + av->calls[av->calls_head]->prev = call; + av->calls_head = friend_number; + } + + av->calls[friend_number] = call; + +RETURN: + + if (error != nullptr) { + *error = rc; + } + + return call; +} + +static ToxAVCall *call_get(ToxAV *av, uint32_t friend_number) +{ + /* Assumes mutex locked */ + if (av->calls == nullptr || av->calls_tail < friend_number) { + return nullptr; + } + + return av->calls[friend_number]; +} + +static ToxAVCall *call_remove(ToxAVCall *call) +{ + if (call == nullptr) { + return nullptr; + } + + const uint32_t friend_number = call->friend_number; + ToxAV *av = call->av; + + ToxAVCall *prev = call->prev; + ToxAVCall *next = call->next; + + /* Set av call in msi to NULL in order to know if call if ToxAVCall is + * removed from the msi call. + */ + if (call->msi_call != nullptr) { + call->msi_call->av_call = nullptr; + } + + pthread_mutex_destroy(call->toxav_call_mutex); + free(call); + + if (prev != nullptr) { + prev->next = next; + } else if (next != nullptr) { + av->calls_head = next->friend_number; + } else { + goto CLEAR; + } + + if (next != nullptr) { + next->prev = prev; + } else if (prev != nullptr) { + av->calls_tail = prev->friend_number; + } else { + goto CLEAR; + } + + av->calls[friend_number] = nullptr; + return next; + +CLEAR: + av->calls_head = 0; + av->calls_tail = 0; + free(av->calls); + av->calls = nullptr; + + return nullptr; +} + +static bool call_prepare_transmission(ToxAVCall *call) +{ + /* Assumes mutex locked */ + + if (call == nullptr) { + return false; + } + + ToxAV *av = call->av; + + if (av->acb == nullptr && av->vcb == nullptr) { + /* It makes no sense to have CSession without callbacks */ + return false; + } + + if (call->active) { + LOGGER_WARNING(av->m->log, "Call already active!"); + return true; + } + + if (create_recursive_mutex(call->mutex_audio) != 0) { + return false; + } + + if (create_recursive_mutex(call->mutex_video) != 0) { + goto FAILURE_2; + } + + /* Prepare bwc */ + call->bwc = bwc_new(av->m, av->tox, call->friend_number, callback_bwc, call, av->toxav_mono_time); + + if (call->bwc == nullptr) { + LOGGER_ERROR(av->m->log, "Failed to create new bwc"); + goto FAILURE; + } + + { /* Prepare audio */ + call->audio = ac_new(av->toxav_mono_time, av->m->log, av, call->friend_number, av->acb, av->acb_user_data); + + if (call->audio == nullptr) { + LOGGER_ERROR(av->m->log, "Failed to create audio codec session"); + goto FAILURE; + } + + call->audio_rtp = rtp_new(RTP_TYPE_AUDIO, av->m, av->tox, call->friend_number, call->bwc, + call->audio, ac_queue_message); + + if (call->audio_rtp == nullptr) { + LOGGER_ERROR(av->m->log, "Failed to create audio rtp session"); + goto FAILURE; + } + } + + { /* Prepare video */ + call->video = vc_new(av->toxav_mono_time, av->m->log, av, call->friend_number, av->vcb, av->vcb_user_data); + + if (call->video == nullptr) { + LOGGER_ERROR(av->m->log, "Failed to create video codec session"); + goto FAILURE; + } + + call->video_rtp = rtp_new(RTP_TYPE_VIDEO, av->m, av->tox, call->friend_number, call->bwc, + call->video, vc_queue_message); + + if (call->video_rtp == nullptr) { + LOGGER_ERROR(av->m->log, "Failed to create video rtp session"); + goto FAILURE; + } + } + + call->active = true; + return true; + +FAILURE: + bwc_kill(call->bwc); + rtp_kill(call->audio_rtp); + ac_kill(call->audio); + call->audio_rtp = nullptr; + call->audio = nullptr; + rtp_kill(call->video_rtp); + vc_kill(call->video); + call->video_rtp = nullptr; + call->video = nullptr; + pthread_mutex_destroy(call->mutex_video); +FAILURE_2: + pthread_mutex_destroy(call->mutex_audio); + return false; +} + +static void call_kill_transmission(ToxAVCall *call) +{ + if (call == nullptr || !call->active) { + return; + } + + call->active = false; + + pthread_mutex_lock(call->mutex_audio); + pthread_mutex_unlock(call->mutex_audio); + pthread_mutex_lock(call->mutex_video); + pthread_mutex_unlock(call->mutex_video); + pthread_mutex_lock(call->toxav_call_mutex); + pthread_mutex_unlock(call->toxav_call_mutex); + + bwc_kill(call->bwc); + + rtp_kill(call->audio_rtp); + ac_kill(call->audio); + call->audio_rtp = nullptr; + call->audio = nullptr; + + rtp_kill(call->video_rtp); + vc_kill(call->video); + call->video_rtp = nullptr; + call->video = nullptr; + + pthread_mutex_destroy(call->mutex_audio); + pthread_mutex_destroy(call->mutex_video); +} diff --git a/local_pod_repo/toxcore/toxcore/toxav/toxav_old.m b/local_pod_repo/toxcore/toxcore/toxav/toxav_old.m new file mode 100644 index 0000000..e1556f3 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/toxav_old.m @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ + +/** + * This file contains the group chats code for the backwards compatibility. + */ +#include "toxav.h" + +#include "../toxcore/tox_struct.h" +#include "groupav.h" + +int toxav_add_av_groupchat(Tox *tox, audio_data_cb *audio_callback, void *userdata) +{ + return add_av_groupchat(tox->m->log, tox, tox->m->conferences_object, audio_callback, userdata); +} + +/** @brief Join a AV group (you need to have been invited first). + * + * @return group number on success. + * @retval -1 on failure. + * + * Note that total size of pcm in bytes is equal to `samples * channels * sizeof(int16_t)`. + */ +int toxav_join_av_groupchat(Tox *tox, uint32_t friendnumber, const uint8_t *data, uint16_t length, + audio_data_cb *audio_callback, void *userdata) +{ + return join_av_groupchat(tox->m->log, tox, tox->m->conferences_object, friendnumber, data, length, audio_callback, userdata); +} + +/** @brief Send audio to the group chat. + * + * @retval 0 on success. + * @retval -1 on failure. + * + * Note that total size of pcm in bytes is equal to `samples * channels * sizeof(int16_t)`. + * + * Valid number of samples are `(sample rate) * (audio length) / 1000` + * (Valid values for audio length are: 2.5, 5, 10, 20, 40 or 60 ms) + * Valid number of channels are 1 or 2. + * Valid sample rates are 8000, 12000, 16000, 24000, or 48000. + * + * Recommended values are: samples = 960, channels = 1, sample_rate = 48000 + */ +int toxav_group_send_audio(Tox *tox, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, + uint32_t sample_rate) +{ + return group_send_audio(tox->m->conferences_object, groupnumber, pcm, samples, channels, sample_rate); +} + +/** @brief Enable A/V in a groupchat. + * + * A/V must be enabled on a groupchat for audio to be sent to it and for + * received audio to be handled. + * + * An A/V group created with `toxav_add_av_groupchat` or `toxav_join_av_groupchat` + * will start with A/V enabled. + * + * An A/V group loaded from a savefile will start with A/V disabled. + * + * @retval 0 on success. + * @retval -1 on failure. + * + * Note that total size of pcm in bytes is equal to `samples * channels * sizeof(int16_t)`. + */ +int toxav_groupchat_enable_av(Tox *tox, uint32_t groupnumber, audio_data_cb *audio_callback, void *userdata) +{ + return groupchat_enable_av(tox->m->log, tox, tox->m->conferences_object, groupnumber, audio_callback, userdata); +} + +/** @brief Disable A/V in a groupchat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int toxav_groupchat_disable_av(Tox *tox, uint32_t groupnumber) +{ + return groupchat_disable_av(tox->m->conferences_object, groupnumber); +} + +/** @brief Return whether A/V is enabled in the groupchat. */ +bool toxav_groupchat_av_enabled(Tox *tox, uint32_t groupnumber) +{ + return groupchat_av_enabled(tox->m->conferences_object, groupnumber); +} diff --git a/local_pod_repo/toxcore/toxcore/toxav/video.h b/local_pod_repo/toxcore/toxcore/toxav/video.h new file mode 100644 index 0000000..1fd4c04 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/video.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ +#ifndef C_TOXCORE_TOXAV_VIDEO_H +#define C_TOXCORE_TOXAV_VIDEO_H + +#include +#include +#include + +#include +#include + +#include + +#include "toxav.h" + +#include "../toxcore/logger.h" +#include "../toxcore/util.h" +#include "ring_buffer.h" +#include "rtp.h" + +typedef struct VCSession { + /* encoding */ + vpx_codec_ctx_t encoder[1]; + uint32_t frame_counter; + + /* decoding */ + vpx_codec_ctx_t decoder[1]; + struct RingBuffer *vbuf_raw; /* Un-decoded data */ + + uint64_t linfts; /* Last received frame time stamp */ + uint32_t lcfd; /* Last calculated frame duration for incoming video payload */ + + const Logger *log; + ToxAV *av; + uint32_t friend_number; + + /* Video frame receive callback */ + toxav_video_receive_frame_cb *vcb; + void *vcb_user_data; + + pthread_mutex_t queue_mutex[1]; +} VCSession; + +VCSession *vc_new(Mono_Time *mono_time, const Logger *log, ToxAV *av, uint32_t friend_number, + toxav_video_receive_frame_cb *cb, void *cb_data); +void vc_kill(VCSession *vc); +void vc_iterate(VCSession *vc); +int vc_queue_message(Mono_Time *mono_time, void *vcp, struct RTPMessage *msg); +int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height, int16_t kf_max_dist); + +#endif // C_TOXCORE_TOXAV_VIDEO_H diff --git a/local_pod_repo/toxcore/toxcore/toxav/video.m b/local_pod_repo/toxcore/toxcore/toxav/video.m new file mode 100644 index 0000000..cf5c386 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxav/video.m @@ -0,0 +1,446 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ +#include "video.h" + +#include +#include +#include + +#include "msi.h" +#include "ring_buffer.h" +#include "rtp.h" + +#include "../toxcore/ccompat.h" +#include "../toxcore/logger.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/network.h" + +/** + * Soft deadline the decoder should attempt to meet, in "us" (microseconds). + * Set to zero for unlimited. + * + * By convention, the value 1 is used to mean "return as fast as possible." + */ +// TODO(zoff99): don't hardcode this, let the application choose it +#define WANTED_MAX_DECODER_FPS 40 + +/** + * VPX_DL_REALTIME (1) + * deadline parameter analogous to VPx REALTIME mode. + * + * VPX_DL_GOOD_QUALITY (1000000) + * deadline parameter analogous to VPx GOOD QUALITY mode. + * + * VPX_DL_BEST_QUALITY (0) + * deadline parameter analogous to VPx BEST QUALITY mode. + */ +#define MAX_DECODE_TIME_US (1000000 / WANTED_MAX_DECODER_FPS) // to allow x fps + +/** + * Codec control function to set encoder internal speed settings. Changes in + * this value influences, among others, the encoder's selection of motion + * estimation methods. Values greater than 0 will increase encoder speed at the + * expense of quality. + * + * Note Valid range for VP8: `-16..16` + */ +#define VP8E_SET_CPUUSED_VALUE 16 + +/** + * Initialize encoder with this value. + * + * Target bandwidth to use for this stream, in kilobits per second. + */ +#define VIDEO_BITRATE_INITIAL_VALUE 5000 +#define VIDEO_DECODE_BUFFER_SIZE 5 // this buffer has normally max. 1 entry + +static vpx_codec_iface_t *video_codec_decoder_interface(void) +{ + return vpx_codec_vp8_dx(); +} +static vpx_codec_iface_t *video_codec_encoder_interface(void) +{ + return vpx_codec_vp8_cx(); +} + +#define VIDEO_CODEC_DECODER_MAX_WIDTH 800 // its a dummy value, because the struct needs a value there +#define VIDEO_CODEC_DECODER_MAX_HEIGHT 600 // its a dummy value, because the struct needs a value there + +#define VPX_MAX_DIST_START 40 + +#define VPX_MAX_ENCODER_THREADS 4 +#define VPX_MAX_DECODER_THREADS 4 +#define VIDEO_VP8_DECODER_POST_PROCESSING_ENABLED 0 + +static void vc_init_encoder_cfg(const Logger *log, vpx_codec_enc_cfg_t *cfg, int16_t kf_max_dist) +{ + const vpx_codec_err_t rc = vpx_codec_enc_config_default(video_codec_encoder_interface(), cfg, 0); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR(log, "vc_init_encoder_cfg:Failed to get config: %s", vpx_codec_err_to_string(rc)); + } + + /* Target bandwidth to use for this stream, in kilobits per second */ + cfg->rc_target_bitrate = VIDEO_BITRATE_INITIAL_VALUE; + cfg->g_w = VIDEO_CODEC_DECODER_MAX_WIDTH; + cfg->g_h = VIDEO_CODEC_DECODER_MAX_HEIGHT; + cfg->g_pass = VPX_RC_ONE_PASS; + cfg->g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS; + cfg->g_lag_in_frames = 0; + + /* Allow lagged encoding + * + * If set, this value allows the encoder to consume a number of input + * frames before producing output frames. This allows the encoder to + * base decisions for the current frame on future frames. This does + * increase the latency of the encoding pipeline, so it is not appropriate + * in all situations (ex: realtime encoding). + * + * Note that this is a maximum value -- the encoder may produce frames + * sooner than the given limit. Set this value to 0 to disable this + * feature. + */ + cfg->kf_min_dist = 0; + cfg->kf_mode = VPX_KF_AUTO; // Encoder determines optimal placement automatically + cfg->rc_end_usage = VPX_VBR; // what quality mode? + + /* + * VPX_VBR Variable Bit Rate (VBR) mode + * VPX_CBR Constant Bit Rate (CBR) mode + * VPX_CQ Constrained Quality (CQ) mode -> give codec a hint that we may be on low bandwidth connection + * VPX_Q Constant Quality (Q) mode + */ + if (kf_max_dist > 1) { + cfg->kf_max_dist = kf_max_dist; // a full frame every x frames minimum (can be more often, codec decides automatically) + LOGGER_DEBUG(log, "kf_max_dist=%d (1)", cfg->kf_max_dist); + } else { + cfg->kf_max_dist = VPX_MAX_DIST_START; + LOGGER_DEBUG(log, "kf_max_dist=%d (2)", cfg->kf_max_dist); + } + + cfg->g_threads = VPX_MAX_ENCODER_THREADS; // Maximum number of threads to use + /* TODO: set these to something reasonable */ + // cfg->g_timebase.num = 1; + // cfg->g_timebase.den = 60; // 60 fps + cfg->rc_resize_allowed = 1; // allow encoder to resize to smaller resolution + cfg->rc_resize_up_thresh = 40; + cfg->rc_resize_down_thresh = 5; + + /* TODO: make quality setting an API call, but start with normal quality */ +#if 0 + /* Highest-resolution encoder settings */ + cfg->rc_dropframe_thresh = 0; + cfg->rc_resize_allowed = 0; + cfg->rc_min_quantizer = 2; + cfg->rc_max_quantizer = 56; + cfg->rc_undershoot_pct = 100; + cfg->rc_overshoot_pct = 15; + cfg->rc_buf_initial_sz = 500; + cfg->rc_buf_optimal_sz = 600; + cfg->rc_buf_sz = 1000; +#endif +} + +VCSession *vc_new(Mono_Time *mono_time, const Logger *log, ToxAV *av, uint32_t friend_number, + toxav_video_receive_frame_cb *cb, void *cb_data) +{ + VCSession *vc = (VCSession *)calloc(1, sizeof(VCSession)); + vpx_codec_err_t rc; + + if (vc == nullptr) { + LOGGER_WARNING(log, "Allocation failed! Application might misbehave!"); + return nullptr; + } + + if (create_recursive_mutex(vc->queue_mutex) != 0) { + LOGGER_WARNING(log, "Failed to create recursive mutex!"); + free(vc); + return nullptr; + } + + const int cpu_used_value = VP8E_SET_CPUUSED_VALUE; + + vc->vbuf_raw = rb_new(VIDEO_DECODE_BUFFER_SIZE); + + if (vc->vbuf_raw == nullptr) { + goto BASE_CLEANUP; + } + + /* + * VPX_CODEC_USE_FRAME_THREADING + * Enable frame-based multi-threading + * + * VPX_CODEC_USE_ERROR_CONCEALMENT + * Conceal errors in decoded frames + */ + vpx_codec_dec_cfg_t dec_cfg; + dec_cfg.threads = VPX_MAX_DECODER_THREADS; // Maximum number of threads to use + dec_cfg.w = VIDEO_CODEC_DECODER_MAX_WIDTH; + dec_cfg.h = VIDEO_CODEC_DECODER_MAX_HEIGHT; + + LOGGER_DEBUG(log, "Using VP8 codec for decoder (0)"); + rc = vpx_codec_dec_init(vc->decoder, video_codec_decoder_interface(), &dec_cfg, + VPX_CODEC_USE_FRAME_THREADING | VPX_CODEC_USE_POSTPROC); + + if (rc == VPX_CODEC_INCAPABLE) { + LOGGER_WARNING(log, "Postproc not supported by this decoder (0)"); + rc = vpx_codec_dec_init(vc->decoder, video_codec_decoder_interface(), &dec_cfg, VPX_CODEC_USE_FRAME_THREADING); + } + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR(log, "Init video_decoder failed: %s", vpx_codec_err_to_string(rc)); + goto BASE_CLEANUP; + } + + if (VIDEO_VP8_DECODER_POST_PROCESSING_ENABLED == 1) { + vp8_postproc_cfg_t pp = {VP8_DEBLOCK, 1, 0}; + const vpx_codec_err_t cc_res = vpx_codec_control(vc->decoder, VP8_SET_POSTPROC, &pp); + + if (cc_res != VPX_CODEC_OK) { + LOGGER_WARNING(log, "Failed to turn on postproc"); + } else { + LOGGER_DEBUG(log, "turn on postproc: OK"); + } + } else { + vp8_postproc_cfg_t pp = {0, 0, 0}; + vpx_codec_err_t cc_res = vpx_codec_control(vc->decoder, VP8_SET_POSTPROC, &pp); + + if (cc_res != VPX_CODEC_OK) { + LOGGER_WARNING(log, "Failed to turn OFF postproc"); + } else { + LOGGER_DEBUG(log, "Disable postproc: OK"); + } + } + + /* Set encoder to some initial values + */ + vpx_codec_enc_cfg_t cfg; + vc_init_encoder_cfg(log, &cfg, 1); + + LOGGER_DEBUG(log, "Using VP8 codec for encoder (0.1)"); + rc = vpx_codec_enc_init(vc->encoder, video_codec_encoder_interface(), &cfg, VPX_CODEC_USE_FRAME_THREADING); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR(log, "Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); + goto BASE_CLEANUP_1; + } + + rc = vpx_codec_control(vc->encoder, VP8E_SET_CPUUSED, cpu_used_value); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR(log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); + vpx_codec_destroy(vc->encoder); + goto BASE_CLEANUP_1; + } + + /* + * VPX_CTRL_USE_TYPE(VP8E_SET_NOISE_SENSITIVITY, unsigned int) + * control function to set noise sensitivity + * 0: off, 1: OnYOnly, 2: OnYUV, 3: OnYUVAggressive, 4: Adaptive + */ +#if 0 + rc = vpx_codec_control(vc->encoder, VP8E_SET_NOISE_SENSITIVITY, 2); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR(log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); + vpx_codec_destroy(vc->encoder); + goto BASE_CLEANUP_1; + } + +#endif + vc->linfts = current_time_monotonic(mono_time); + vc->lcfd = 60; + vc->vcb = cb; + vc->vcb_user_data = cb_data; + vc->friend_number = friend_number; + vc->av = av; + vc->log = log; + return vc; +BASE_CLEANUP_1: + vpx_codec_destroy(vc->decoder); +BASE_CLEANUP: + pthread_mutex_destroy(vc->queue_mutex); + rb_kill(vc->vbuf_raw); + free(vc); + return nullptr; +} + +void vc_kill(VCSession *vc) +{ + if (vc == nullptr) { + return; + } + + vpx_codec_destroy(vc->encoder); + vpx_codec_destroy(vc->decoder); + void *p; + + while (rb_read(vc->vbuf_raw, &p)) { + free(p); + } + + rb_kill(vc->vbuf_raw); + pthread_mutex_destroy(vc->queue_mutex); + LOGGER_DEBUG(vc->log, "Terminated video handler: %p", (void *)vc); + free(vc); +} + +void vc_iterate(VCSession *vc) +{ + if (vc == nullptr) { + return; + } + + pthread_mutex_lock(vc->queue_mutex); + + struct RTPMessage *p; + + if (!rb_read(vc->vbuf_raw, (void **)&p)) { + LOGGER_TRACE(vc->log, "no Video frame data available"); + pthread_mutex_unlock(vc->queue_mutex); + return; + } + + const uint16_t log_rb_size = rb_size(vc->vbuf_raw); + pthread_mutex_unlock(vc->queue_mutex); + const struct RTPHeader *const header = &p->header; + + uint32_t full_data_len; + + if ((header->flags & RTP_LARGE_FRAME) != 0) { + full_data_len = header->data_length_full; + LOGGER_DEBUG(vc->log, "vc_iterate:001:full_data_len=%d", (int)full_data_len); + } else { + full_data_len = p->len; + LOGGER_DEBUG(vc->log, "vc_iterate:002"); + } + + LOGGER_DEBUG(vc->log, "vc_iterate: rb_read p->len=%d p->header.xe=%d", (int)full_data_len, p->header.xe); + LOGGER_DEBUG(vc->log, "vc_iterate: rb_read rb size=%d", (int)log_rb_size); + const vpx_codec_err_t rc = vpx_codec_decode(vc->decoder, p->data, full_data_len, nullptr, MAX_DECODE_TIME_US); + free(p); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR(vc->log, "Error decoding video: %d %s", (int)rc, vpx_codec_err_to_string(rc)); + return; + } + + /* Play decoded images */ + vpx_codec_iter_t iter = nullptr; + + for (vpx_image_t *dest = vpx_codec_get_frame(vc->decoder, &iter); + dest != nullptr; + dest = vpx_codec_get_frame(vc->decoder, &iter)) { + if (vc->vcb != nullptr) { + vc->vcb(vc->av, vc->friend_number, dest->d_w, dest->d_h, + dest->planes[0], dest->planes[1], dest->planes[2], + dest->stride[0], dest->stride[1], dest->stride[2], vc->vcb_user_data); + } + + vpx_img_free(dest); // is this needed? none of the VPx examples show that + } +} + +int vc_queue_message(Mono_Time *mono_time, void *vcp, struct RTPMessage *msg) +{ + /* This function is called with complete messages + * they have already been assembled. + * this function gets called from handle_rtp_packet() and handle_rtp_packet_v3() + */ + if (vcp == nullptr || msg == nullptr) { + free(msg); + + return -1; + } + + VCSession *vc = (VCSession *)vcp; + const struct RTPHeader *const header = &msg->header; + + if (msg->header.pt == (RTP_TYPE_VIDEO + 2) % 128) { + LOGGER_WARNING(vc->log, "Got dummy!"); + free(msg); + return 0; + } + + if (msg->header.pt != RTP_TYPE_VIDEO % 128) { + LOGGER_WARNING(vc->log, "Invalid payload type! pt=%d", (int)msg->header.pt); + free(msg); + return -1; + } + + pthread_mutex_lock(vc->queue_mutex); + + if ((header->flags & RTP_LARGE_FRAME) != 0 && header->pt == RTP_TYPE_VIDEO % 128) { + LOGGER_DEBUG(vc->log, "rb_write msg->len=%d b0=%d b1=%d", (int)msg->len, (int)msg->data[0], (int)msg->data[1]); + } + + free(rb_write(vc->vbuf_raw, msg)); + + /* Calculate time it took for peer to send us this frame */ + const uint32_t t_lcfd = current_time_monotonic(mono_time) - vc->linfts; + vc->lcfd = t_lcfd > 100 ? vc->lcfd : t_lcfd; + vc->linfts = current_time_monotonic(mono_time); + pthread_mutex_unlock(vc->queue_mutex); + return 0; +} + +int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height, int16_t kf_max_dist) +{ + if (vc == nullptr) { + return -1; + } + + vpx_codec_enc_cfg_t cfg2 = *vc->encoder->config.enc; + + if (cfg2.rc_target_bitrate == bit_rate && cfg2.g_w == width && cfg2.g_h == height && kf_max_dist == -1) { + return 0; /* Nothing changed */ + } + + if (cfg2.g_w == width && cfg2.g_h == height && kf_max_dist == -1) { + /* Only bit rate changed */ + LOGGER_INFO(vc->log, "bitrate change from: %u to: %u", (uint32_t)cfg2.rc_target_bitrate, (uint32_t)bit_rate); + cfg2.rc_target_bitrate = bit_rate; + const vpx_codec_err_t rc = vpx_codec_enc_config_set(vc->encoder, &cfg2); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR(vc->log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); + return -1; + } + } else { + /* Resolution is changed, must reinitialize encoder since libvpx v1.4 doesn't support + * reconfiguring encoder to use resolutions greater than initially set. + */ + LOGGER_DEBUG(vc->log, "Have to reinitialize vpx encoder on session %p", (void *)vc); + vpx_codec_ctx_t new_c; + vpx_codec_enc_cfg_t cfg; + vc_init_encoder_cfg(vc->log, &cfg, kf_max_dist); + cfg.rc_target_bitrate = bit_rate; + cfg.g_w = width; + cfg.g_h = height; + + LOGGER_DEBUG(vc->log, "Using VP8 codec for encoder"); + vpx_codec_err_t rc = vpx_codec_enc_init(&new_c, video_codec_encoder_interface(), &cfg, VPX_CODEC_USE_FRAME_THREADING); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR(vc->log, "Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); + return -1; + } + + const int cpu_used_value = VP8E_SET_CPUUSED_VALUE; + + rc = vpx_codec_control(&new_c, VP8E_SET_CPUUSED, cpu_used_value); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR(vc->log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); + vpx_codec_destroy(&new_c); + return -1; + } + + vpx_codec_destroy(vc->encoder); + memcpy(vc->encoder, &new_c, sizeof(new_c)); + } + + return 0; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/DHT.h b/local_pod_repo/toxcore/toxcore/toxcore/DHT.h new file mode 100644 index 0000000..34ecf9d --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/DHT.h @@ -0,0 +1,555 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** @file + * @brief An implementation of the DHT as seen in docs/updates/DHT.md + */ +#ifndef C_TOXCORE_TOXCORE_DHT_H +#define C_TOXCORE_TOXCORE_DHT_H + +#include + +#include "attributes.h" +#include "crypto_core.h" +#include "logger.h" +#include "mono_time.h" +#include "network.h" +#include "ping_array.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Maximum size of a signature (may be smaller) */ +#define SIGNATURE_SIZE CRYPTO_SIGNATURE_SIZE +/** Maximum number of clients stored per friend. */ +#define MAX_FRIEND_CLIENTS 8 + +#define LCLIENT_NODES MAX_FRIEND_CLIENTS +#define LCLIENT_LENGTH 128 + +/** A list of the clients mathematically closest to ours. */ +#define LCLIENT_LIST (LCLIENT_LENGTH * LCLIENT_NODES) + +#define MAX_CLOSE_TO_BOOTSTRAP_NODES 8 + +/** The max number of nodes to send with send nodes. */ +#define MAX_SENT_NODES 4 + +/** Ping timeout in seconds */ +#define PING_TIMEOUT 5 + +/** size of DHT ping arrays. */ +#define DHT_PING_ARRAY_SIZE 512 + +/** Ping interval in seconds for each node in our lists. */ +#define PING_INTERVAL 60 + +/** The number of seconds for a non responsive node to become bad. */ +#define PINGS_MISSED_NODE_GOES_BAD 1 +#define PING_ROUNDTRIP 2 +#define BAD_NODE_TIMEOUT (PING_INTERVAL + PINGS_MISSED_NODE_GOES_BAD * (PING_INTERVAL + PING_ROUNDTRIP)) + +/** + * The number of "fake" friends to add. + * + * (for optimization purposes and so our paths for the onion part are more random) + */ +#define DHT_FAKE_FRIEND_NUMBER 2 + +/** Maximum packet size for a DHT request packet. */ +#define MAX_CRYPTO_REQUEST_SIZE 1024 + +#define CRYPTO_PACKET_FRIEND_REQ 32 // Friend request crypto packet ID. +#define CRYPTO_PACKET_DHTPK 156 +#define CRYPTO_PACKET_NAT_PING 254 // NAT ping crypto packet ID. + +/* Max size of a packed node for IPV4 and IPV6 respectively */ +#define PACKED_NODE_SIZE_IP4 (1 + SIZE_IP4 + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE) +#define PACKED_NODE_SIZE_IP6 (1 + SIZE_IP6 + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE) + +/** + * This define can eventually be removed; it is necessary if a significant + * proportion of dht nodes do not implement the dht announcements protocol. + */ +#define CHECK_ANNOUNCE_NODE + +/** + * @brief Create a request to peer. + * + * Packs the data and sender public key and encrypts the packet. + * + * @param[in] send_public_key public key of the sender. + * @param[in] send_secret_key secret key of the sender. + * @param[out] packet an array of @ref MAX_CRYPTO_REQUEST_SIZE big. + * @param[in] recv_public_key public key of the receiver. + * @param[in] data represents the data we send with the request. + * @param[in] data_length the length of the data. + * @param[in] request_id the id of the request (32 = friend request, 254 = ping request). + * + * @attention Constraints: + * @code + * sizeof(packet) >= MAX_CRYPTO_REQUEST_SIZE + * @endcode + * + * @retval -1 on failure. + * @return the length of the created packet on success. + */ +non_null() +int create_request(const Random *rng, const uint8_t *send_public_key, const uint8_t *send_secret_key, + uint8_t *packet, const uint8_t *recv_public_key, + const uint8_t *data, uint32_t data_length, uint8_t request_id); + +/** + * @brief Decrypts and unpacks a DHT request packet. + * + * Puts the senders public key in the request in @p public_key, the data from + * the request in @p data. + * + * @param[in] self_public_key public key of the receiver (us). + * @param[in] self_secret_key secret key of the receiver (us). + * @param[out] public_key public key of the sender, copied from the input packet. + * @param[out] data decrypted request data, copied from the input packet, must + * have room for @ref MAX_CRYPTO_REQUEST_SIZE bytes. + * @param[in] packet is the request packet. + * @param[in] packet_length length of the packet. + * + * @attention Constraints: + * @code + * sizeof(data) >= MAX_CRYPTO_REQUEST_SIZE + * @endcode + * + * @retval -1 if not valid request. + * @return the length of the unpacked data. + */ +non_null() +int handle_request( + const uint8_t *self_public_key, const uint8_t *self_secret_key, uint8_t *public_key, uint8_t *data, + uint8_t *request_id, const uint8_t *packet, uint16_t packet_length); + +typedef struct IPPTs { + IP_Port ip_port; + uint64_t timestamp; +} IPPTs; + +typedef struct IPPTsPng { + IP_Port ip_port; + uint64_t timestamp; + uint64_t last_pinged; + + /* Returned by this node */ + IP_Port ret_ip_port; + uint64_t ret_timestamp; + /* true if this ip_port is ours */ + bool ret_ip_self; +} IPPTsPng; + +typedef struct Client_data { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + IPPTsPng assoc4; + IPPTsPng assoc6; + +#ifdef CHECK_ANNOUNCE_NODE + /* Responded to data search? */ + bool announce_node; +#endif +} Client_data; + +/*----------------------------------------------------------------------------------*/ + +typedef struct NAT { + /* true if currently hole punching */ + bool hole_punching; + uint32_t punching_index; + uint32_t tries; + uint32_t punching_index2; + + uint64_t punching_timestamp; + uint64_t recv_nat_ping_timestamp; + uint64_t nat_ping_id; + uint64_t nat_ping_timestamp; +} NAT; + +#define DHT_FRIEND_MAX_LOCKS 32 + +typedef struct Node_format { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + IP_Port ip_port; +} Node_format; + +extern const Node_format empty_node_format; + +typedef struct DHT_Friend DHT_Friend; + +non_null() const uint8_t *dht_friend_public_key(const DHT_Friend *dht_friend); +non_null() const Client_data *dht_friend_client(const DHT_Friend *dht_friend, size_t index); + +/** @return packet size of packed node with ip_family on success. + * @retval -1 on failure. + */ +int packed_node_size(Family ip_family); + +/** @brief Pack an IP_Port structure into data of max size length. + * + * Packed_length is the offset of data currently packed. + * + * @return size of packed IP_Port data on success. + * @retval -1 on failure. + */ +non_null() +int pack_ip_port(const Logger *logger, uint8_t *data, uint16_t length, const IP_Port *ip_port); + +/** @brief Encrypt plain and write resulting DHT packet into packet with max size length. + * + * @return size of packet on success. + * @retval -1 on failure. + */ +non_null() +int dht_create_packet(const Random *rng, + const uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE], + const uint8_t *shared_key, const uint8_t type, + const uint8_t *plain, size_t plain_length, + uint8_t *packet, size_t length); + +/** @brief Unpack IP_Port structure from data of max size length into ip_port. + * + * len_processed is the offset of data currently unpacked. + * + * @return size of unpacked ip_port on success. + * @retval -1 on failure. + */ +non_null() +int unpack_ip_port(IP_Port *ip_port, const uint8_t *data, uint16_t length, bool tcp_enabled); + +/** @brief Pack number of nodes into data of maxlength length. + * + * @return length of packed nodes on success. + * @retval -1 on failure. + */ +non_null() +int pack_nodes(const Logger *logger, uint8_t *data, uint16_t length, const Node_format *nodes, uint16_t number); + +/** @brief Unpack data of length into nodes of size max_num_nodes. + * Put the length of the data processed in processed_data_len. + * tcp_enabled sets if TCP nodes are expected (true) or not (false). + * + * @return number of unpacked nodes on success. + * @retval -1 on failure. + */ +non_null(1, 4) nullable(3) +int unpack_nodes(Node_format *nodes, uint16_t max_num_nodes, uint16_t *processed_data_len, const uint8_t *data, + uint16_t length, bool tcp_enabled); + + +/*----------------------------------------------------------------------------------*/ +/* struct to store some shared keys so we don't have to regenerate them for each request. */ +#define MAX_KEYS_PER_SLOT 4 +#define KEYS_TIMEOUT 600 + +typedef struct Shared_Key { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint32_t times_requested; + bool stored; + uint64_t time_last_requested; +} Shared_Key; + +typedef struct Shared_Keys { + Shared_Key keys[256 * MAX_KEYS_PER_SLOT]; +} Shared_Keys; + +/*----------------------------------------------------------------------------------*/ + +typedef int cryptopacket_handler_cb(void *object, const IP_Port *ip_port, const uint8_t *source_pubkey, + const uint8_t *data, uint16_t len, void *userdata); + +typedef struct DHT DHT; + +non_null() const uint8_t *dht_get_self_public_key(const DHT *dht); +non_null() const uint8_t *dht_get_self_secret_key(const DHT *dht); +non_null() void dht_set_self_public_key(DHT *dht, const uint8_t *key); +non_null() void dht_set_self_secret_key(DHT *dht, const uint8_t *key); + +non_null() Networking_Core *dht_get_net(const DHT *dht); +non_null() struct Ping *dht_get_ping(const DHT *dht); +non_null() const Client_data *dht_get_close_clientlist(const DHT *dht); +non_null() const Client_data *dht_get_close_client(const DHT *dht, uint32_t client_num); +non_null() uint16_t dht_get_num_friends(const DHT *dht); + +non_null() DHT_Friend *dht_get_friend(DHT *dht, uint32_t friend_num); +non_null() const uint8_t *dht_get_friend_public_key(const DHT *dht, uint32_t friend_num); + +/*----------------------------------------------------------------------------------*/ + +/** + * Shared key generations are costly, it is therefore smart to store commonly used + * ones so that they can be re-used later without being computed again. + * + * If a shared key is already in shared_keys, copy it to shared_key. + * Otherwise generate it into shared_key and copy it to shared_keys + */ +non_null() +void get_shared_key( + const Mono_Time *mono_time, Shared_Keys *shared_keys, uint8_t *shared_key, + const uint8_t *secret_key, const uint8_t *public_key); + +/** + * Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key + * for packets that we receive. + */ +non_null() +void dht_get_shared_key_recv(DHT *dht, uint8_t *shared_key, const uint8_t *public_key); + +/** + * Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key + * for packets that we send. + */ +non_null() +void dht_get_shared_key_sent(DHT *dht, uint8_t *shared_key, const uint8_t *public_key); + +/** + * Sends a getnodes request to `ip_port` with the public key `public_key` for nodes + * that are close to `client_id`. + * + * @retval true on success. + */ +non_null() +bool dht_getnodes(DHT *dht, const IP_Port *ip_port, const uint8_t *public_key, const uint8_t *client_id); + +typedef void dht_ip_cb(void *object, int32_t number, const IP_Port *ip_port); + +typedef void dht_get_nodes_response_cb(const DHT *dht, const Node_format *node, void *user_data); + +/** Sets the callback to be triggered on a getnodes response. */ +non_null(1) nullable(2) +void dht_callback_get_nodes_response(DHT *dht, dht_get_nodes_response_cb *function); + +/** @brief Add a new friend to the friends list. + * public_key must be CRYPTO_PUBLIC_KEY_SIZE bytes long. + * + * ip_callback is the callback of a function that will be called when the ip address + * is found along with arguments data and number. + * + * lock_count will be set to a non zero number that must be passed to `dht_delfriend()` + * to properly remove the callback. + * + * @retval 0 if success. + * @retval -1 if failure (friends list is full). + */ +non_null(1, 2) nullable(3, 4, 6) +int dht_addfriend(DHT *dht, const uint8_t *public_key, dht_ip_cb *ip_callback, + void *data, int32_t number, uint16_t *lock_count); + +/** @brief Delete a friend from the friends list. + * public_key must be CRYPTO_PUBLIC_KEY_SIZE bytes long. + * + * @retval 0 if success. + * @retval -1 if failure (public_key not in friends list). + */ +non_null() +int dht_delfriend(DHT *dht, const uint8_t *public_key, uint16_t lock_count); + +/** @brief Get ip of friend. + * + * @param public_key must be CRYPTO_PUBLIC_KEY_SIZE bytes long. + * + * @retval -1 if public_key does NOT refer to a friend + * @retval 0 if public_key refers to a friend and we failed to find the friend (yet) + * @retval 1 if public_key refers to a friend and we found him + */ +non_null() +int dht_getfriendip(const DHT *dht, const uint8_t *public_key, IP_Port *ip_port); + +/** @brief Compares pk1 and pk2 with pk. + * + * @retval 0 if both are same distance. + * @retval 1 if pk1 is closer. + * @retval 2 if pk2 is closer. + */ +non_null() +int id_closest(const uint8_t *pk, const uint8_t *pk1, const uint8_t *pk2); + +/** Return index of first unequal bit number between public keys pk1 and pk2. */ +non_null() +unsigned int bit_by_bit_cmp(const uint8_t *pk1, const uint8_t *pk2); + +/** + * Add node to the node list making sure only the nodes closest to cmp_pk are in the list. + * + * @return true iff the node was added to the list. + */ +non_null() +bool add_to_list( + Node_format *nodes_list, uint32_t length, const uint8_t *pk, const IP_Port *ip_port, const uint8_t *cmp_pk); + +/** Return 1 if node can be added to close list, 0 if it can't. */ +non_null() +bool node_addable_to_close_list(DHT *dht, const uint8_t *public_key, const IP_Port *ip_port); + +#ifdef CHECK_ANNOUNCE_NODE +/** Set node as announce node. */ +non_null() +void set_announce_node(DHT *dht, const uint8_t *public_key); +#endif + +/** + * Get the (maximum MAX_SENT_NODES) closest nodes to public_key we know + * and put them in nodes_list (must be MAX_SENT_NODES big). + * + * sa_family = family (IPv4 or IPv6) (0 if we don't care)? + * is_LAN = return some LAN ips (true or false) + * want_announce: return only nodes which implement the dht announcements protocol. + * + * @return the number of nodes returned. + */ +non_null() +int get_close_nodes(const DHT *dht, const uint8_t *public_key, Node_format *nodes_list, Family sa_family, + bool is_LAN, bool want_announce); + + +/** @brief Put up to max_num nodes in nodes from the random friends. + * + * Important: this function relies on the first two DHT friends *not* being real + * friends to avoid leaking information about real friends into the onion paths. + * + * @return the number of nodes. + */ +non_null() +uint16_t randfriends_nodes(const DHT *dht, Node_format *nodes, uint16_t max_num); + +/** @brief Put up to max_num nodes in nodes from the closelist. + * + * @return the number of nodes. + */ +non_null() +uint16_t closelist_nodes(const DHT *dht, Node_format *nodes, uint16_t max_num); + +/** Run this function at least a couple times per second (It's the main loop). */ +non_null() +void do_dht(DHT *dht); + +/* + * Use these two functions to bootstrap the client. + */ +/** + * @brief Sends a "get nodes" request to the given node with ip, port and public_key + * to setup connections + */ +non_null() +bool dht_bootstrap(DHT *dht, const IP_Port *ip_port, const uint8_t *public_key); + +/** @brief Resolves address into an IP address. + * + * If successful, sends a "get nodes" request to the given node with ip, port + * and public_key to setup connections + * + * @param address can be a hostname or an IP address (IPv4 or IPv6). + * @param ipv6enabled if false, the resolving sticks STRICTLY to IPv4 addresses. + * Otherwise, the resolving looks for IPv6 addresses first, then IPv4 addresses. + * + * @retval 1 if the address could be converted into an IP address + * @retval 0 otherwise + */ +non_null() +int dht_bootstrap_from_address(DHT *dht, const char *address, bool ipv6enabled, + uint16_t port, const uint8_t *public_key); + +/** @brief Start sending packets after DHT loaded_friends_list and loaded_clients_list are set. + * + * @retval 0 if successful + * @retval -1 otherwise + */ +non_null() +int dht_connect_after_load(DHT *dht); + +/* ROUTING FUNCTIONS */ + +/** @brief Send the given packet to node with public_key. + * + * @return number of bytes sent. + * @retval -1 if failure. + */ +non_null() +int route_packet(const DHT *dht, const uint8_t *public_key, const uint8_t *packet, uint16_t length); + +/** + * Send the following packet to everyone who tells us they are connected to friend_id. + * + * @return ip for friend. + * @return number of nodes the packet was sent to. (Only works if more than (MAX_FRIEND_CLIENTS / 4). + */ +non_null() +uint32_t route_to_friend(const DHT *dht, const uint8_t *friend_id, const Packet *packet); + +/** Function to handle crypto packets. */ +non_null(1) nullable(3, 4) +void cryptopacket_registerhandler(DHT *dht, uint8_t byte, cryptopacket_handler_cb *cb, void *object); + +/* SAVE/LOAD functions */ + +/** Get the size of the DHT (for saving). */ +non_null() +uint32_t dht_size(const DHT *dht); + +/** Save the DHT in data where data is an array of size `dht_size()`. */ +non_null() +void dht_save(const DHT *dht, uint8_t *data); + +/** @brief Load the DHT from data of size size. + * + * @retval -1 if failure. + * @retval 0 if success. + */ +non_null() +int dht_load(DHT *dht, const uint8_t *data, uint32_t length); + +/** Initialize DHT. */ +non_null() +DHT *new_dht(const Logger *log, const Random *rng, const Network *ns, Mono_Time *mono_time, Networking_Core *net, + bool hole_punching_enabled, bool lan_discovery_enabled); + +nullable(1) +void kill_dht(DHT *dht); + +/** + * @retval false if we are not connected to the DHT. + * @retval true if we are. + */ +non_null() +bool dht_isconnected(const DHT *dht); + +/** + * @retval false if we are not connected or only connected to lan peers with the DHT. + * @retval true if we are. + */ +non_null() +bool dht_non_lan_connected(const DHT *dht); + +/** @brief Attempt to add client with ip_port and public_key to the friends client list + * and close_clientlist. + * + * @return 1+ if the item is used in any list, 0 else + */ +non_null() +uint32_t addto_lists(DHT *dht, const IP_Port *ip_port, const uint8_t *public_key); + +/** @brief Copies our own ip_port structure to `dest`. + * + * WAN addresses take priority over LAN addresses. + * + * This function will zero the `dest` buffer before use. + * + * @retval 0 if our ip port can't be found (this usually means we're not connected to the DHT). + * @retval 1 if IP is a WAN address. + * @retval 2 if IP is a LAN address. + */ +non_null() +unsigned int ipport_self_copy(const DHT *dht, IP_Port *dest); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/DHT.m b/local_pod_repo/toxcore/toxcore/toxcore/DHT.m new file mode 100644 index 0000000..8aa1611 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/DHT.m @@ -0,0 +1,3119 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * An implementation of the DHT as seen in docs/updates/DHT.md + */ +#include "DHT.h" + +#include +#include +#include + +#include "LAN_discovery.h" +#include "ccompat.h" +#include "logger.h" +#include "mono_time.h" +#include "network.h" +#include "ping.h" +#include "state.h" +#include "util.h" + +/** The timeout after which a node is discarded completely. */ +#define KILL_NODE_TIMEOUT (BAD_NODE_TIMEOUT + PING_INTERVAL) + +/** Ping interval in seconds for each random sending of a get nodes request. */ +#define GET_NODE_INTERVAL 20 + +#define MAX_PUNCHING_PORTS 48 + +/** Interval in seconds between punching attempts*/ +#define PUNCH_INTERVAL 3 + +/** Time in seconds after which punching parameters will be reset */ +#define PUNCH_RESET_TIME 40 + +#define MAX_NORMAL_PUNCHING_TRIES 5 + +#define NAT_PING_REQUEST 0 +#define NAT_PING_RESPONSE 1 + +/** Number of get node requests to send to quickly find close nodes. */ +#define MAX_BOOTSTRAP_TIMES 5 + +typedef struct DHT_Friend_Callback { + dht_ip_cb *ip_callback; + void *data; + int32_t number; +} DHT_Friend_Callback; + +struct DHT_Friend { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + Client_data client_list[MAX_FRIEND_CLIENTS]; + + /* Time at which the last get_nodes request was sent. */ + uint64_t lastgetnode; + /* number of times get_node packets were sent. */ + uint32_t bootstrap_times; + + /* Symmetric NAT hole punching stuff. */ + NAT nat; + + uint16_t lock_count; + DHT_Friend_Callback callbacks[DHT_FRIEND_MAX_LOCKS]; + + Node_format to_bootstrap[MAX_SENT_NODES]; + unsigned int num_to_bootstrap; +}; + +static const DHT_Friend empty_dht_friend = {{0}}; +const Node_format empty_node_format = {{0}}; + +typedef struct Cryptopacket_Handler { + cryptopacket_handler_cb *function; + void *object; +} Cryptopacket_Handler; + +struct DHT { + const Logger *log; + const Network *ns; + Mono_Time *mono_time; + const Random *rng; + Networking_Core *net; + + bool hole_punching_enabled; + bool lan_discovery_enabled; + + Client_data close_clientlist[LCLIENT_LIST]; + uint64_t close_lastgetnodes; + uint32_t close_bootstrap_times; + + /* DHT keypair */ + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + DHT_Friend *friends_list; + uint16_t num_friends; + + Node_format *loaded_nodes_list; + uint32_t loaded_num_nodes; + unsigned int loaded_nodes_index; + + Shared_Keys shared_keys_recv; + Shared_Keys shared_keys_sent; + + struct Ping *ping; + Ping_Array *dht_ping_array; + uint64_t cur_time; + + Cryptopacket_Handler cryptopackethandlers[256]; + + Node_format to_bootstrap[MAX_CLOSE_TO_BOOTSTRAP_NODES]; + unsigned int num_to_bootstrap; + + dht_get_nodes_response_cb *get_nodes_response; +}; + +const uint8_t *dht_friend_public_key(const DHT_Friend *dht_friend) +{ + return dht_friend->public_key; +} + +const Client_data *dht_friend_client(const DHT_Friend *dht_friend, size_t index) +{ + return &dht_friend->client_list[index]; +} + +const uint8_t *dht_get_self_public_key(const DHT *dht) +{ + return dht->self_public_key; +} +const uint8_t *dht_get_self_secret_key(const DHT *dht) +{ + return dht->self_secret_key; +} + +void dht_set_self_public_key(DHT *dht, const uint8_t *key) +{ + memcpy(dht->self_public_key, key, CRYPTO_PUBLIC_KEY_SIZE); +} +void dht_set_self_secret_key(DHT *dht, const uint8_t *key) +{ + memcpy(dht->self_secret_key, key, CRYPTO_SECRET_KEY_SIZE); +} + +Networking_Core *dht_get_net(const DHT *dht) +{ + return dht->net; +} +struct Ping *dht_get_ping(const DHT *dht) +{ + return dht->ping; +} +const Client_data *dht_get_close_clientlist(const DHT *dht) +{ + return dht->close_clientlist; +} +const Client_data *dht_get_close_client(const DHT *dht, uint32_t client_num) +{ + assert(client_num < sizeof(dht->close_clientlist) / sizeof(dht->close_clientlist[0])); + return &dht->close_clientlist[client_num]; +} +uint16_t dht_get_num_friends(const DHT *dht) +{ + return dht->num_friends; +} + +DHT_Friend *dht_get_friend(DHT *dht, uint32_t friend_num) +{ + assert(friend_num < dht->num_friends); + return &dht->friends_list[friend_num]; +} +const uint8_t *dht_get_friend_public_key(const DHT *dht, uint32_t friend_num) +{ + assert(friend_num < dht->num_friends); + return dht->friends_list[friend_num].public_key; +} + +non_null() +static bool assoc_timeout(uint64_t cur_time, const IPPTsPng *assoc) +{ + return (assoc->timestamp + BAD_NODE_TIMEOUT) <= cur_time; +} + +/** @brief Converts an IPv4-in-IPv6 to IPv4 and returns the new IP_Port. + * + * If the ip_port is already IPv4 this function returns a copy of the original ip_port. + */ +non_null() +static IP_Port ip_port_normalize(const IP_Port *ip_port) +{ + IP_Port res = *ip_port; + + if (net_family_is_ipv6(res.ip.family) && ipv6_ipv4_in_v6(&res.ip.ip.v6)) { + res.ip.family = net_family_ipv4(); + res.ip.ip.v4.uint32 = res.ip.ip.v6.uint32[3]; + } + + return res; +} + +/** @brief Compares pk1 and pk2 with pk. + * + * @retval 0 if both are same distance. + * @retval 1 if pk1 is closer. + * @retval 2 if pk2 is closer. + */ +int id_closest(const uint8_t *pk, const uint8_t *pk1, const uint8_t *pk2) +{ + for (size_t i = 0; i < CRYPTO_PUBLIC_KEY_SIZE; ++i) { + const uint8_t distance1 = pk[i] ^ pk1[i]; + const uint8_t distance2 = pk[i] ^ pk2[i]; + + if (distance1 < distance2) { + return 1; + } + + if (distance1 > distance2) { + return 2; + } + } + + return 0; +} + +/** Return index of first unequal bit number between public keys pk1 and pk2. */ +unsigned int bit_by_bit_cmp(const uint8_t *pk1, const uint8_t *pk2) +{ + unsigned int i; + unsigned int j = 0; + + for (i = 0; i < CRYPTO_PUBLIC_KEY_SIZE; ++i) { + if (pk1[i] == pk2[i]) { + continue; + } + + for (j = 0; j < 8; ++j) { + const uint8_t mask = 1 << (7 - j); + + if ((pk1[i] & mask) != (pk2[i] & mask)) { + break; + } + } + + break; + } + + return i * 8 + j; +} + +/** + * Shared key generations are costly, it is therefore smart to store commonly used + * ones so that they can be re-used later without being computed again. + * + * If a shared key is already in shared_keys, copy it to shared_key. + * Otherwise generate it into shared_key and copy it to shared_keys + */ +void get_shared_key(const Mono_Time *mono_time, Shared_Keys *shared_keys, uint8_t *shared_key, + const uint8_t *secret_key, const uint8_t *public_key) +{ + uint32_t num = -1; + uint32_t curr = 0; + + for (uint32_t i = 0; i < MAX_KEYS_PER_SLOT; ++i) { + const int index = public_key[30] * MAX_KEYS_PER_SLOT + i; + Shared_Key *const key = &shared_keys->keys[index]; + + if (key->stored) { + if (pk_equal(public_key, key->public_key)) { + memcpy(shared_key, key->shared_key, CRYPTO_SHARED_KEY_SIZE); + ++key->times_requested; + key->time_last_requested = mono_time_get(mono_time); + return; + } + + if (num != 0) { + if (mono_time_is_timeout(mono_time, key->time_last_requested, KEYS_TIMEOUT)) { + num = 0; + curr = index; + } else if (num > key->times_requested) { + num = key->times_requested; + curr = index; + } + } + } else if (num != 0) { + num = 0; + curr = index; + } + } + + encrypt_precompute(public_key, secret_key, shared_key); + + if (num != UINT32_MAX) { + Shared_Key *const key = &shared_keys->keys[curr]; + key->stored = true; + key->times_requested = 1; + memcpy(key->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(key->shared_key, shared_key, CRYPTO_SHARED_KEY_SIZE); + key->time_last_requested = mono_time_get(mono_time); + } +} + +/** + * Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key + * for packets that we receive. + */ +void dht_get_shared_key_recv(DHT *dht, uint8_t *shared_key, const uint8_t *public_key) +{ + get_shared_key(dht->mono_time, &dht->shared_keys_recv, shared_key, dht->self_secret_key, public_key); +} + +/** + * Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key + * for packets that we send. + */ +void dht_get_shared_key_sent(DHT *dht, uint8_t *shared_key, const uint8_t *public_key) +{ + get_shared_key(dht->mono_time, &dht->shared_keys_sent, shared_key, dht->self_secret_key, public_key); +} + +#define CRYPTO_SIZE (1 + CRYPTO_PUBLIC_KEY_SIZE * 2 + CRYPTO_NONCE_SIZE) + +/** + * @brief Create a request to peer. + * + * Packs the data and sender public key and encrypts the packet. + * + * @param[in] send_public_key public key of the sender. + * @param[in] send_secret_key secret key of the sender. + * @param[out] packet an array of @ref MAX_CRYPTO_REQUEST_SIZE big. + * @param[in] recv_public_key public key of the receiver. + * @param[in] data represents the data we send with the request. + * @param[in] data_length the length of the data. + * @param[in] request_id the id of the request (32 = friend request, 254 = ping request). + * + * @attention Constraints: + * @code + * sizeof(packet) >= MAX_CRYPTO_REQUEST_SIZE + * @endcode + * + * @retval -1 on failure. + * @return the length of the created packet on success. + */ +int create_request(const Random *rng, const uint8_t *send_public_key, const uint8_t *send_secret_key, + uint8_t *packet, const uint8_t *recv_public_key, + const uint8_t *data, uint32_t data_length, uint8_t request_id) +{ + if (send_public_key == nullptr || packet == nullptr || recv_public_key == nullptr || data == nullptr) { + return -1; + } + + if (MAX_CRYPTO_REQUEST_SIZE < data_length + CRYPTO_SIZE + 1 + CRYPTO_MAC_SIZE) { + return -1; + } + + uint8_t *const nonce = packet + 1 + CRYPTO_PUBLIC_KEY_SIZE * 2; + random_nonce(rng, nonce); + uint8_t temp[MAX_CRYPTO_REQUEST_SIZE] = {0}; + temp[0] = request_id; + memcpy(temp + 1, data, data_length); + const int len = encrypt_data(recv_public_key, send_secret_key, nonce, temp, data_length + 1, + packet + CRYPTO_SIZE); + + if (len == -1) { + crypto_memzero(temp, MAX_CRYPTO_REQUEST_SIZE); + return -1; + } + + packet[0] = NET_PACKET_CRYPTO; + memcpy(packet + 1, recv_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, send_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + crypto_memzero(temp, MAX_CRYPTO_REQUEST_SIZE); + return len + CRYPTO_SIZE; +} + +/** + * @brief Decrypts and unpacks a DHT request packet. + * + * Puts the senders public key in the request in @p public_key, the data from + * the request in @p data. + * + * @param[in] self_public_key public key of the receiver (us). + * @param[in] self_secret_key secret key of the receiver (us). + * @param[out] public_key public key of the sender, copied from the input packet. + * @param[out] data decrypted request data, copied from the input packet, must + * have room for @ref MAX_CRYPTO_REQUEST_SIZE bytes. + * @param[in] packet is the request packet. + * @param[in] packet_length length of the packet. + * + * @attention Constraints: + * @code + * sizeof(data) >= MAX_CRYPTO_REQUEST_SIZE + * @endcode + * + * @retval -1 if not valid request. + * @return the length of the unpacked data. + */ +int handle_request(const uint8_t *self_public_key, const uint8_t *self_secret_key, uint8_t *public_key, uint8_t *data, + uint8_t *request_id, const uint8_t *packet, uint16_t packet_length) +{ + if (self_public_key == nullptr || public_key == nullptr || data == nullptr || request_id == nullptr + || packet == nullptr) { + return -1; + } + + if (packet_length <= CRYPTO_SIZE + CRYPTO_MAC_SIZE || packet_length > MAX_CRYPTO_REQUEST_SIZE) { + return -1; + } + + if (!pk_equal(packet + 1, self_public_key)) { + return -1; + } + + memcpy(public_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); + const uint8_t *const nonce = packet + 1 + CRYPTO_PUBLIC_KEY_SIZE * 2; + uint8_t temp[MAX_CRYPTO_REQUEST_SIZE]; + int32_t len1 = decrypt_data(public_key, self_secret_key, nonce, + packet + CRYPTO_SIZE, packet_length - CRYPTO_SIZE, temp); + + if (len1 == -1 || len1 == 0) { + crypto_memzero(temp, MAX_CRYPTO_REQUEST_SIZE); + return -1; + } + + assert(len1 == packet_length - CRYPTO_SIZE - CRYPTO_MAC_SIZE); + // Because coverity can't figure out this equation: + assert(len1 <= MAX_CRYPTO_REQUEST_SIZE - CRYPTO_SIZE - CRYPTO_MAC_SIZE); + + request_id[0] = temp[0]; + --len1; + memcpy(data, temp + 1, len1); + crypto_memzero(temp, MAX_CRYPTO_REQUEST_SIZE); + return len1; +} + +/** @return packet size of packed node with ip_family on success. + * @retval -1 on failure. + */ +int packed_node_size(Family ip_family) +{ + if (net_family_is_ipv4(ip_family) || net_family_is_tcp_ipv4(ip_family)) { + return PACKED_NODE_SIZE_IP4; + } + + if (net_family_is_ipv6(ip_family) || net_family_is_tcp_ipv6(ip_family)) { + return PACKED_NODE_SIZE_IP6; + } + + return -1; +} + + +/** @brief Pack an IP_Port structure into data of max size length. + * + * Packed_length is the offset of data currently packed. + * + * @return size of packed IP_Port data on success. + * @retval -1 on failure. + */ +int pack_ip_port(const Logger *logger, uint8_t *data, uint16_t length, const IP_Port *ip_port) +{ + if (data == nullptr) { + return -1; + } + + bool is_ipv4; + uint8_t family; + + if (net_family_is_ipv4(ip_port->ip.family)) { + // TODO(irungentoo): use functions to convert endianness + is_ipv4 = true; + family = TOX_AF_INET; + } else if (net_family_is_tcp_ipv4(ip_port->ip.family)) { + is_ipv4 = true; + family = TOX_TCP_INET; + } else if (net_family_is_ipv6(ip_port->ip.family)) { + is_ipv4 = false; + family = TOX_AF_INET6; + } else if (net_family_is_tcp_ipv6(ip_port->ip.family)) { + is_ipv4 = false; + family = TOX_TCP_INET6; + } else { + Ip_Ntoa ip_str; + // TODO(iphydf): Find out why we're trying to pack invalid IPs, stop + // doing that, and turn this into an error. + LOGGER_TRACE(logger, "cannot pack invalid IP: %s", net_ip_ntoa(&ip_port->ip, &ip_str)); + return -1; + } + + if (is_ipv4) { + const uint32_t size = 1 + SIZE_IP4 + sizeof(uint16_t); + + if (size > length) { + return -1; + } + + data[0] = family; + memcpy(data + 1, &ip_port->ip.ip.v4, SIZE_IP4); + memcpy(data + 1 + SIZE_IP4, &ip_port->port, sizeof(uint16_t)); + return size; + } else { + const uint32_t size = 1 + SIZE_IP6 + sizeof(uint16_t); + + if (size > length) { + return -1; + } + + data[0] = family; + memcpy(data + 1, &ip_port->ip.ip.v6, SIZE_IP6); + memcpy(data + 1 + SIZE_IP6, &ip_port->port, sizeof(uint16_t)); + return size; + } +} + +/** @brief Encrypt plain and write resulting DHT packet into packet with max size length. + * + * @return size of packet on success. + * @retval -1 on failure. + */ +int dht_create_packet(const Random *rng, const uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE], + const uint8_t *shared_key, const uint8_t type, + const uint8_t *plain, size_t plain_length, + uint8_t *packet, size_t length) +{ + uint8_t *encrypted = (uint8_t *)malloc(plain_length + CRYPTO_MAC_SIZE); + uint8_t nonce[CRYPTO_NONCE_SIZE]; + + if (encrypted == nullptr) { + return -1; + } + + random_nonce(rng, nonce); + + const int encrypted_length = encrypt_data_symmetric(shared_key, nonce, plain, plain_length, encrypted); + + if (encrypted_length == -1) { + free(encrypted); + return -1; + } + + if (length < 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + encrypted_length) { + free(encrypted); + return -1; + } + + packet[0] = type; + memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, encrypted, encrypted_length); + + free(encrypted); + return 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + encrypted_length; +} + +/** @brief Unpack IP_Port structure from data of max size length into ip_port. + * + * len_processed is the offset of data currently unpacked. + * + * @return size of unpacked ip_port on success. + * @retval -1 on failure. + */ +int unpack_ip_port(IP_Port *ip_port, const uint8_t *data, uint16_t length, bool tcp_enabled) +{ + if (data == nullptr) { + return -1; + } + + bool is_ipv4; + Family host_family; + + if (data[0] == TOX_AF_INET) { + is_ipv4 = true; + host_family = net_family_ipv4(); + } else if (data[0] == TOX_TCP_INET) { + if (!tcp_enabled) { + return -1; + } + + is_ipv4 = true; + host_family = net_family_tcp_ipv4(); + } else if (data[0] == TOX_AF_INET6) { + is_ipv4 = false; + host_family = net_family_ipv6(); + } else if (data[0] == TOX_TCP_INET6) { + if (!tcp_enabled) { + return -1; + } + + is_ipv4 = false; + host_family = net_family_tcp_ipv6(); + } else { + return -1; + } + + *ip_port = empty_ip_port; + + if (is_ipv4) { + const uint32_t size = 1 + SIZE_IP4 + sizeof(uint16_t); + + if (size > length) { + return -1; + } + + ip_port->ip.family = host_family; + memcpy(&ip_port->ip.ip.v4, data + 1, SIZE_IP4); + memcpy(&ip_port->port, data + 1 + SIZE_IP4, sizeof(uint16_t)); + return size; + } else { + const uint32_t size = 1 + SIZE_IP6 + sizeof(uint16_t); + + if (size > length) { + return -1; + } + + ip_port->ip.family = host_family; + memcpy(&ip_port->ip.ip.v6, data + 1, SIZE_IP6); + memcpy(&ip_port->port, data + 1 + SIZE_IP6, sizeof(uint16_t)); + return size; + } +} + +/** @brief Pack number of nodes into data of maxlength length. + * + * @return length of packed nodes on success. + * @retval -1 on failure. + */ +int pack_nodes(const Logger *logger, uint8_t *data, uint16_t length, const Node_format *nodes, uint16_t number) +{ + uint32_t packed_length = 0; + + for (uint32_t i = 0; i < number && packed_length < length; ++i) { + const int ipp_size = pack_ip_port(logger, data + packed_length, length - packed_length, &nodes[i].ip_port); + + if (ipp_size == -1) { + return -1; + } + + packed_length += ipp_size; + + if (packed_length + CRYPTO_PUBLIC_KEY_SIZE > length) { + return -1; + } + + memcpy(data + packed_length, nodes[i].public_key, CRYPTO_PUBLIC_KEY_SIZE); + packed_length += CRYPTO_PUBLIC_KEY_SIZE; + +#ifndef NDEBUG + const uint32_t increment = ipp_size + CRYPTO_PUBLIC_KEY_SIZE; +#endif + assert(increment == PACKED_NODE_SIZE_IP4 || increment == PACKED_NODE_SIZE_IP6); + } + + return packed_length; +} + +/** @brief Unpack data of length into nodes of size max_num_nodes. + * Put the length of the data processed in processed_data_len. + * tcp_enabled sets if TCP nodes are expected (true) or not (false). + * + * @return number of unpacked nodes on success. + * @retval -1 on failure. + */ +int unpack_nodes(Node_format *nodes, uint16_t max_num_nodes, uint16_t *processed_data_len, const uint8_t *data, + uint16_t length, bool tcp_enabled) +{ + uint32_t num = 0; + uint32_t len_processed = 0; + + while (num < max_num_nodes && len_processed < length) { + const int ipp_size = unpack_ip_port(&nodes[num].ip_port, data + len_processed, length - len_processed, tcp_enabled); + + if (ipp_size == -1) { + return -1; + } + + len_processed += ipp_size; + + if (len_processed + CRYPTO_PUBLIC_KEY_SIZE > length) { + return -1; + } + + memcpy(nodes[num].public_key, data + len_processed, CRYPTO_PUBLIC_KEY_SIZE); + len_processed += CRYPTO_PUBLIC_KEY_SIZE; + ++num; + +#ifndef NDEBUG + const uint32_t increment = ipp_size + CRYPTO_PUBLIC_KEY_SIZE; +#endif + assert(increment == PACKED_NODE_SIZE_IP4 || increment == PACKED_NODE_SIZE_IP6); + } + + if (processed_data_len != nullptr) { + *processed_data_len = len_processed; + } + + return num; +} + +/** @brief Find index in an array with public_key equal to pk. + * + * @return index or UINT32_MAX if not found. + */ +non_null(3) nullable(1) +static uint32_t index_of_client_pk(const Client_data *array, uint32_t size, const uint8_t *pk) +{ + assert(size == 0 || array != nullptr); + + for (uint32_t i = 0; i < size; ++i) { + if (pk_equal(array[i].public_key, pk)) { + return i; + } + } + + return UINT32_MAX; +} + +non_null(3) nullable(1) +static uint32_t index_of_friend_pk(const DHT_Friend *array, uint32_t size, const uint8_t *pk) +{ + assert(size == 0 || array != nullptr); + + for (uint32_t i = 0; i < size; ++i) { + if (pk_equal(array[i].public_key, pk)) { + return i; + } + } + + return UINT32_MAX; +} + +non_null(3) nullable(1) +static uint32_t index_of_node_pk(const Node_format *array, uint32_t size, const uint8_t *pk) +{ + assert(size == 0 || array != nullptr); + + for (uint32_t i = 0; i < size; ++i) { + if (pk_equal(array[i].public_key, pk)) { + return i; + } + } + + return UINT32_MAX; +} + +/** @brief Find index of Client_data with ip_port equal to param ip_port. + * + * @return index or UINT32_MAX if not found. + */ +non_null(3) nullable(1) +static uint32_t index_of_client_ip_port(const Client_data *array, uint32_t size, const IP_Port *ip_port) +{ + assert(size == 0 || array != nullptr); + + for (uint32_t i = 0; i < size; ++i) { + if ((net_family_is_ipv4(ip_port->ip.family) && ipport_equal(&array[i].assoc4.ip_port, ip_port)) || + (net_family_is_ipv6(ip_port->ip.family) && ipport_equal(&array[i].assoc6.ip_port, ip_port))) { + return i; + } + } + + return UINT32_MAX; +} + +/** Update ip_port of client if it's needed. */ +non_null() +static void update_client(const Logger *log, const Mono_Time *mono_time, int index, Client_data *client, + const IP_Port *ip_port) +{ + IPPTsPng *assoc; + int ip_version; + + if (net_family_is_ipv4(ip_port->ip.family)) { + assoc = &client->assoc4; + ip_version = 4; + } else if (net_family_is_ipv6(ip_port->ip.family)) { + assoc = &client->assoc6; + ip_version = 6; + } else { + return; + } + + if (!ipport_equal(&assoc->ip_port, ip_port)) { + Ip_Ntoa ip_str_from; + Ip_Ntoa ip_str_to; + LOGGER_TRACE(log, "coipil[%u]: switching ipv%d from %s:%u to %s:%u", + index, ip_version, + net_ip_ntoa(&assoc->ip_port.ip, &ip_str_from), + net_ntohs(assoc->ip_port.port), + net_ip_ntoa(&ip_port->ip, &ip_str_to), + net_ntohs(ip_port->port)); + } + + if (!ip_is_lan(&assoc->ip_port.ip) && ip_is_lan(&ip_port->ip)) { + return; + } + + assoc->ip_port = *ip_port; + assoc->timestamp = mono_time_get(mono_time); +} + +/** @brief Check if client with public_key is already in list of length length. + * + * If it is then set its corresponding timestamp to current time. + * If the id is already in the list with a different ip_port, update it. + * TODO(irungentoo): Maybe optimize this. + */ +non_null() +static bool client_or_ip_port_in_list(const Logger *log, const Mono_Time *mono_time, Client_data *list, uint16_t length, + const uint8_t *public_key, const IP_Port *ip_port) +{ + const uint64_t temp_time = mono_time_get(mono_time); + uint32_t index = index_of_client_pk(list, length, public_key); + + /* if public_key is in list, find it and maybe overwrite ip_port */ + if (index != UINT32_MAX) { + update_client(log, mono_time, index, &list[index], ip_port); + return true; + } + + /* public_key not in list yet: see if we can find an identical ip_port, in + * that case we kill the old public_key by overwriting it with the new one + * TODO(irungentoo): maybe we SHOULDN'T do that if that public_key is in a friend_list + * and the one who is the actual friend's public_key/address set? + * MAYBE: check the other address, if valid, don't nuke? */ + index = index_of_client_ip_port(list, length, ip_port); + + if (index == UINT32_MAX) { + return false; + } + + IPPTsPng *assoc; + int ip_version; + + if (net_family_is_ipv4(ip_port->ip.family)) { + assoc = &list[index].assoc4; + ip_version = 4; + } else { + assoc = &list[index].assoc6; + ip_version = 6; + } + + /* Initialize client timestamp. */ + assoc->timestamp = temp_time; + memcpy(list[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + + LOGGER_DEBUG(log, "coipil[%u]: switching public_key (ipv%d)", index, ip_version); + + /* kill the other address, if it was set */ + const IPPTsPng empty_ipptspng = {{{{0}}}}; + *assoc = empty_ipptspng; + return true; +} + +bool add_to_list(Node_format *nodes_list, uint32_t length, const uint8_t *pk, const IP_Port *ip_port, + const uint8_t *cmp_pk) +{ + for (uint32_t i = 0; i < length; ++i) { + if (id_closest(cmp_pk, nodes_list[i].public_key, pk) == 2) { + uint8_t pk_bak[CRYPTO_PUBLIC_KEY_SIZE]; + memcpy(pk_bak, nodes_list[i].public_key, CRYPTO_PUBLIC_KEY_SIZE); + const IP_Port ip_port_bak = nodes_list[i].ip_port; + memcpy(nodes_list[i].public_key, pk, CRYPTO_PUBLIC_KEY_SIZE); + nodes_list[i].ip_port = *ip_port; + + if (i != length - 1) { + add_to_list(nodes_list, length, pk_bak, &ip_port_bak, cmp_pk); + } + + return true; + } + } + + return false; +} + +/** + * helper for `get_close_nodes()`. argument list is a monster :D + */ +non_null() +static void get_close_nodes_inner(uint64_t cur_time, const uint8_t *public_key, Node_format *nodes_list, + Family sa_family, const Client_data *client_list, uint32_t client_list_length, + uint32_t *num_nodes_ptr, bool is_LAN, + bool want_announce) +{ + if (!net_family_is_ipv4(sa_family) && !net_family_is_ipv6(sa_family) && !net_family_is_unspec(sa_family)) { + return; + } + + uint32_t num_nodes = *num_nodes_ptr; + + for (uint32_t i = 0; i < client_list_length; ++i) { + const Client_data *const client = &client_list[i]; + + /* node already in list? */ + if (index_of_node_pk(nodes_list, MAX_SENT_NODES, client->public_key) != UINT32_MAX) { + continue; + } + + const IPPTsPng *ipptp; + + if (net_family_is_ipv4(sa_family)) { + ipptp = &client->assoc4; + } else if (net_family_is_ipv6(sa_family)) { + ipptp = &client->assoc6; + } else if (client->assoc4.timestamp >= client->assoc6.timestamp) { + ipptp = &client->assoc4; + } else { + ipptp = &client->assoc6; + } + + /* node not in a good condition? */ + if (assoc_timeout(cur_time, ipptp)) { + continue; + } + + /* don't send LAN ips to non LAN peers */ + if (ip_is_lan(&ipptp->ip_port.ip) && !is_LAN) { + continue; + } + +#ifdef CHECK_ANNOUNCE_NODE + + if (want_announce && !client->announce_node) { + continue; + } + +#endif + + if (num_nodes < MAX_SENT_NODES) { + memcpy(nodes_list[num_nodes].public_key, client->public_key, CRYPTO_PUBLIC_KEY_SIZE); + nodes_list[num_nodes].ip_port = ipptp->ip_port; + ++num_nodes; + } else { + // TODO(zugz): this could be made significantly more efficient by + // using a version of add_to_list which works with a sorted list. + add_to_list(nodes_list, MAX_SENT_NODES, client->public_key, &ipptp->ip_port, public_key); + } + } + + *num_nodes_ptr = num_nodes; +} + +/** + * Find MAX_SENT_NODES nodes closest to the public_key for the send nodes request: + * put them in the nodes_list and return how many were found. + * + * want_announce: return only nodes which implement the dht announcements protocol. + */ +non_null() +static int get_somewhat_close_nodes(const DHT *dht, const uint8_t *public_key, Node_format *nodes_list, + Family sa_family, bool is_LAN, bool want_announce) +{ + uint32_t num_nodes = 0; + get_close_nodes_inner(dht->cur_time, public_key, nodes_list, sa_family, + dht->close_clientlist, LCLIENT_LIST, &num_nodes, is_LAN, want_announce); + + for (uint32_t i = 0; i < dht->num_friends; ++i) { + get_close_nodes_inner(dht->cur_time, public_key, nodes_list, sa_family, + dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, + &num_nodes, is_LAN, want_announce); + } + + return num_nodes; +} + +int get_close_nodes(const DHT *dht, const uint8_t *public_key, Node_format *nodes_list, Family sa_family, + bool is_LAN, bool want_announce) +{ + memset(nodes_list, 0, MAX_SENT_NODES * sizeof(Node_format)); + return get_somewhat_close_nodes(dht, public_key, nodes_list, sa_family, + is_LAN, want_announce); +} + +typedef struct DHT_Cmp_Data { + uint64_t cur_time; + const uint8_t *base_public_key; + Client_data entry; +} DHT_Cmp_Data; + +non_null() +static int dht_cmp_entry(const void *a, const void *b) +{ + const DHT_Cmp_Data *cmp1 = (const DHT_Cmp_Data *)a; + const DHT_Cmp_Data *cmp2 = (const DHT_Cmp_Data *)b; + const Client_data entry1 = cmp1->entry; + const Client_data entry2 = cmp2->entry; + const uint8_t *cmp_public_key = cmp1->base_public_key; + + const bool t1 = assoc_timeout(cmp1->cur_time, &entry1.assoc4) && assoc_timeout(cmp1->cur_time, &entry1.assoc6); + const bool t2 = assoc_timeout(cmp2->cur_time, &entry2.assoc4) && assoc_timeout(cmp2->cur_time, &entry2.assoc6); + + if (t1 && t2) { + return 0; + } + + if (t1) { + return -1; + } + + if (t2) { + return 1; + } + + const int closest = id_closest(cmp_public_key, entry1.public_key, entry2.public_key); + + if (closest == 1) { + return 1; + } + + if (closest == 2) { + return -1; + } + + return 0; +} + +#ifdef CHECK_ANNOUNCE_NODE +non_null() +static void set_announce_node_in_list(Client_data *list, uint32_t list_len, const uint8_t *public_key) +{ + const uint32_t index = index_of_client_pk(list, list_len, public_key); + + if (index != UINT32_MAX) { + list[index].announce_node = true; + } +} + +void set_announce_node(DHT *dht, const uint8_t *public_key) +{ + unsigned int index = bit_by_bit_cmp(public_key, dht->self_public_key); + + if (index >= LCLIENT_LENGTH) { + index = LCLIENT_LENGTH - 1; + } + + set_announce_node_in_list(dht->close_clientlist + index * LCLIENT_NODES, LCLIENT_NODES, public_key); + + for (int32_t i = 0; i < dht->num_friends; ++i) { + set_announce_node_in_list(dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, public_key); + } +} + +/** @brief Send data search request, searching for a random key. */ +non_null() +static bool send_announce_ping(DHT *dht, const uint8_t *public_key, const IP_Port *ip_port) +{ + uint8_t plain[CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint64_t)]; + + uint8_t unused_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(dht->rng, plain, unused_secret_key); + + const uint64_t ping_id = ping_array_add(dht->dht_ping_array, + dht->mono_time, + dht->rng, + public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(plain + CRYPTO_PUBLIC_KEY_SIZE, &ping_id, sizeof(ping_id)); + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + dht_get_shared_key_sent(dht, shared_key, public_key); + + uint8_t request[1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + sizeof(plain) + CRYPTO_MAC_SIZE]; + + if (dht_create_packet(dht->rng, dht->self_public_key, shared_key, NET_PACKET_DATA_SEARCH_REQUEST, + plain, sizeof(plain), request, sizeof(request)) != sizeof(request)) { + return false; + } + + return sendpacket(dht->net, ip_port, request, sizeof(request)) == sizeof(request); +} + +/** @brief If the response is valid, set the sender as an announce node. */ +non_null(1, 2, 3) nullable(5) +static int handle_data_search_response(void *object, const IP_Port *source, + const uint8_t *packet, uint16_t length, + void *userdata) +{ + DHT *dht = (DHT *) object; + + const int32_t plain_len = (int32_t)length - (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_MAC_SIZE); + + if (plain_len < (int32_t)(CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint64_t))) { + return 1; + } + + VLA(uint8_t, plain, plain_len); + const uint8_t *public_key = packet + 1; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + dht_get_shared_key_recv(dht, shared_key, public_key); + + if (decrypt_data_symmetric(shared_key, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + plain_len + CRYPTO_MAC_SIZE, + plain) != plain_len) { + return 1; + } + + uint64_t ping_id; + memcpy(&ping_id, plain + (plain_len - sizeof(uint64_t)), sizeof(ping_id)); + + uint8_t ping_data[CRYPTO_PUBLIC_KEY_SIZE]; + + if (ping_array_check(dht->dht_ping_array, + dht->mono_time, ping_data, + sizeof(ping_data), ping_id) != sizeof(ping_data)) { + return 1; + } + + if (!pk_equal(ping_data, public_key)) { + return 1; + } + + set_announce_node(dht, public_key); + + return 0; + +} +#endif + +/** @brief Is it ok to store node with public_key in client. + * + * return false if node can't be stored. + * return true if it can. + */ +non_null() +static bool store_node_ok(const Client_data *client, uint64_t cur_time, const uint8_t *public_key, + const uint8_t *comp_public_key) +{ + return (assoc_timeout(cur_time, &client->assoc4) + && assoc_timeout(cur_time, &client->assoc6)) + || id_closest(comp_public_key, client->public_key, public_key) == 2; +} + +non_null() +static void sort_client_list(Client_data *list, uint64_t cur_time, unsigned int length, + const uint8_t *comp_public_key) +{ + // Pass comp_public_key to qsort with each Client_data entry, so the + // comparison function can use it as the base of comparison. + DHT_Cmp_Data *cmp_list = (DHT_Cmp_Data *)calloc(length, sizeof(DHT_Cmp_Data)); + + if (cmp_list == nullptr) { + return; + } + + for (uint32_t i = 0; i < length; ++i) { + cmp_list[i].cur_time = cur_time; + cmp_list[i].base_public_key = comp_public_key; + cmp_list[i].entry = list[i]; + } + + qsort(cmp_list, length, sizeof(DHT_Cmp_Data), dht_cmp_entry); + + for (uint32_t i = 0; i < length; ++i) { + list[i] = cmp_list[i].entry; + } + + free(cmp_list); +} + +non_null() +static void update_client_with_reset(const Mono_Time *mono_time, Client_data *client, const IP_Port *ip_port) +{ + IPPTsPng *ipptp_write = nullptr; + IPPTsPng *ipptp_clear = nullptr; + + if (net_family_is_ipv4(ip_port->ip.family)) { + ipptp_write = &client->assoc4; + ipptp_clear = &client->assoc6; + } else { + ipptp_write = &client->assoc6; + ipptp_clear = &client->assoc4; + } + + ipptp_write->ip_port = *ip_port; + ipptp_write->timestamp = mono_time_get(mono_time); + + ip_reset(&ipptp_write->ret_ip_port.ip); + ipptp_write->ret_ip_port.port = 0; + ipptp_write->ret_timestamp = 0; + ipptp_write->ret_ip_self = false; + + /* zero out other address */ + memset(ipptp_clear, 0, sizeof(*ipptp_clear)); +} + +/** + * Replace a first bad (or empty) node with this one + * or replace a possibly bad node (tests failed or not done yet) + * that is further than any other in the list + * from the comp_public_key + * or replace a good node that is further + * than any other in the list from the comp_public_key + * and further than public_key. + * + * Do not replace any node if the list has no bad or possibly bad nodes + * and all nodes in the list are closer to comp_public_key + * than public_key. + * + * @return true when the item was stored, false otherwise + */ +non_null() +static bool replace_all(const DHT *dht, + Client_data *list, + uint16_t length, + const uint8_t *public_key, + const IP_Port *ip_port, + const uint8_t *comp_public_key) +{ + if (!net_family_is_ipv4(ip_port->ip.family) && !net_family_is_ipv6(ip_port->ip.family)) { + return false; + } + + if (!store_node_ok(&list[1], dht->cur_time, public_key, comp_public_key) && + !store_node_ok(&list[0], dht->cur_time, public_key, comp_public_key)) { + return false; + } + + sort_client_list(list, dht->cur_time, length, comp_public_key); + + Client_data *const client = &list[0]; + pk_copy(client->public_key, public_key); + + update_client_with_reset(dht->mono_time, client, ip_port); + return true; +} + +/** @brief Add node to close list. + * + * simulate is set to 1 if we want to check if a node can be added to the list without adding it. + * + * return false on failure. + * return true on success. + */ +non_null() +static bool add_to_close(DHT *dht, const uint8_t *public_key, const IP_Port *ip_port, bool simulate) +{ + unsigned int index = bit_by_bit_cmp(public_key, dht->self_public_key); + + if (index >= LCLIENT_LENGTH) { + index = LCLIENT_LENGTH - 1; + } + + for (uint32_t i = 0; i < LCLIENT_NODES; ++i) { + /* TODO(iphydf): write bounds checking test to catch the case that + * index is left as >= LCLIENT_LENGTH */ + Client_data *const client = &dht->close_clientlist[(index * LCLIENT_NODES) + i]; + + if (!assoc_timeout(dht->cur_time, &client->assoc4) || + !assoc_timeout(dht->cur_time, &client->assoc6)) { + continue; + } + + if (simulate) { + return true; + } + + pk_copy(client->public_key, public_key); + update_client_with_reset(dht->mono_time, client, ip_port); +#ifdef CHECK_ANNOUNCE_NODE + client->announce_node = false; + send_announce_ping(dht, public_key, ip_port); +#endif + return true; + } + + return false; +} + +/** Return 1 if node can be added to close list, 0 if it can't. */ +bool node_addable_to_close_list(DHT *dht, const uint8_t *public_key, const IP_Port *ip_port) +{ + return add_to_close(dht, public_key, ip_port, true); +} + +non_null() +static bool is_pk_in_client_list(const Client_data *list, unsigned int client_list_length, uint64_t cur_time, + const uint8_t *public_key, const IP_Port *ip_port) +{ + const uint32_t index = index_of_client_pk(list, client_list_length, public_key); + + if (index == UINT32_MAX) { + return false; + } + + const IPPTsPng *assoc = net_family_is_ipv4(ip_port->ip.family) + ? &list[index].assoc4 + : &list[index].assoc6; + + return !assoc_timeout(cur_time, assoc); +} + +non_null() +static bool is_pk_in_close_list(const DHT *dht, const uint8_t *public_key, const IP_Port *ip_port) +{ + unsigned int index = bit_by_bit_cmp(public_key, dht->self_public_key); + + if (index >= LCLIENT_LENGTH) { + index = LCLIENT_LENGTH - 1; + } + + return is_pk_in_client_list(dht->close_clientlist + index * LCLIENT_NODES, LCLIENT_NODES, dht->cur_time, public_key, + ip_port); +} + +/** @brief Check if the node obtained with a get_nodes with public_key should be pinged. + * + * NOTE: for best results call it after addto_lists. + * + * return false if the node should not be pinged. + * return true if it should. + */ +non_null() +static bool ping_node_from_getnodes_ok(DHT *dht, const uint8_t *public_key, const IP_Port *ip_port) +{ + bool ret = false; + + if (add_to_close(dht, public_key, ip_port, true)) { + ret = true; + } + + { + unsigned int *const num = &dht->num_to_bootstrap; + const uint32_t index = index_of_node_pk(dht->to_bootstrap, *num, public_key); + const bool in_close_list = is_pk_in_close_list(dht, public_key, ip_port); + + if (ret && index == UINT32_MAX && !in_close_list) { + if (*num < MAX_CLOSE_TO_BOOTSTRAP_NODES) { + memcpy(dht->to_bootstrap[*num].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + dht->to_bootstrap[*num].ip_port = *ip_port; + ++*num; + } else { + // TODO(irungentoo): ipv6 vs v4 + add_to_list(dht->to_bootstrap, MAX_CLOSE_TO_BOOTSTRAP_NODES, public_key, ip_port, dht->self_public_key); + } + } + } + + for (uint32_t i = 0; i < dht->num_friends; ++i) { + DHT_Friend *dht_friend = &dht->friends_list[i]; + + bool store_ok = false; + + if (store_node_ok(&dht_friend->client_list[1], dht->cur_time, public_key, dht_friend->public_key)) { + store_ok = true; + } + + if (store_node_ok(&dht_friend->client_list[0], dht->cur_time, public_key, dht_friend->public_key)) { + store_ok = true; + } + + unsigned int *const friend_num = &dht_friend->num_to_bootstrap; + const uint32_t index = index_of_node_pk(dht_friend->to_bootstrap, *friend_num, public_key); + const bool pk_in_list = is_pk_in_client_list(dht_friend->client_list, MAX_FRIEND_CLIENTS, dht->cur_time, public_key, + ip_port); + + if (store_ok && index == UINT32_MAX && !pk_in_list) { + if (*friend_num < MAX_SENT_NODES) { + Node_format *const format = &dht_friend->to_bootstrap[*friend_num]; + memcpy(format->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + format->ip_port = *ip_port; + ++*friend_num; + } else { + add_to_list(dht_friend->to_bootstrap, MAX_SENT_NODES, public_key, ip_port, dht_friend->public_key); + } + + ret = true; + } + } + + return ret; +} + +/** @brief Attempt to add client with ip_port and public_key to the friends client list + * and close_clientlist. + * + * @return 1+ if the item is used in any list, 0 else + */ +uint32_t addto_lists(DHT *dht, const IP_Port *ip_port, const uint8_t *public_key) +{ + IP_Port ipp_copy = ip_port_normalize(ip_port); + + uint32_t used = 0; + + /* NOTE: Current behavior if there are two clients with the same id is + * to replace the first ip by the second. + */ + const bool in_close_list = client_or_ip_port_in_list(dht->log, dht->mono_time, dht->close_clientlist, LCLIENT_LIST, + public_key, &ipp_copy); + + /* add_to_close should be called only if !in_list (don't extract to variable) */ + if (in_close_list || !add_to_close(dht, public_key, &ipp_copy, false)) { + ++used; + } + + const DHT_Friend *friend_foundip = nullptr; + + for (uint32_t i = 0; i < dht->num_friends; ++i) { + const bool in_list = client_or_ip_port_in_list(dht->log, dht->mono_time, dht->friends_list[i].client_list, + MAX_FRIEND_CLIENTS, public_key, &ipp_copy); + + /* replace_all should be called only if !in_list (don't extract to variable) */ + if (in_list + || replace_all(dht, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, public_key, &ipp_copy, + dht->friends_list[i].public_key)) { + const DHT_Friend *dht_friend = &dht->friends_list[i]; + + if (pk_equal(public_key, dht_friend->public_key)) { + friend_foundip = dht_friend; + } + + ++used; + } + } + + if (friend_foundip == nullptr) { + return used; + } + + for (uint32_t i = 0; i < friend_foundip->lock_count; ++i) { + if (friend_foundip->callbacks[i].ip_callback != nullptr) { + friend_foundip->callbacks[i].ip_callback(friend_foundip->callbacks[i].data, + friend_foundip->callbacks[i].number, &ipp_copy); + } + } + + return used; +} + +non_null() +static bool update_client_data(const Mono_Time *mono_time, Client_data *array, size_t size, const IP_Port *ip_port, + const uint8_t *pk, bool node_is_self) +{ + const uint64_t temp_time = mono_time_get(mono_time); + const uint32_t index = index_of_client_pk(array, size, pk); + + if (index == UINT32_MAX) { + return false; + } + + Client_data *const data = &array[index]; + IPPTsPng *assoc; + + if (net_family_is_ipv4(ip_port->ip.family)) { + assoc = &data->assoc4; + } else if (net_family_is_ipv6(ip_port->ip.family)) { + assoc = &data->assoc6; + } else { + return true; + } + + assoc->ret_ip_port = *ip_port; + assoc->ret_timestamp = temp_time; + assoc->ret_ip_self = node_is_self; + + return true; +} + +/** + * If public_key is a friend or us, update ret_ip_port + * nodepublic_key is the id of the node that sent us this info. + */ +non_null() +static void returnedip_ports(DHT *dht, const IP_Port *ip_port, const uint8_t *public_key, const uint8_t *nodepublic_key) +{ + IP_Port ipp_copy = ip_port_normalize(ip_port); + + if (pk_equal(public_key, dht->self_public_key)) { + update_client_data(dht->mono_time, dht->close_clientlist, LCLIENT_LIST, &ipp_copy, nodepublic_key, true); + return; + } + + for (uint32_t i = 0; i < dht->num_friends; ++i) { + if (pk_equal(public_key, dht->friends_list[i].public_key)) { + Client_data *const client_list = dht->friends_list[i].client_list; + + if (update_client_data(dht->mono_time, client_list, MAX_FRIEND_CLIENTS, &ipp_copy, nodepublic_key, false)) { + return; + } + } + } +} + +bool dht_getnodes(DHT *dht, const IP_Port *ip_port, const uint8_t *public_key, const uint8_t *client_id) +{ + /* Check if packet is going to be sent to ourself. */ + if (pk_equal(public_key, dht->self_public_key)) { + return false; + } + + uint8_t plain_message[sizeof(Node_format) * 2] = {0}; + + Node_format receiver; + memcpy(receiver.public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + receiver.ip_port = *ip_port; + + if (pack_nodes(dht->log, plain_message, sizeof(plain_message), &receiver, 1) == -1) { + return false; + } + + uint64_t ping_id = 0; + + ping_id = ping_array_add(dht->dht_ping_array, dht->mono_time, dht->rng, plain_message, sizeof(receiver)); + + if (ping_id == 0) { + LOGGER_ERROR(dht->log, "adding ping id failed"); + return false; + } + + uint8_t plain[CRYPTO_PUBLIC_KEY_SIZE + sizeof(ping_id)]; + uint8_t data[1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + sizeof(plain) + CRYPTO_MAC_SIZE]; + + memcpy(plain, client_id, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(plain + CRYPTO_PUBLIC_KEY_SIZE, &ping_id, sizeof(ping_id)); + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + dht_get_shared_key_sent(dht, shared_key, public_key); + + const int len = dht_create_packet(dht->rng, + dht->self_public_key, shared_key, NET_PACKET_GET_NODES, + plain, sizeof(plain), data, sizeof(data)); + + crypto_memzero(shared_key, sizeof(shared_key)); + + if (len != sizeof(data)) { + LOGGER_ERROR(dht->log, "getnodes packet encryption failed"); + return false; + } + + return sendpacket(dht->net, ip_port, data, len) > 0; +} + +/** Send a send nodes response: message for IPv6 nodes */ +non_null() +static int sendnodes_ipv6(const DHT *dht, const IP_Port *ip_port, const uint8_t *public_key, const uint8_t *client_id, + const uint8_t *sendback_data, uint16_t length, const uint8_t *shared_encryption_key) +{ + /* Check if packet is going to be sent to ourself. */ + if (pk_equal(public_key, dht->self_public_key)) { + return -1; + } + + if (length != sizeof(uint64_t)) { + return -1; + } + + const size_t node_format_size = sizeof(Node_format); + + Node_format nodes_list[MAX_SENT_NODES]; + const uint32_t num_nodes = + get_close_nodes(dht, client_id, nodes_list, net_family_unspec(), ip_is_lan(&ip_port->ip), false); + + VLA(uint8_t, plain, 1 + node_format_size * MAX_SENT_NODES + length); + + int nodes_length = 0; + + if (num_nodes > 0) { + nodes_length = pack_nodes(dht->log, plain + 1, node_format_size * MAX_SENT_NODES, nodes_list, num_nodes); + + if (nodes_length <= 0) { + return -1; + } + } + + plain[0] = num_nodes; + memcpy(plain + 1 + nodes_length, sendback_data, length); + + const uint32_t crypto_size = 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_MAC_SIZE; + VLA(uint8_t, data, 1 + nodes_length + length + crypto_size); + + const int len = dht_create_packet(dht->rng, + dht->self_public_key, shared_encryption_key, NET_PACKET_SEND_NODES_IPV6, + plain, 1 + nodes_length + length, data, SIZEOF_VLA(data)); + + if (len != SIZEOF_VLA(data)) { + return -1; + } + + return sendpacket(dht->net, ip_port, data, len); +} + +#define CRYPTO_NODE_SIZE (CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint64_t)) + +non_null() +static int handle_getnodes(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, void *userdata) +{ + if (length != (CRYPTO_SIZE + CRYPTO_MAC_SIZE + sizeof(uint64_t))) { + return 1; + } + + DHT *const dht = (DHT *)object; + + /* Check if packet is from ourself. */ + if (pk_equal(packet + 1, dht->self_public_key)) { + return 1; + } + + uint8_t plain[CRYPTO_NODE_SIZE]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + + dht_get_shared_key_recv(dht, shared_key, packet + 1); + const int len = decrypt_data_symmetric( + shared_key, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + CRYPTO_NODE_SIZE + CRYPTO_MAC_SIZE, + plain); + + if (len != CRYPTO_NODE_SIZE) { + crypto_memzero(shared_key, sizeof(shared_key)); + return 1; + } + + sendnodes_ipv6(dht, source, packet + 1, plain, plain + CRYPTO_PUBLIC_KEY_SIZE, sizeof(uint64_t), shared_key); + + ping_add(dht->ping, packet + 1, source); + + crypto_memzero(shared_key, sizeof(shared_key)); + + return 0; +} + +/** Return true if we sent a getnode packet to the peer associated with the supplied info. */ +non_null() +static bool sent_getnode_to_node(DHT *dht, const uint8_t *public_key, const IP_Port *node_ip_port, uint64_t ping_id) +{ + uint8_t data[sizeof(Node_format) * 2]; + + if (ping_array_check(dht->dht_ping_array, dht->mono_time, data, sizeof(data), ping_id) != sizeof(Node_format)) { + return false; + } + + Node_format test; + + if (unpack_nodes(&test, 1, nullptr, data, sizeof(data), false) != 1) { + return false; + } + + return ipport_equal(&test.ip_port, node_ip_port) && pk_equal(test.public_key, public_key); +} + +non_null() +static bool handle_sendnodes_core(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + Node_format *plain_nodes, uint16_t size_plain_nodes, uint32_t *num_nodes_out) +{ + DHT *const dht = (DHT *)object; + const uint32_t cid_size = 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + 1 + sizeof(uint64_t) + CRYPTO_MAC_SIZE; + + if (length < cid_size) { /* too short */ + return false; + } + + const uint32_t data_size = length - cid_size; + + if (data_size == 0) { + return false; + } + + if (data_size > sizeof(Node_format) * MAX_SENT_NODES) { /* invalid length */ + return false; + } + + VLA(uint8_t, plain, 1 + data_size + sizeof(uint64_t)); + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + dht_get_shared_key_sent(dht, shared_key, packet + 1); + const int len = decrypt_data_symmetric( + shared_key, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + 1 + data_size + sizeof(uint64_t) + CRYPTO_MAC_SIZE, + plain); + + crypto_memzero(shared_key, sizeof(shared_key)); + + if ((unsigned int)len != SIZEOF_VLA(plain)) { + return false; + } + + if (plain[0] > size_plain_nodes) { + return false; + } + + uint64_t ping_id; + memcpy(&ping_id, plain + 1 + data_size, sizeof(ping_id)); + + if (!sent_getnode_to_node(dht, packet + 1, source, ping_id)) { + return false; + } + + uint16_t length_nodes = 0; + const int num_nodes = unpack_nodes(plain_nodes, plain[0], &length_nodes, plain + 1, data_size, false); + + if (length_nodes != data_size) { + return false; + } + + if (num_nodes != plain[0]) { + return false; + } + + if (num_nodes < 0) { + return false; + } + + /* store the address the *request* was sent to */ + addto_lists(dht, source, packet + 1); + + *num_nodes_out = num_nodes; + + return true; +} + +non_null() +static int handle_sendnodes_ipv6(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + DHT *const dht = (DHT *)object; + Node_format plain_nodes[MAX_SENT_NODES]; + uint32_t num_nodes; + + if (!handle_sendnodes_core(object, source, packet, length, plain_nodes, MAX_SENT_NODES, &num_nodes)) { + return 1; + } + + if (num_nodes == 0) { + return 0; + } + + for (uint32_t i = 0; i < num_nodes; ++i) { + if (ipport_isset(&plain_nodes[i].ip_port)) { + ping_node_from_getnodes_ok(dht, plain_nodes[i].public_key, &plain_nodes[i].ip_port); + returnedip_ports(dht, &plain_nodes[i].ip_port, plain_nodes[i].public_key, packet + 1); + + if (dht->get_nodes_response != nullptr) { + dht->get_nodes_response(dht, &plain_nodes[i], userdata); + } + } + } + + return 0; +} + +/*----------------------------------------------------------------------------------*/ +/*------------------------END of packet handling functions--------------------------*/ + +non_null(1) nullable(2, 3, 5) +static void dht_friend_lock(DHT_Friend *const dht_friend, dht_ip_cb *ip_callback, + void *data, int32_t number, uint16_t *lock_count) +{ + const uint16_t lock_num = dht_friend->lock_count; + ++dht_friend->lock_count; + dht_friend->callbacks[lock_num].ip_callback = ip_callback; + dht_friend->callbacks[lock_num].data = data; + dht_friend->callbacks[lock_num].number = number; + + if (lock_count != nullptr) { + *lock_count = lock_num + 1; + } +} + +int dht_addfriend(DHT *dht, const uint8_t *public_key, dht_ip_cb *ip_callback, + void *data, int32_t number, uint16_t *lock_count) +{ + const uint32_t friend_num = index_of_friend_pk(dht->friends_list, dht->num_friends, public_key); + + if (friend_num != UINT32_MAX) { /* Is friend already in DHT? */ + DHT_Friend *const dht_friend = &dht->friends_list[friend_num]; + + if (dht_friend->lock_count == DHT_FRIEND_MAX_LOCKS) { + return -1; + } + + dht_friend_lock(dht_friend, ip_callback, data, number, lock_count); + + return 0; + } + + DHT_Friend *const temp = (DHT_Friend *)realloc(dht->friends_list, sizeof(DHT_Friend) * (dht->num_friends + 1)); + + if (temp == nullptr) { + return -1; + } + + dht->friends_list = temp; + DHT_Friend *const dht_friend = &dht->friends_list[dht->num_friends]; + *dht_friend = empty_dht_friend; + memcpy(dht_friend->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + + dht_friend->nat.nat_ping_id = random_u64(dht->rng); + ++dht->num_friends; + + dht_friend_lock(dht_friend, ip_callback, data, number, lock_count); + + dht_friend->num_to_bootstrap = get_close_nodes(dht, dht_friend->public_key, dht_friend->to_bootstrap, net_family_unspec(), + true, false); + + return 0; +} + +int dht_delfriend(DHT *dht, const uint8_t *public_key, uint16_t lock_count) +{ + const uint32_t friend_num = index_of_friend_pk(dht->friends_list, dht->num_friends, public_key); + + if (friend_num == UINT32_MAX) { + return -1; + } + + DHT_Friend *const dht_friend = &dht->friends_list[friend_num]; + --dht_friend->lock_count; + + if (dht_friend->lock_count > 0 && lock_count > 0) { /* DHT friend is still in use.*/ + --lock_count; + dht_friend->callbacks[lock_count].ip_callback = nullptr; + dht_friend->callbacks[lock_count].data = nullptr; + dht_friend->callbacks[lock_count].number = 0; + return 0; + } + + --dht->num_friends; + + if (dht->num_friends != friend_num) { + dht->friends_list[friend_num] = dht->friends_list[dht->num_friends]; + } + + if (dht->num_friends == 0) { + free(dht->friends_list); + dht->friends_list = nullptr; + return 0; + } + + DHT_Friend *const temp = (DHT_Friend *)realloc(dht->friends_list, sizeof(DHT_Friend) * dht->num_friends); + + if (temp == nullptr) { + return -1; + } + + dht->friends_list = temp; + return 0; +} + +/* TODO(irungentoo): Optimize this. */ +int dht_getfriendip(const DHT *dht, const uint8_t *public_key, IP_Port *ip_port) +{ + ip_reset(&ip_port->ip); + ip_port->port = 0; + + const uint32_t friend_index = index_of_friend_pk(dht->friends_list, dht->num_friends, public_key); + + if (friend_index == UINT32_MAX) { + return -1; + } + + const DHT_Friend *const frnd = &dht->friends_list[friend_index]; + const uint32_t client_index = index_of_client_pk(frnd->client_list, MAX_FRIEND_CLIENTS, public_key); + + if (client_index == -1) { + return 0; + } + + const Client_data *const client = &frnd->client_list[client_index]; + const IPPTsPng *const assocs[] = { &client->assoc6, &client->assoc4, nullptr }; + + for (const IPPTsPng * const *it = assocs; *it != nullptr; ++it) { + const IPPTsPng *const assoc = *it; + + if (!assoc_timeout(dht->cur_time, assoc)) { + *ip_port = assoc->ip_port; + return 1; + } + } + + return -1; +} + +/** returns number of nodes not in kill-timeout */ +non_null() +static uint8_t do_ping_and_sendnode_requests(DHT *dht, uint64_t *lastgetnode, const uint8_t *public_key, + Client_data *list, uint32_t list_count, uint32_t *bootstrap_times, bool sortable) +{ + uint8_t not_kill = 0; + const uint64_t temp_time = mono_time_get(dht->mono_time); + + uint32_t num_nodes = 0; + Client_data **client_list = (Client_data **)calloc(list_count * 2, sizeof(Client_data *)); + IPPTsPng **assoc_list = (IPPTsPng **)calloc(list_count * 2, sizeof(IPPTsPng *)); + unsigned int sort = 0; + bool sort_ok = false; + + if (client_list == nullptr || assoc_list == nullptr) { + free(assoc_list); + free(client_list); + return 0; + } + + for (uint32_t i = 0; i < list_count; ++i) { + /* If node is not dead. */ + Client_data *client = &list[i]; + + IPPTsPng *const assocs[] = { &client->assoc6, &client->assoc4 }; + + for (uint32_t j = 0; j < sizeof(assocs) / sizeof(assocs[0]); ++j) { + IPPTsPng *const assoc = assocs[j]; + + if (!mono_time_is_timeout(dht->mono_time, assoc->timestamp, KILL_NODE_TIMEOUT)) { + sort = 0; + ++not_kill; + + if (mono_time_is_timeout(dht->mono_time, assoc->last_pinged, PING_INTERVAL)) { + dht_getnodes(dht, &assoc->ip_port, client->public_key, public_key); + assoc->last_pinged = temp_time; + } + + /* If node is good. */ + if (!assoc_timeout(dht->cur_time, assoc)) { + client_list[num_nodes] = client; + assoc_list[num_nodes] = assoc; + ++num_nodes; + } + } else { + ++sort; + + /* Timed out should be at beginning, if they are not, sort the list. */ + if (sort > 1 && sort < (((j + 1) * 2) - 1)) { + sort_ok = true; + } + } + } + } + + if (sortable && sort_ok) { + sort_client_list(list, dht->cur_time, list_count, public_key); + } + + if (num_nodes > 0 && (mono_time_is_timeout(dht->mono_time, *lastgetnode, GET_NODE_INTERVAL) + || *bootstrap_times < MAX_BOOTSTRAP_TIMES)) { + uint32_t rand_node = random_range_u32(dht->rng, num_nodes); + + if ((num_nodes - 1) != rand_node) { + rand_node += random_range_u32(dht->rng, num_nodes - (rand_node + 1)); + } + + dht_getnodes(dht, &assoc_list[rand_node]->ip_port, client_list[rand_node]->public_key, public_key); + + *lastgetnode = temp_time; + ++*bootstrap_times; + } + + free(assoc_list); + free(client_list); + return not_kill; +} + +/** @brief Ping each client in the "friends" list every PING_INTERVAL seconds. + * + * Send a get nodes request every GET_NODE_INTERVAL seconds to a random good + * node for each "friend" in our "friends" list. + */ +non_null() +static void do_dht_friends(DHT *dht) +{ + for (size_t i = 0; i < dht->num_friends; ++i) { + DHT_Friend *const dht_friend = &dht->friends_list[i]; + + for (size_t j = 0; j < dht_friend->num_to_bootstrap; ++j) { + dht_getnodes(dht, &dht_friend->to_bootstrap[j].ip_port, dht_friend->to_bootstrap[j].public_key, dht_friend->public_key); + } + + dht_friend->num_to_bootstrap = 0; + + do_ping_and_sendnode_requests(dht, &dht_friend->lastgetnode, dht_friend->public_key, dht_friend->client_list, + MAX_FRIEND_CLIENTS, + &dht_friend->bootstrap_times, true); + } +} + +/** @brief Ping each client in the close nodes list every PING_INTERVAL seconds. + * + * Send a get nodes request every GET_NODE_INTERVAL seconds to a random good node in the list. + */ +non_null() +static void do_Close(DHT *dht) +{ + for (size_t i = 0; i < dht->num_to_bootstrap; ++i) { + dht_getnodes(dht, &dht->to_bootstrap[i].ip_port, dht->to_bootstrap[i].public_key, dht->self_public_key); + } + + dht->num_to_bootstrap = 0; + + const uint8_t not_killed = do_ping_and_sendnode_requests( + dht, &dht->close_lastgetnodes, dht->self_public_key, dht->close_clientlist, LCLIENT_LIST, &dht->close_bootstrap_times, + false); + + if (not_killed != 0) { + return; + } + + /* all existing nodes are at least KILL_NODE_TIMEOUT, + * which means we are mute, as we only send packets to + * nodes NOT in KILL_NODE_TIMEOUT + * + * so: reset all nodes to be BAD_NODE_TIMEOUT, but not + * KILL_NODE_TIMEOUT, so we at least keep trying pings */ + const uint64_t badonly = mono_time_get(dht->mono_time) - BAD_NODE_TIMEOUT; + + for (size_t i = 0; i < LCLIENT_LIST; ++i) { + Client_data *const client = &dht->close_clientlist[i]; + + IPPTsPng *const assocs[] = { &client->assoc6, &client->assoc4, nullptr }; + + for (IPPTsPng * const *it = assocs; *it != nullptr; ++it) { + IPPTsPng *const assoc = *it; + + if (assoc->timestamp != 0) { + assoc->timestamp = badonly; + } + } + } +} + +bool dht_bootstrap(DHT *dht, const IP_Port *ip_port, const uint8_t *public_key) +{ + if (pk_equal(public_key, dht->self_public_key)) { + // Bootstrapping off ourselves is ok (onion paths are still set up). + return true; + } + + return dht_getnodes(dht, ip_port, public_key, dht->self_public_key); +} + +int dht_bootstrap_from_address(DHT *dht, const char *address, bool ipv6enabled, + uint16_t port, const uint8_t *public_key) +{ + IP_Port ip_port_v64; + IP *ip_extra = nullptr; + IP_Port ip_port_v4; + ip_init(&ip_port_v64.ip, ipv6enabled); + + if (ipv6enabled) { + /* setup for getting BOTH: an IPv6 AND an IPv4 address */ + ip_port_v64.ip.family = net_family_unspec(); + ip_reset(&ip_port_v4.ip); + ip_extra = &ip_port_v4.ip; + } + + if (addr_resolve_or_parse_ip(dht->ns, address, &ip_port_v64.ip, ip_extra)) { + ip_port_v64.port = port; + dht_bootstrap(dht, &ip_port_v64, public_key); + + if ((ip_extra != nullptr) && ip_isset(ip_extra)) { + ip_port_v4.port = port; + dht_bootstrap(dht, &ip_port_v4, public_key); + } + + return 1; + } + + return 0; +} + +/** @brief Send the given packet to node with public_key. + * + * @return number of bytes sent. + * @retval -1 if failure. + */ +int route_packet(const DHT *dht, const uint8_t *public_key, const uint8_t *packet, uint16_t length) +{ + for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { + if (pk_equal(public_key, dht->close_clientlist[i].public_key)) { + const Client_data *const client = &dht->close_clientlist[i]; + const IPPTsPng *const assocs[] = { &client->assoc6, &client->assoc4, nullptr }; + + for (const IPPTsPng * const *it = assocs; *it != nullptr; ++it) { + const IPPTsPng *const assoc = *it; + + if (ip_isset(&assoc->ip_port.ip)) { + return sendpacket(dht->net, &assoc->ip_port, packet, length); + } + } + + break; + } + } + + return -1; +} + +/** @brief Puts all the different ips returned by the nodes for a friend_num into array ip_portlist. + * + * ip_portlist must be at least MAX_FRIEND_CLIENTS big. + * + * @return the number of ips returned. + * @retval 0 if we are connected to friend or if no ips were found. + * @retval -1 if no such friend. + */ +non_null() +static int friend_iplist(const DHT *dht, IP_Port *ip_portlist, uint16_t friend_num) +{ + if (friend_num >= dht->num_friends) { + return -1; + } + + const DHT_Friend *const dht_friend = &dht->friends_list[friend_num]; + IP_Port ipv4s[MAX_FRIEND_CLIENTS]; + int num_ipv4s = 0; + IP_Port ipv6s[MAX_FRIEND_CLIENTS]; + int num_ipv6s = 0; + + for (size_t i = 0; i < MAX_FRIEND_CLIENTS; ++i) { + const Client_data *const client = &dht_friend->client_list[i]; + + /* If ip is not zero and node is good. */ + if (ip_isset(&client->assoc4.ret_ip_port.ip) + && !mono_time_is_timeout(dht->mono_time, client->assoc4.ret_timestamp, BAD_NODE_TIMEOUT)) { + ipv4s[num_ipv4s] = client->assoc4.ret_ip_port; + ++num_ipv4s; + } + + if (ip_isset(&client->assoc6.ret_ip_port.ip) + && !mono_time_is_timeout(dht->mono_time, client->assoc6.ret_timestamp, BAD_NODE_TIMEOUT)) { + ipv6s[num_ipv6s] = client->assoc6.ret_ip_port; + ++num_ipv6s; + } + + if (pk_equal(client->public_key, dht_friend->public_key)) { + if (!assoc_timeout(dht->cur_time, &client->assoc6) + || !assoc_timeout(dht->cur_time, &client->assoc4)) { + return 0; /* direct connectivity */ + } + } + } + +#ifdef FRIEND_IPLIST_PAD + memcpy(ip_portlist, ipv6s, num_ipv6s * sizeof(IP_Port)); + + if (num_ipv6s == MAX_FRIEND_CLIENTS) { + return MAX_FRIEND_CLIENTS; + } + + int num_ipv4s_used = MAX_FRIEND_CLIENTS - num_ipv6s; + + if (num_ipv4s_used > num_ipv4s) { + num_ipv4s_used = num_ipv4s; + } + + memcpy(&ip_portlist[num_ipv6s], ipv4s, num_ipv4s_used * sizeof(IP_Port)); + return num_ipv6s + num_ipv4s_used; + +#else /* !FRIEND_IPLIST_PAD */ + + /* there must be some secret reason why we can't pad the longer list + * with the shorter one... + */ + if (num_ipv6s >= num_ipv4s) { + memcpy(ip_portlist, ipv6s, num_ipv6s * sizeof(IP_Port)); + return num_ipv6s; + } + + memcpy(ip_portlist, ipv4s, num_ipv4s * sizeof(IP_Port)); + return num_ipv4s; + +#endif /* !FRIEND_IPLIST_PAD */ +} + + +/** + * Callback invoked for each IP/port of each client of a friend. + * + * For each client, the callback is invoked twice: once for IPv4 and once for + * IPv6. If the callback returns `false` after the IPv4 invocation, it will not + * be invoked for IPv6. + * + * @param dht The main DHT instance. + * @param ip_port The currently processed IP/port. + * @param n A pointer to the number that will be returned from `foreach_ip_port`. + * @param userdata The `userdata` pointer passed to `foreach_ip_port`. + */ +typedef bool foreach_ip_port_cb(const DHT *dht, const IP_Port *ip_port, uint32_t *n, void *userdata); + +/** + * Runs a callback on every active connection for a given DHT friend. + * + * This iterates over the client list of a DHT friend and invokes a callback for + * every non-zero IP/port (IPv4 and IPv6) that's not timed out. + * + * @param dht The main DHT instance, passed to the callback. + * @param dht_friend The friend over whose connections we should iterate. + * @param callback The callback to invoke for each IP/port. + * @param userdata Extra pointer passed to the callback. + */ +non_null() +static uint32_t foreach_ip_port(const DHT *dht, const DHT_Friend *dht_friend, + foreach_ip_port_cb *callback, void *userdata) +{ + uint32_t n = 0; + + /* extra legwork, because having the outside allocating the space for us + * is *usually* good(tm) (bites us in the behind in this case though) */ + for (uint32_t i = 0; i < MAX_FRIEND_CLIENTS; ++i) { + const Client_data *const client = &dht_friend->client_list[i]; + const IPPTsPng *const assocs[] = { &client->assoc4, &client->assoc6, nullptr }; + + for (const IPPTsPng * const *it = assocs; *it != nullptr; ++it) { + const IPPTsPng *const assoc = *it; + + /* If ip is not zero and node is good. */ + if (!ip_isset(&assoc->ret_ip_port.ip) + && !mono_time_is_timeout(dht->mono_time, assoc->ret_timestamp, BAD_NODE_TIMEOUT)) { + continue; + } + + if (!callback(dht, &assoc->ip_port, &n, userdata)) { + /* If the callback is happy with just one of the assocs, we + * don't give it the second one. */ + break; + } + } + } + + return n; +} + +non_null() +static bool send_packet_to_friend(const DHT *dht, const IP_Port *ip_port, uint32_t *n, void *userdata) +{ + const Packet *packet = (const Packet *)userdata; + const int retval = send_packet(dht->net, ip_port, *packet); + + if ((uint32_t)retval == packet->length) { + ++*n; + /* Send one packet per friend: stop the foreach on the first success. */ + return false; + } + + return true; +} + +/** + * Send the following packet to everyone who tells us they are connected to friend_id. + * + * @return ip for friend. + * @return number of nodes the packet was sent to. (Only works if more than (MAX_FRIEND_CLIENTS / 4). + */ +uint32_t route_to_friend(const DHT *dht, const uint8_t *friend_id, const Packet *packet) +{ + const uint32_t num = index_of_friend_pk(dht->friends_list, dht->num_friends, friend_id); + + if (num == UINT32_MAX) { + return 0; + } + + + IP_Port ip_list[MAX_FRIEND_CLIENTS]; + const int ip_num = friend_iplist(dht, ip_list, num); + + if (ip_num < MAX_FRIEND_CLIENTS / 4) { + return 0; /* Reason for that? */ + } + + const DHT_Friend *const dht_friend = &dht->friends_list[num]; + Packet packet_userdata = *packet; // Copy because it needs to be non-const. + + return foreach_ip_port(dht, dht_friend, send_packet_to_friend, &packet_userdata); +} + +non_null() +static bool get_ip_port(const DHT *dht, const IP_Port *ip_port, uint32_t *n, void *userdata) +{ + IP_Port *ip_list = (IP_Port *)userdata; + ip_list[*n] = *ip_port; + ++*n; + return true; +} + +/** @brief Send the following packet to one random person who tells us they are connected to friend_id. + * + * @return number of nodes the packet was sent to. + */ +non_null() +static uint32_t routeone_to_friend(const DHT *dht, const uint8_t *friend_id, const Packet *packet) +{ + const uint32_t num = index_of_friend_pk(dht->friends_list, dht->num_friends, friend_id); + + if (num == UINT32_MAX) { + return 0; + } + + const DHT_Friend *const dht_friend = &dht->friends_list[num]; + + IP_Port ip_list[MAX_FRIEND_CLIENTS * 2]; + + const int n = foreach_ip_port(dht, dht_friend, get_ip_port, ip_list); + + if (n < 1) { + return 0; + } + + const uint32_t rand_idx = random_range_u32(dht->rng, n); + const int retval = send_packet(dht->net, &ip_list[rand_idx], *packet); + + if ((unsigned int)retval == packet->length) { + return 1; + } + + return 0; +} + +/*----------------------------------------------------------------------------------*/ +/*---------------------BEGINNING OF NAT PUNCHING FUNCTIONS--------------------------*/ + +non_null() +static int send_NATping(const DHT *dht, const uint8_t *public_key, uint64_t ping_id, uint8_t type) +{ + uint8_t data[sizeof(uint64_t) + 1]; + uint8_t packet_data[MAX_CRYPTO_REQUEST_SIZE]; + + data[0] = type; + memcpy(data + 1, &ping_id, sizeof(uint64_t)); + /* 254 is NAT ping request packet id */ + const int len = create_request( + dht->rng, dht->self_public_key, dht->self_secret_key, packet_data, public_key, + data, sizeof(uint64_t) + 1, CRYPTO_PACKET_NAT_PING); + + if (len == -1) { + return -1; + } + + assert(len <= UINT16_MAX); + uint32_t num = 0; + const Packet packet = {packet_data, (uint16_t)len}; + + if (type == 0) { /* If packet is request use many people to route it. */ + num = route_to_friend(dht, public_key, &packet); + } else if (type == 1) { /* If packet is response use only one person to route it */ + num = routeone_to_friend(dht, public_key, &packet); + } + + if (num == 0) { + return -1; + } + + return num; +} + +/** Handle a received ping request for. */ +non_null() +static int handle_NATping(void *object, const IP_Port *source, const uint8_t *source_pubkey, const uint8_t *packet, + uint16_t length, void *userdata) +{ + if (length != sizeof(uint64_t) + 1) { + return 1; + } + + DHT *const dht = (DHT *)object; + uint64_t ping_id; + memcpy(&ping_id, packet + 1, sizeof(uint64_t)); + + const uint32_t friendnumber = index_of_friend_pk(dht->friends_list, dht->num_friends, source_pubkey); + + if (friendnumber == UINT32_MAX) { + return 1; + } + + DHT_Friend *const dht_friend = &dht->friends_list[friendnumber]; + + if (packet[0] == NAT_PING_REQUEST) { + /* 1 is reply */ + send_NATping(dht, source_pubkey, ping_id, NAT_PING_RESPONSE); + dht_friend->nat.recv_nat_ping_timestamp = mono_time_get(dht->mono_time); + return 0; + } + + if (packet[0] == NAT_PING_RESPONSE) { + if (dht_friend->nat.nat_ping_id == ping_id) { + dht_friend->nat.nat_ping_id = random_u64(dht->rng); + dht_friend->nat.hole_punching = true; + return 0; + } + } + + return 1; +} + +/** @brief Get the most common ip in the ip_portlist. + * Only return ip if it appears in list min_num or more. + * len must not be bigger than MAX_FRIEND_CLIENTS. + * + * @return ip of 0 if failure. + */ +non_null() +static IP nat_commonip(const IP_Port *ip_portlist, uint16_t len, uint16_t min_num) +{ + IP zero; + ip_reset(&zero); + + if (len > MAX_FRIEND_CLIENTS) { + return zero; + } + + uint16_t numbers[MAX_FRIEND_CLIENTS] = {0}; + + for (uint32_t i = 0; i < len; ++i) { + for (uint32_t j = 0; j < len; ++j) { + if (ip_equal(&ip_portlist[i].ip, &ip_portlist[j].ip)) { + ++numbers[i]; + } + } + + if (numbers[i] >= min_num) { + return ip_portlist[i].ip; + } + } + + return zero; +} + +/** @brief Return all the ports for one ip in a list. + * portlist must be at least len long, + * where len is the length of ip_portlist. + * + * @return number of ports and puts the list of ports in portlist. + */ +non_null() +static uint16_t nat_getports(uint16_t *portlist, const IP_Port *ip_portlist, uint16_t len, const IP *ip) +{ + uint16_t num = 0; + + for (uint32_t i = 0; i < len; ++i) { + if (ip_equal(&ip_portlist[i].ip, ip)) { + portlist[num] = net_ntohs(ip_portlist[i].port); + ++num; + } + } + + return num; +} + +non_null() +static void punch_holes(DHT *dht, const IP *ip, const uint16_t *port_list, uint16_t numports, uint16_t friend_num) +{ + if (!dht->hole_punching_enabled) { + return; + } + + if (numports > MAX_FRIEND_CLIENTS || numports == 0) { + return; + } + + const uint16_t first_port = port_list[0]; + uint16_t port_candidate; + + for (port_candidate = 0; port_candidate < numports; ++port_candidate) { + if (first_port != port_list[port_candidate]) { + break; + } + } + + if (port_candidate == numports) { /* If all ports are the same, only try that one port. */ + IP_Port pinging; + ip_copy(&pinging.ip, ip); + pinging.port = net_htons(first_port); + ping_send_request(dht->ping, &pinging, dht->friends_list[friend_num].public_key); + } else { + uint16_t i; + for (i = 0; i < MAX_PUNCHING_PORTS; ++i) { + /* TODO(irungentoo): Improve port guessing algorithm. */ + const uint32_t it = i + dht->friends_list[friend_num].nat.punching_index; + const int8_t sign = (it % 2 != 0) ? -1 : 1; + const uint32_t delta = sign * (it / (2 * numports)); + const uint32_t index = (it / 2) % numports; + const uint16_t port = port_list[index] + delta; + IP_Port pinging; + ip_copy(&pinging.ip, ip); + pinging.port = net_htons(port); + ping_send_request(dht->ping, &pinging, dht->friends_list[friend_num].public_key); + } + + dht->friends_list[friend_num].nat.punching_index += i; + } + + if (dht->friends_list[friend_num].nat.tries > MAX_NORMAL_PUNCHING_TRIES) { + IP_Port pinging; + ip_copy(&pinging.ip, ip); + + uint16_t i; + for (i = 0; i < MAX_PUNCHING_PORTS; ++i) { + uint32_t it = i + dht->friends_list[friend_num].nat.punching_index2; + const uint16_t port = 1024; + pinging.port = net_htons(port + it); + ping_send_request(dht->ping, &pinging, dht->friends_list[friend_num].public_key); + } + + dht->friends_list[friend_num].nat.punching_index2 += i - (MAX_PUNCHING_PORTS / 2); + } + + ++dht->friends_list[friend_num].nat.tries; +} + +non_null() +static void do_NAT(DHT *dht) +{ + const uint64_t temp_time = mono_time_get(dht->mono_time); + + for (uint32_t i = 0; i < dht->num_friends; ++i) { + IP_Port ip_list[MAX_FRIEND_CLIENTS]; + const int num = friend_iplist(dht, ip_list, i); + + /* If already connected or friend is not online don't try to hole punch. */ + if (num < MAX_FRIEND_CLIENTS / 2) { + continue; + } + + if (dht->friends_list[i].nat.nat_ping_timestamp + PUNCH_INTERVAL < temp_time) { + send_NATping(dht, dht->friends_list[i].public_key, dht->friends_list[i].nat.nat_ping_id, NAT_PING_REQUEST); + dht->friends_list[i].nat.nat_ping_timestamp = temp_time; + } + + if (dht->friends_list[i].nat.hole_punching && + dht->friends_list[i].nat.punching_timestamp + PUNCH_INTERVAL < temp_time && + dht->friends_list[i].nat.recv_nat_ping_timestamp + PUNCH_INTERVAL * 2 >= temp_time) { + + const IP ip = nat_commonip(ip_list, num, MAX_FRIEND_CLIENTS / 2); + + if (!ip_isset(&ip)) { + continue; + } + + if (dht->friends_list[i].nat.punching_timestamp + PUNCH_RESET_TIME < temp_time) { + dht->friends_list[i].nat.tries = 0; + dht->friends_list[i].nat.punching_index = 0; + dht->friends_list[i].nat.punching_index2 = 0; + } + + uint16_t port_list[MAX_FRIEND_CLIENTS]; + const uint16_t numports = nat_getports(port_list, ip_list, num, &ip); + punch_holes(dht, &ip, port_list, numports, i); + + dht->friends_list[i].nat.punching_timestamp = temp_time; + dht->friends_list[i].nat.hole_punching = false; + } + } +} + +/*----------------------------------------------------------------------------------*/ +/*-----------------------END OF NAT PUNCHING FUNCTIONS------------------------------*/ + +/** @brief Put up to max_num nodes in nodes from the closelist. + * + * @return the number of nodes. + */ +non_null() +static uint16_t list_nodes(const Random *rng, const Client_data *list, size_t length, + uint64_t cur_time, Node_format *nodes, uint16_t max_num) +{ + if (max_num == 0) { + return 0; + } + + uint16_t count = 0; + + for (size_t i = length; i != 0; --i) { + const IPPTsPng *assoc = nullptr; + + if (!assoc_timeout(cur_time, &list[i - 1].assoc4)) { + assoc = &list[i - 1].assoc4; + } + + if (!assoc_timeout(cur_time, &list[i - 1].assoc6)) { + if (assoc == nullptr) { + assoc = &list[i - 1].assoc6; + } else if ((random_u08(rng) % 2) != 0) { + assoc = &list[i - 1].assoc6; + } + } + + if (assoc != nullptr) { + memcpy(nodes[count].public_key, list[i - 1].public_key, CRYPTO_PUBLIC_KEY_SIZE); + nodes[count].ip_port = assoc->ip_port; + ++count; + + if (count >= max_num) { + return count; + } + } + } + + return count; +} + +/** @brief Put up to max_num nodes in nodes from the random friends. + * + * Important: this function relies on the first two DHT friends *not* being real + * friends to avoid leaking information about real friends into the onion paths. + * + * @return the number of nodes. + */ +uint16_t randfriends_nodes(const DHT *dht, Node_format *nodes, uint16_t max_num) +{ + if (max_num == 0) { + return 0; + } + + uint16_t count = 0; + const uint32_t r = random_u32(dht->rng); + + assert(DHT_FAKE_FRIEND_NUMBER <= dht->num_friends); + + // Only gather nodes from the initial 2 fake friends. + for (uint32_t i = 0; i < DHT_FAKE_FRIEND_NUMBER; ++i) { + count += list_nodes(dht->rng, dht->friends_list[(i + r) % DHT_FAKE_FRIEND_NUMBER].client_list, + MAX_FRIEND_CLIENTS, dht->cur_time, + nodes + count, max_num - count); + + if (count >= max_num) { + break; + } + } + + return count; +} + +/** @brief Put up to max_num nodes in nodes from the closelist. + * + * @return the number of nodes. + */ +uint16_t closelist_nodes(const DHT *dht, Node_format *nodes, uint16_t max_num) +{ + return list_nodes(dht->rng, dht->close_clientlist, LCLIENT_LIST, dht->cur_time, nodes, max_num); +} + +/*----------------------------------------------------------------------------------*/ + +void cryptopacket_registerhandler(DHT *dht, uint8_t byte, cryptopacket_handler_cb *cb, void *object) +{ + dht->cryptopackethandlers[byte].function = cb; + dht->cryptopackethandlers[byte].object = object; +} + +non_null() +static int cryptopacket_handle(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + DHT *const dht = (DHT *)object; + + assert(packet[0] == NET_PACKET_CRYPTO); + + if (length <= CRYPTO_PUBLIC_KEY_SIZE * 2 + CRYPTO_NONCE_SIZE + 1 + CRYPTO_MAC_SIZE || + length > MAX_CRYPTO_REQUEST_SIZE + CRYPTO_MAC_SIZE) { + return 1; + } + + // Check if request is for us. + if (pk_equal(packet + 1, dht->self_public_key)) { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t data[MAX_CRYPTO_REQUEST_SIZE]; + uint8_t number; + const int len = handle_request(dht->self_public_key, dht->self_secret_key, public_key, + data, &number, packet, length); + + if (len == -1 || len == 0) { + return 1; + } + + if (dht->cryptopackethandlers[number].function == nullptr) { + return 1; + } + + return dht->cryptopackethandlers[number].function( + dht->cryptopackethandlers[number].object, source, public_key, + data, len, userdata); + } + + /* If request is not for us, try routing it. */ + const int retval = route_packet(dht, packet + 1, packet, length); + + if ((unsigned int)retval == length) { + return 0; + } + + return 1; +} + +void dht_callback_get_nodes_response(DHT *dht, dht_get_nodes_response_cb *function) +{ + dht->get_nodes_response = function; +} + +non_null(1, 2, 3) nullable(5) +static int handle_LANdiscovery(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + DHT *dht = (DHT *)object; + + if (!dht->lan_discovery_enabled) { + return 1; + } + + if (!ip_is_lan(&source->ip)) { + return 1; + } + + if (length != CRYPTO_PUBLIC_KEY_SIZE + 1) { + return 1; + } + + dht_bootstrap(dht, source, packet + 1); + return 0; +} + +/*----------------------------------------------------------------------------------*/ + +DHT *new_dht(const Logger *log, const Random *rng, const Network *ns, Mono_Time *mono_time, Networking_Core *net, + bool hole_punching_enabled, bool lan_discovery_enabled) +{ + if (net == nullptr) { + return nullptr; + } + + DHT *const dht = (DHT *)calloc(1, sizeof(DHT)); + + if (dht == nullptr) { + return nullptr; + } + + dht->ns = ns; + dht->mono_time = mono_time; + dht->cur_time = mono_time_get(mono_time); + dht->log = log; + dht->net = net; + dht->rng = rng; + + dht->hole_punching_enabled = hole_punching_enabled; + dht->lan_discovery_enabled = lan_discovery_enabled; + + dht->ping = ping_new(mono_time, rng, dht); + + if (dht->ping == nullptr) { + kill_dht(dht); + return nullptr; + } + + networking_registerhandler(dht->net, NET_PACKET_GET_NODES, &handle_getnodes, dht); + networking_registerhandler(dht->net, NET_PACKET_SEND_NODES_IPV6, &handle_sendnodes_ipv6, dht); + networking_registerhandler(dht->net, NET_PACKET_CRYPTO, &cryptopacket_handle, dht); + networking_registerhandler(dht->net, NET_PACKET_LAN_DISCOVERY, &handle_LANdiscovery, dht); + cryptopacket_registerhandler(dht, CRYPTO_PACKET_NAT_PING, &handle_NATping, dht); + +#ifdef CHECK_ANNOUNCE_NODE + networking_registerhandler(dht->net, NET_PACKET_DATA_SEARCH_RESPONSE, &handle_data_search_response, dht); +#endif + + crypto_new_keypair(rng, dht->self_public_key, dht->self_secret_key); + + dht->dht_ping_array = ping_array_new(DHT_PING_ARRAY_SIZE, PING_TIMEOUT); + + if (dht->dht_ping_array == nullptr) { + kill_dht(dht); + return nullptr; + } + + for (uint32_t i = 0; i < DHT_FAKE_FRIEND_NUMBER; ++i) { + uint8_t random_public_key_bytes[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t random_secret_key_bytes[CRYPTO_SECRET_KEY_SIZE]; + + crypto_new_keypair(rng, random_public_key_bytes, random_secret_key_bytes); + + if (dht_addfriend(dht, random_public_key_bytes, nullptr, nullptr, 0, nullptr) != 0) { + kill_dht(dht); + return nullptr; + } + } + + if (dht->num_friends != DHT_FAKE_FRIEND_NUMBER) { + LOGGER_ERROR(log, "the RNG provided seems to be broken: it generated the same keypair twice"); + kill_dht(dht); + return nullptr; + } + + return dht; +} + +void do_dht(DHT *dht) +{ + const uint64_t cur_time = mono_time_get(dht->mono_time); + + if (dht->cur_time == cur_time) { + return; + } + + dht->cur_time = cur_time; + + // Load friends/clients if first call to do_dht + if (dht->loaded_num_nodes > 0) { + dht_connect_after_load(dht); + } + + do_Close(dht); + do_dht_friends(dht); + do_NAT(dht); + ping_iterate(dht->ping); +} + +void kill_dht(DHT *dht) +{ + if (dht == nullptr) { + return; + } + + networking_registerhandler(dht->net, NET_PACKET_GET_NODES, nullptr, nullptr); + networking_registerhandler(dht->net, NET_PACKET_SEND_NODES_IPV6, nullptr, nullptr); + networking_registerhandler(dht->net, NET_PACKET_CRYPTO, nullptr, nullptr); + networking_registerhandler(dht->net, NET_PACKET_LAN_DISCOVERY, nullptr, nullptr); + cryptopacket_registerhandler(dht, CRYPTO_PACKET_NAT_PING, nullptr, nullptr); + + ping_array_kill(dht->dht_ping_array); + ping_kill(dht->ping); + free(dht->friends_list); + free(dht->loaded_nodes_list); + crypto_memzero(&dht->shared_keys_recv, sizeof(dht->shared_keys_recv)); + crypto_memzero(&dht->shared_keys_sent, sizeof(dht->shared_keys_sent)); + crypto_memzero(dht->self_secret_key, sizeof(dht->self_secret_key)); + free(dht); +} + +/* new DHT format for load/save, more robust and forward compatible */ +// TODO(irungentoo): Move this closer to Messenger. +#define DHT_STATE_COOKIE_GLOBAL 0x159000d + +#define DHT_STATE_COOKIE_TYPE 0x11ce +#define DHT_STATE_TYPE_NODES 4 + +#define MAX_SAVED_DHT_NODES (((DHT_FAKE_FRIEND_NUMBER * MAX_FRIEND_CLIENTS) + LCLIENT_LIST) * 2) + +/** Get the size of the DHT (for saving). */ +uint32_t dht_size(const DHT *dht) +{ + uint32_t numv4 = 0; + uint32_t numv6 = 0; + + for (uint32_t i = 0; i < dht->loaded_num_nodes; ++i) { + numv4 += net_family_is_ipv4(dht->loaded_nodes_list[i].ip_port.ip.family); + numv6 += net_family_is_ipv6(dht->loaded_nodes_list[i].ip_port.ip.family); + } + + for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { + numv4 += dht->close_clientlist[i].assoc4.timestamp != 0; + numv6 += dht->close_clientlist[i].assoc6.timestamp != 0; + } + + for (uint32_t i = 0; i < DHT_FAKE_FRIEND_NUMBER && i < dht->num_friends; ++i) { + const DHT_Friend *const fr = &dht->friends_list[i]; + + for (uint32_t j = 0; j < MAX_FRIEND_CLIENTS; ++j) { + numv4 += fr->client_list[j].assoc4.timestamp != 0; + numv6 += fr->client_list[j].assoc6.timestamp != 0; + } + } + + const uint32_t size32 = sizeof(uint32_t); + const uint32_t sizesubhead = size32 * 2; + + return size32 + sizesubhead + packed_node_size(net_family_ipv4()) * numv4 + packed_node_size(net_family_ipv6()) * numv6; +} + +/** Save the DHT in data where data is an array of size `dht_size()`. */ +void dht_save(const DHT *dht, uint8_t *data) +{ + host_to_lendian_bytes32(data, DHT_STATE_COOKIE_GLOBAL); + data += sizeof(uint32_t); + + uint8_t *const old_data = data; + + /* get right offset. we write the actual header later. */ + data = state_write_section_header(data, DHT_STATE_COOKIE_TYPE, 0, 0); + + Node_format *clients = (Node_format *)calloc(MAX_SAVED_DHT_NODES, sizeof(Node_format)); + + if (clients == nullptr) { + LOGGER_ERROR(dht->log, "could not allocate %u nodes", MAX_SAVED_DHT_NODES); + return; + } + + uint32_t num = 0; + + if (dht->loaded_num_nodes > 0) { + memcpy(clients, dht->loaded_nodes_list, sizeof(Node_format) * dht->loaded_num_nodes); + num += dht->loaded_num_nodes; + } + + for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { + if (dht->close_clientlist[i].assoc4.timestamp != 0) { + memcpy(clients[num].public_key, dht->close_clientlist[i].public_key, CRYPTO_PUBLIC_KEY_SIZE); + clients[num].ip_port = dht->close_clientlist[i].assoc4.ip_port; + ++num; + } + + if (dht->close_clientlist[i].assoc6.timestamp != 0) { + memcpy(clients[num].public_key, dht->close_clientlist[i].public_key, CRYPTO_PUBLIC_KEY_SIZE); + clients[num].ip_port = dht->close_clientlist[i].assoc6.ip_port; + ++num; + } + } + + for (uint32_t i = 0; i < DHT_FAKE_FRIEND_NUMBER && i < dht->num_friends; ++i) { + const DHT_Friend *const fr = &dht->friends_list[i]; + + for (uint32_t j = 0; j < MAX_FRIEND_CLIENTS; ++j) { + if (fr->client_list[j].assoc4.timestamp != 0) { + memcpy(clients[num].public_key, fr->client_list[j].public_key, CRYPTO_PUBLIC_KEY_SIZE); + clients[num].ip_port = fr->client_list[j].assoc4.ip_port; + ++num; + } + + if (fr->client_list[j].assoc6.timestamp != 0) { + memcpy(clients[num].public_key, fr->client_list[j].public_key, CRYPTO_PUBLIC_KEY_SIZE); + clients[num].ip_port = fr->client_list[j].assoc6.ip_port; + ++num; + } + } + } + + state_write_section_header(old_data, DHT_STATE_COOKIE_TYPE, pack_nodes(dht->log, data, sizeof(Node_format) * num, + clients, num), DHT_STATE_TYPE_NODES); + + free(clients); +} + +/** Bootstrap from this number of nodes every time `dht_connect_after_load()` is called */ +#define SAVE_BOOTSTAP_FREQUENCY 8 + +/** @brief Start sending packets after DHT loaded_friends_list and loaded_clients_list are set. + * + * @retval 0 if successful + * @retval -1 otherwise + */ +int dht_connect_after_load(DHT *dht) +{ + if (dht == nullptr) { + return -1; + } + + if (dht->loaded_nodes_list == nullptr) { + return -1; + } + + /* DHT is connected, stop. */ + if (dht_non_lan_connected(dht)) { + free(dht->loaded_nodes_list); + dht->loaded_nodes_list = nullptr; + dht->loaded_num_nodes = 0; + return 0; + } + + for (uint32_t i = 0; i < dht->loaded_num_nodes && i < SAVE_BOOTSTAP_FREQUENCY; ++i) { + const unsigned int index = dht->loaded_nodes_index % dht->loaded_num_nodes; + dht_bootstrap(dht, &dht->loaded_nodes_list[index].ip_port, dht->loaded_nodes_list[index].public_key); + ++dht->loaded_nodes_index; + } + + return 0; +} + +non_null() +static State_Load_Status dht_load_state_callback(void *outer, const uint8_t *data, uint32_t length, uint16_t type) +{ + DHT *dht = (DHT *)outer; + + switch (type) { + case DHT_STATE_TYPE_NODES: { + if (length == 0) { + break; + } + + free(dht->loaded_nodes_list); + // Copy to loaded_clients_list + dht->loaded_nodes_list = (Node_format *)calloc(MAX_SAVED_DHT_NODES, sizeof(Node_format)); + + if (dht->loaded_nodes_list == nullptr) { + LOGGER_ERROR(dht->log, "could not allocate %u nodes", MAX_SAVED_DHT_NODES); + dht->loaded_num_nodes = 0; + break; + } + + const int num = unpack_nodes(dht->loaded_nodes_list, MAX_SAVED_DHT_NODES, nullptr, data, length, false); + + if (num > 0) { + dht->loaded_num_nodes = num; + } else { + dht->loaded_num_nodes = 0; + } + + break; + } + + default: { + LOGGER_ERROR(dht->log, "Load state (DHT): contains unrecognized part (len %u, type %u)", + length, type); + break; + } + } + + return STATE_LOAD_STATUS_CONTINUE; +} + +/** @brief Load the DHT from data of size size. + * + * @retval -1 if failure. + * @retval 0 if success. + */ +int dht_load(DHT *dht, const uint8_t *data, uint32_t length) +{ + const uint32_t cookie_len = sizeof(uint32_t); + + if (length > cookie_len) { + uint32_t data32; + lendian_bytes_to_host32(&data32, data); + + if (data32 == DHT_STATE_COOKIE_GLOBAL) { + return state_load(dht->log, dht_load_state_callback, dht, data + cookie_len, + length - cookie_len, DHT_STATE_COOKIE_TYPE); + } + } + + return -1; +} + +/** + * @retval false if we are not connected to the DHT. + * @retval true if we are. + */ +bool dht_isconnected(const DHT *dht) +{ + for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { + const Client_data *const client = &dht->close_clientlist[i]; + + if (!assoc_timeout(dht->cur_time, &client->assoc4) || + !assoc_timeout(dht->cur_time, &client->assoc6)) { + return true; + } + } + + return false; +} + +/** + * @retval false if we are not connected or only connected to lan peers with the DHT. + * @retval true if we are. + */ +bool dht_non_lan_connected(const DHT *dht) +{ + for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { + const Client_data *const client = &dht->close_clientlist[i]; + + if (!assoc_timeout(dht->cur_time, &client->assoc4) + && !ip_is_lan(&client->assoc4.ip_port.ip)) { + return true; + } + + if (!assoc_timeout(dht->cur_time, &client->assoc6) + && !ip_is_lan(&client->assoc6.ip_port.ip)) { + return true; + } + } + + return false; +} + +/** @brief Copies our own ip_port structure to `dest`. + * + * WAN addresses take priority over LAN addresses. + * + * This function will zero the `dest` buffer before use. + * + * @retval 0 if our ip port can't be found (this usually means we're not connected to the DHT). + * @retval 1 if IP is a WAN address. + * @retval 2 if IP is a LAN address. + */ +unsigned int ipport_self_copy(const DHT *dht, IP_Port *dest) +{ + ipport_reset(dest); + + bool is_lan = false; + + for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { + const Client_data *client = dht_get_close_client(dht, i); + const IP_Port *ip_port4 = &client->assoc4.ret_ip_port; + + if (client->assoc4.ret_ip_self && ipport_isset(ip_port4)) { + ipport_copy(dest, ip_port4); + is_lan = ip_is_lan(&dest->ip); + + if (!is_lan) { + break; + } + } + + const IP_Port *ip_port6 = &client->assoc6.ret_ip_port; + + if (client->assoc6.ret_ip_self && ipport_isset(ip_port6)) { + ipport_copy(dest, ip_port6); + is_lan = ip_is_lan(&dest->ip); + + if (!is_lan) { + break; + } + } + } + + if (!ipport_isset(dest)) { + return 0; + } + + if (is_lan) { + return 2; + } + + return 1; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/LAN_discovery.h b/local_pod_repo/toxcore/toxcore/toxcore/LAN_discovery.h new file mode 100644 index 0000000..85ea947 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/LAN_discovery.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * LAN discovery implementation. + */ +#ifndef C_TOXCORE_TOXCORE_LAN_DISCOVERY_H +#define C_TOXCORE_TOXCORE_LAN_DISCOVERY_H + +#include "network.h" + +/** + * Interval in seconds between LAN discovery packet sending. + */ +#define LAN_DISCOVERY_INTERVAL 10 + +typedef struct Broadcast_Info Broadcast_Info; + +/** + * Send a LAN discovery pcaket to the broadcast address with port port. + * + * @return true on success, false on failure. + */ +non_null() +bool lan_discovery_send(const Networking_Core *net, const Broadcast_Info *broadcast, const uint8_t *dht_pk, uint16_t port); + +/** + * Discovers broadcast devices and IP addresses. + */ +non_null() +Broadcast_Info *lan_discovery_init(const Network *ns); + +/** + * Free all resources associated with the broadcast info. + */ +nullable(1) +void lan_discovery_kill(Broadcast_Info *broadcast); + +/** + * Is IP a local ip or not. + */ +non_null() +bool ip_is_local(const IP *ip); + +/** + * Checks if a given IP isn't routable. + * + * @return true if ip is a LAN ip, false if it is not. + */ +non_null() +bool ip_is_lan(const IP *ip); + +#endif // C_TOXCORE_TOXCORE_LAN_DISCOVERY_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/LAN_discovery.m b/local_pod_repo/toxcore/toxcore/toxcore/LAN_discovery.m new file mode 100644 index 0000000..82c02b5 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/LAN_discovery.m @@ -0,0 +1,386 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * LAN discovery implementation. + */ +#include "LAN_discovery.h" + +#include +#include + +#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +// The mingw32/64 Windows library warns about including winsock2.h after +// windows.h even though with the above it's a valid thing to do. So, to make +// mingw32 headers happy, we include winsock2.h first. +#include + +#include +#include + +#include +#endif + +#if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) +#include +#include +#include +#include +#include +#endif + +#ifdef __linux__ +#include +#endif + +#if defined(__FreeBSD__) || defined(__DragonFly__) +#include +#endif + +#include "ccompat.h" +#include "crypto_core.h" +#include "util.h" + +#define MAX_INTERFACES 16 + + +struct Broadcast_Info { + uint32_t count; + IP ips[MAX_INTERFACES]; +}; + +#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) + +non_null() +static Broadcast_Info *fetch_broadcast_info(const Network *ns) +{ + Broadcast_Info *broadcast = (Broadcast_Info *)calloc(1, sizeof(Broadcast_Info)); + + if (broadcast == nullptr) { + return nullptr; + } + + IP_ADAPTER_INFO *pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO)); + unsigned long ulOutBufLen = sizeof(IP_ADAPTER_INFO); + + if (pAdapterInfo == nullptr) { + free(broadcast); + return nullptr; + } + + if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) { + free(pAdapterInfo); + pAdapterInfo = (IP_ADAPTER_INFO *)malloc(ulOutBufLen); + + if (pAdapterInfo == nullptr) { + free(broadcast); + return nullptr; + } + } + + const int ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen); + + if (ret == NO_ERROR) { + IP_ADAPTER_INFO *pAdapter = pAdapterInfo; + + while (pAdapter != nullptr) { + IP gateway = {0}; + IP subnet_mask = {0}; + + if (addr_parse_ip(pAdapter->IpAddressList.IpMask.String, &subnet_mask) + && addr_parse_ip(pAdapter->GatewayList.IpAddress.String, &gateway)) { + if (net_family_is_ipv4(gateway.family) && net_family_is_ipv4(subnet_mask.family)) { + IP *ip = &broadcast->ips[broadcast->count]; + ip->family = net_family_ipv4(); + const uint32_t gateway_ip = net_ntohl(gateway.ip.v4.uint32); + const uint32_t subnet_ip = net_ntohl(subnet_mask.ip.v4.uint32); + const uint32_t broadcast_ip = gateway_ip + ~subnet_ip - 1; + ip->ip.v4.uint32 = net_htonl(broadcast_ip); + ++broadcast->count; + + if (broadcast->count >= MAX_INTERFACES) { + break; + } + } + } + + pAdapter = pAdapter->Next; + } + } + + if (pAdapterInfo != nullptr) { + free(pAdapterInfo); + } + + return broadcast; +} + +#elif !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && (defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__)) + +non_null() +static Broadcast_Info *fetch_broadcast_info(const Network *ns) +{ + Broadcast_Info *broadcast = (Broadcast_Info *)calloc(1, sizeof(Broadcast_Info)); + + if (broadcast == nullptr) { + return nullptr; + } + + /* Not sure how many platforms this will run on, + * so it's wrapped in `__linux__` for now. + * Definitely won't work like this on Windows... + */ + const Socket sock = net_socket(ns, net_family_ipv4(), TOX_SOCK_STREAM, 0); + + if (!sock_valid(sock)) { + free(broadcast); + return nullptr; + } + + /* Configure ifconf for the ioctl call. */ + struct ifreq i_faces[MAX_INTERFACES]; + memset(i_faces, 0, sizeof(struct ifreq) * MAX_INTERFACES); + + struct ifconf ifc; + ifc.ifc_buf = (char *)i_faces; + ifc.ifc_len = sizeof(i_faces); + + if (ioctl(sock.sock, SIOCGIFCONF, &ifc) < 0) { + kill_sock(ns, sock); + free(broadcast); + return nullptr; + } + + /* `ifc.ifc_len` is set by the `ioctl()` to the actual length used. + * On usage of the complete array the call should be repeated with + * a larger array, not done (640kB and 16 interfaces shall be + * enough, for everybody!) + */ + const int n = ifc.ifc_len / sizeof(struct ifreq); + + for (int i = 0; i < n; ++i) { + /* there are interfaces with are incapable of broadcast */ + if (ioctl(sock.sock, SIOCGIFBRDADDR, &i_faces[i]) < 0) { + continue; + } + + /* moot check: only AF_INET returned (backwards compat.) */ + if (i_faces[i].ifr_broadaddr.sa_family != AF_INET) { + continue; + } + + const struct sockaddr_in *sock4 = (const struct sockaddr_in *)(void *)&i_faces[i].ifr_broadaddr; + + if (broadcast->count >= MAX_INTERFACES) { + break; + } + + IP *ip = &broadcast->ips[broadcast->count]; + ip->family = net_family_ipv4(); + ip->ip.v4.uint32 = sock4->sin_addr.s_addr; + + if (ip->ip.v4.uint32 == 0) { + continue; + } + + ++broadcast->count; + } + + kill_sock(ns, sock); + + return broadcast; +} + +#else // TODO(irungentoo): Other platforms? + +non_null() +static Broadcast_Info *fetch_broadcast_info(const Network *ns) +{ + return (Broadcast_Info *)calloc(1, sizeof(Broadcast_Info)); +} + +#endif + +/** @brief Send packet to all IPv4 broadcast addresses + * + * @retval true if sent to at least one broadcast target. + * @retval false on failure to find any valid broadcast target. + */ +non_null() +static bool send_broadcasts(const Networking_Core *net, const Broadcast_Info *broadcast, uint16_t port, + const uint8_t *data, uint16_t length) +{ + if (broadcast->count == 0) { + return false; + } + + for (uint32_t i = 0; i < broadcast->count; ++i) { + IP_Port ip_port; + ip_port.ip = broadcast->ips[i]; + ip_port.port = port; + sendpacket(net, &ip_port, data, length); + } + + return true; +} + +/** Return the broadcast ip. */ +static IP broadcast_ip(Family family_socket, Family family_broadcast) +{ + IP ip; + ip_reset(&ip); + + if (net_family_is_ipv6(family_socket)) { + if (net_family_is_ipv6(family_broadcast)) { + ip.family = net_family_ipv6(); + /* `FF02::1` is - according to RFC 4291 - multicast all-nodes link-local */ + /* `FE80::*:` MUST be exact, for that we would need to look over all + * interfaces and check in which status they are */ + ip.ip.v6.uint8[ 0] = 0xFF; + ip.ip.v6.uint8[ 1] = 0x02; + ip.ip.v6.uint8[15] = 0x01; + } else if (net_family_is_ipv4(family_broadcast)) { + ip.family = net_family_ipv6(); + ip.ip.v6 = ip6_broadcast; + } + } else if (net_family_is_ipv4(family_socket) && net_family_is_ipv4(family_broadcast)) { + ip.family = net_family_ipv4(); + ip.ip.v4 = ip4_broadcast; + } + + return ip; +} + +non_null() +static bool ip4_is_local(const IP4 *ip4) +{ + /* Loopback. */ + return ip4->uint8[0] == 127; +} + +/** + * Is IP a local ip or not. + */ +bool ip_is_local(const IP *ip) +{ + if (net_family_is_ipv4(ip->family)) { + return ip4_is_local(&ip->ip.v4); + } + + /* embedded IPv4-in-IPv6 */ + if (ipv6_ipv4_in_v6(&ip->ip.v6)) { + IP4 ip4; + ip4.uint32 = ip->ip.v6.uint32[3]; + return ip4_is_local(&ip4); + } + + /* localhost in IPv6 (::1) */ + return ip->ip.v6.uint64[0] == 0 && ip->ip.v6.uint32[2] == 0 && ip->ip.v6.uint32[3] == net_htonl(1); +} + +non_null() +static bool ip4_is_lan(const IP4 *ip4) +{ + /* 10.0.0.0 to 10.255.255.255 range. */ + if (ip4->uint8[0] == 10) { + return true; + } + + /* 172.16.0.0 to 172.31.255.255 range. */ + if (ip4->uint8[0] == 172 && ip4->uint8[1] >= 16 && ip4->uint8[1] <= 31) { + return true; + } + + /* 192.168.0.0 to 192.168.255.255 range. */ + if (ip4->uint8[0] == 192 && ip4->uint8[1] == 168) { + return true; + } + + /* 169.254.1.0 to 169.254.254.255 range. */ + if (ip4->uint8[0] == 169 && ip4->uint8[1] == 254 && ip4->uint8[2] != 0 + && ip4->uint8[2] != 255) { + return true; + } + + /* RFC 6598: 100.64.0.0 to 100.127.255.255 (100.64.0.0/10) + * (shared address space to stack another layer of NAT) */ + return (ip4->uint8[0] == 100) && ((ip4->uint8[1] & 0xC0) == 0x40); +} + +bool ip_is_lan(const IP *ip) +{ + if (ip_is_local(ip)) { + return true; + } + + if (net_family_is_ipv4(ip->family)) { + return ip4_is_lan(&ip->ip.v4); + } + + if (net_family_is_ipv6(ip->family)) { + /* autogenerated for each interface: `FE80::*` (up to `FEBF::*`) + * `FF02::1` is - according to RFC 4291 - multicast all-nodes link-local */ + if (((ip->ip.v6.uint8[0] == 0xFF) && (ip->ip.v6.uint8[1] < 3) && (ip->ip.v6.uint8[15] == 1)) || + ((ip->ip.v6.uint8[0] == 0xFE) && ((ip->ip.v6.uint8[1] & 0xC0) == 0x80))) { + return true; + } + + /* embedded IPv4-in-IPv6 */ + if (ipv6_ipv4_in_v6(&ip->ip.v6)) { + IP4 ip4; + ip4.uint32 = ip->ip.v6.uint32[3]; + return ip4_is_lan(&ip4); + } + } + + return false; +} + + +bool lan_discovery_send(const Networking_Core *net, const Broadcast_Info *broadcast, const uint8_t *dht_pk, uint16_t port) +{ + if (broadcast == nullptr) { + return false; + } + + uint8_t data[CRYPTO_PUBLIC_KEY_SIZE + 1]; + data[0] = NET_PACKET_LAN_DISCOVERY; + pk_copy(data + 1, dht_pk); + + send_broadcasts(net, broadcast, port, data, 1 + CRYPTO_PUBLIC_KEY_SIZE); + + bool res = false; + IP_Port ip_port; + ip_port.port = port; + + /* IPv6 multicast */ + if (net_family_is_ipv6(net_family(net))) { + ip_port.ip = broadcast_ip(net_family_ipv6(), net_family_ipv6()); + + if (ip_isset(&ip_port.ip) && sendpacket(net, &ip_port, data, 1 + CRYPTO_PUBLIC_KEY_SIZE) > 0) { + res = true; + } + } + + /* IPv4 broadcast (has to be IPv4-in-IPv6 mapping if socket is IPv6 */ + ip_port.ip = broadcast_ip(net_family(net), net_family_ipv4()); + + if (ip_isset(&ip_port.ip) && sendpacket(net, &ip_port, data, 1 + CRYPTO_PUBLIC_KEY_SIZE) > 0) { + res = true; + } + + return res; +} + + +Broadcast_Info *lan_discovery_init(const Network *ns) +{ + return fetch_broadcast_info(ns); +} + +void lan_discovery_kill(Broadcast_Info *broadcast) +{ + free(broadcast); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/Makefile.inc b/local_pod_repo/toxcore/toxcore/toxcore/Makefile.inc new file mode 100644 index 0000000..239154f --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/Makefile.inc @@ -0,0 +1,132 @@ +lib_LTLIBRARIES += libtoxcore.la + +libtoxcore_la_include_HEADERS = \ + ../toxcore/tox.h + +libtoxcore_la_includedir = $(includedir)/tox + +libtoxcore_la_SOURCES = ../third_party/cmp/cmp.c \ + ../third_party/cmp/cmp.h \ + ../toxcore/attributes.h \ + ../toxcore/bin_pack.c \ + ../toxcore/bin_pack.h \ + ../toxcore/bin_unpack.c \ + ../toxcore/bin_unpack.h \ + ../toxcore/ccompat.c \ + ../toxcore/ccompat.h \ + ../toxcore/events/conference_connected.c \ + ../toxcore/events/conference_invite.c \ + ../toxcore/events/conference_message.c \ + ../toxcore/events/conference_peer_list_changed.c \ + ../toxcore/events/conference_peer_name.c \ + ../toxcore/events/conference_title.c \ + ../toxcore/events/file_chunk_request.c \ + ../toxcore/events/file_recv.c \ + ../toxcore/events/file_recv_chunk.c \ + ../toxcore/events/file_recv_control.c \ + ../toxcore/events/friend_connection_status.c \ + ../toxcore/events/friend_lossless_packet.c \ + ../toxcore/events/friend_lossy_packet.c \ + ../toxcore/events/friend_message.c \ + ../toxcore/events/friend_name.c \ + ../toxcore/events/friend_read_receipt.c \ + ../toxcore/events/friend_request.c \ + ../toxcore/events/friend_status.c \ + ../toxcore/events/friend_status_message.c \ + ../toxcore/events/friend_typing.c \ + ../toxcore/events/events_alloc.c \ + ../toxcore/events/events_alloc.h \ + ../toxcore/events/self_connection_status.c \ + ../toxcore/DHT.h \ + ../toxcore/DHT.c \ + ../toxcore/mono_time.h \ + ../toxcore/mono_time.c \ + ../toxcore/network.h \ + ../toxcore/network.c \ + ../toxcore/crypto_core.h \ + ../toxcore/crypto_core.c \ + ../toxcore/timed_auth.h \ + ../toxcore/timed_auth.c \ + ../toxcore/ping_array.h \ + ../toxcore/ping_array.c \ + ../toxcore/net_crypto.h \ + ../toxcore/net_crypto.c \ + ../toxcore/friend_requests.h \ + ../toxcore/friend_requests.c \ + ../toxcore/LAN_discovery.h \ + ../toxcore/LAN_discovery.c \ + ../toxcore/friend_connection.h \ + ../toxcore/friend_connection.c \ + ../toxcore/Messenger.h \ + ../toxcore/Messenger.c \ + ../toxcore/ping.h \ + ../toxcore/ping.c \ + ../toxcore/state.h \ + ../toxcore/state.c \ + ../toxcore/tox.h \ + ../toxcore/tox.c \ + ../toxcore/tox_dispatch.h \ + ../toxcore/tox_dispatch.c \ + ../toxcore/tox_events.h \ + ../toxcore/tox_events.c \ + ../toxcore/tox_unpack.h \ + ../toxcore/tox_unpack.c \ + ../toxcore/tox_private.c \ + ../toxcore/tox_private.h \ + ../toxcore/tox_struct.h \ + ../toxcore/tox_api.c \ + ../toxcore/util.h \ + ../toxcore/util.c \ + ../toxcore/group.h \ + ../toxcore/group.c \ + ../toxcore/onion.h \ + ../toxcore/onion.c \ + ../toxcore/logger.h \ + ../toxcore/logger.c \ + ../toxcore/onion_announce.h \ + ../toxcore/onion_announce.c \ + ../toxcore/onion_client.h \ + ../toxcore/onion_client.c \ + ../toxcore/announce.h \ + ../toxcore/announce.c \ + ../toxcore/forwarding.h \ + ../toxcore/forwarding.c \ + ../toxcore/TCP_client.h \ + ../toxcore/TCP_client.c \ + ../toxcore/TCP_common.h \ + ../toxcore/TCP_common.c \ + ../toxcore/TCP_server.h \ + ../toxcore/TCP_server.c \ + ../toxcore/TCP_connection.h \ + ../toxcore/TCP_connection.c \ + ../toxcore/list.c \ + ../toxcore/list.h + +libtoxcore_la_CFLAGS = -I$(top_srcdir) \ + -I$(top_srcdir)/toxcore \ + $(LIBSODIUM_CFLAGS) \ + $(NACL_CFLAGS) \ + $(MSGPACK_CFLAGS) \ + $(PTHREAD_CFLAGS) \ + -DCMP_NO_FLOAT=1 + +libtoxcore_la_LDFLAGS = $(LT_LDFLAGS) \ + $(EXTRA_LT_LDFLAGS) \ + $(LIBSODIUM_LDFLAGS) \ + $(NACL_LDFLAGS) \ + $(MSGPACK_LDFLAGS) \ + $(MATH_LDFLAGS) \ + $(RT_LIBS) \ + $(WINSOCK2_LIBS) + +libtoxcore_la_LIBADD = $(LIBSODIUM_LIBS) \ + $(NACL_OBJECTS) \ + $(NACL_LIBS) \ + $(MSGPACK_LIBS) \ + $(PTHREAD_LIBS) + +if SET_SO_VERSION + +EXTRA_libtoxcore_la_DEPENDENCIES = ../so.version + +endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/Messenger.h b/local_pod_repo/toxcore/toxcore/toxcore/Messenger.h new file mode 100644 index 0000000..43bf5a5 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/Messenger.h @@ -0,0 +1,869 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * An implementation of a simple text chat only messenger on the tox network + * core. + */ +#ifndef C_TOXCORE_TOXCORE_MESSENGER_H +#define C_TOXCORE_TOXCORE_MESSENGER_H + +#include "TCP_server.h" +#include "announce.h" +#include "forwarding.h" +#include "friend_connection.h" +#include "friend_requests.h" +#include "logger.h" +#include "net_crypto.h" +#include "state.h" + +#define MAX_NAME_LENGTH 128 +/* TODO(irungentoo): this must depend on other variable. */ +#define MAX_STATUSMESSAGE_LENGTH 1007 +/* Used for TCP relays in Messenger struct (may need to be `% 2 == 0`)*/ +#define NUM_SAVED_TCP_RELAYS 8 +/* This cannot be bigger than 256 */ +#define MAX_CONCURRENT_FILE_PIPES 256 + + +#define FRIEND_ADDRESS_SIZE (CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t) + sizeof(uint16_t)) + +typedef enum Message_Type { + MESSAGE_NORMAL = 0, + MESSAGE_ACTION = 1, + MESSAGE_HIGH_LEVEL_ACK = 2, +} Message_Type; + +typedef struct Messenger Messenger; + +// Returns the size of the data +typedef uint32_t m_state_size_cb(const Messenger *m); + +// Returns the new pointer to data +typedef uint8_t *m_state_save_cb(const Messenger *m, uint8_t *data); + +// Returns if there were any erros during loading +typedef State_Load_Status m_state_load_cb(Messenger *m, const uint8_t *data, uint32_t length); + +typedef struct Messenger_State_Plugin { + State_Type type; + m_state_size_cb *size; + m_state_save_cb *save; + m_state_load_cb *load; +} Messenger_State_Plugin; + +typedef struct Messenger_Options { + bool ipv6enabled; + bool udp_disabled; + TCP_Proxy_Info proxy_info; + uint16_t port_range[2]; + uint16_t tcp_server_port; + + bool hole_punching_enabled; + bool local_discovery_enabled; + bool dht_announcements_enabled; + + logger_cb *log_callback; + void *log_context; + void *log_user_data; + + Messenger_State_Plugin *state_plugins; + uint8_t state_plugins_length; +} Messenger_Options; + +/* this means no special capabilities, in other words clients that are older + * and did not implement this feature yet + */ +#define TOX_CAPABILITY_BASIC 0 +/* ATTENTION: if you are adding new flags in your fork or toxcore, + * or in c-toxcore master, + * please coordinate with us first! + * thank you, the Tox Devs. + */ +#define TOX_CAPABILITY_CAPABILITIES ((uint64_t)1) << 0 +#define TOX_CAPABILITY_MSGV2 ((uint64_t)1) << 1 +#define TOX_CAPABILITY_TOXAV_H264 ((uint64_t)1) << 2 +#define TOX_CAPABILITY_MSGV3 ((uint64_t)1) << 3 +/* add new flags/bits here */ +/* if the TOX_CAPABILITY_NEXT_IMPLEMENTATION flag is set it means + * we are using a different system for indicating capabilities now, + * and TOX_CAPABILITIES_* should be ignored and just the new (not yet known) + * system should be used + */ +#define TOX_CAPABILITY_NEXT_IMPLEMENTATION ((uint64_t)1) << 63 +/* hardcoded capabilities of this version/branch of toxcore */ +#define TOX_CAPABILITIES_CURRENT (uint64_t)(TOX_CAPABILITY_CAPABILITIES | TOX_CAPABILITY_MSGV3) +/* size of the FLAGS in bytes */ +#define TOX_CAPABILITIES_SIZE sizeof(uint64_t) + +struct Receipts { + uint32_t packet_num; + uint32_t msg_id; + struct Receipts *next; +}; + +/** Status definitions. */ +typedef enum Friend_Status { + NOFRIEND, + FRIEND_ADDED, + FRIEND_REQUESTED, + FRIEND_CONFIRMED, + FRIEND_ONLINE, +} Friend_Status; + +/** @brief Errors for m_addfriend + * + * FAERR - Friend Add Error + */ +typedef enum Friend_Add_Error { + FAERR_TOOLONG = -1, + FAERR_NOMESSAGE = -2, + FAERR_OWNKEY = -3, + FAERR_ALREADYSENT = -4, + FAERR_BADCHECKSUM = -6, + FAERR_SETNEWNOSPAM = -7, + FAERR_NOMEM = -8, +} Friend_Add_Error; + + +/** Default start timeout in seconds between friend requests. */ +#define FRIENDREQUEST_TIMEOUT 5 + +typedef enum Connection_Status { + CONNECTION_NONE, + CONNECTION_TCP, + CONNECTION_UDP, +} Connection_Status; + +/** + * Represents userstatuses someone can have. + */ +typedef enum Userstatus { + USERSTATUS_NONE, + USERSTATUS_AWAY, + USERSTATUS_BUSY, + USERSTATUS_INVALID, +} Userstatus; + +#define FILE_ID_LENGTH 32 + +struct File_Transfers { + uint64_t size; + uint64_t transferred; + uint8_t status; /* 0 == no transfer, 1 = not accepted, 3 = transferring, 4 = broken, 5 = finished */ + uint8_t paused; /* 0: not paused, 1 = paused by us, 2 = paused by other, 3 = paused by both. */ + uint32_t last_packet_number; /* number of the last packet sent. */ + uint64_t requested; /* total data requested by the request chunk callback */ + uint8_t id[FILE_ID_LENGTH]; +}; +typedef enum Filestatus { + FILESTATUS_NONE, + FILESTATUS_NOT_ACCEPTED, + FILESTATUS_TRANSFERRING, + // FILESTATUS_BROKEN, + FILESTATUS_FINISHED, +} Filestatus; + +typedef enum File_Pause { + FILE_PAUSE_NOT, + FILE_PAUSE_US, + FILE_PAUSE_OTHER, + FILE_PAUSE_BOTH, +} File_Pause; + +typedef enum Filecontrol { + FILECONTROL_ACCEPT, + FILECONTROL_PAUSE, + FILECONTROL_KILL, + FILECONTROL_SEEK, +} Filecontrol; + +typedef enum Filekind { + FILEKIND_DATA, + FILEKIND_AVATAR, +} Filekind; + + +typedef void m_self_connection_status_cb(Messenger *m, Onion_Connection_Status connection_status, void *user_data); +typedef void m_friend_status_cb(Messenger *m, uint32_t friend_number, unsigned int status, void *user_data); +typedef void m_friend_connection_status_cb(Messenger *m, uint32_t friend_number, unsigned int connection_status, + void *user_data); +typedef void m_friend_message_cb(Messenger *m, uint32_t friend_number, unsigned int message_type, + const uint8_t *message, size_t length, void *user_data); +typedef void m_file_recv_control_cb(Messenger *m, uint32_t friend_number, uint32_t file_number, unsigned int control, + void *user_data); +typedef void m_friend_request_cb(Messenger *m, const uint8_t *public_key, const uint8_t *message, size_t length, + void *user_data); +typedef void m_friend_name_cb(Messenger *m, uint32_t friend_number, const uint8_t *name, size_t length, + void *user_data); +typedef void m_friend_status_message_cb(Messenger *m, uint32_t friend_number, const uint8_t *message, size_t length, + void *user_data); +typedef void m_friend_typing_cb(Messenger *m, uint32_t friend_number, bool is_typing, void *user_data); +typedef void m_friend_read_receipt_cb(Messenger *m, uint32_t friend_number, uint32_t message_id, void *user_data); +typedef void m_file_recv_cb(Messenger *m, uint32_t friend_number, uint32_t file_number, uint32_t kind, + uint64_t file_size, const uint8_t *filename, size_t filename_length, void *user_data); +typedef void m_file_chunk_request_cb(Messenger *m, uint32_t friend_number, uint32_t file_number, uint64_t position, + size_t length, void *user_data); +typedef void m_file_recv_chunk_cb(Messenger *m, uint32_t friend_number, uint32_t file_number, uint64_t position, + const uint8_t *data, size_t length, void *user_data); +typedef void m_friend_lossy_packet_cb(Messenger *m, uint32_t friend_number, uint8_t packet_id, const uint8_t *data, + size_t length, void *user_data); +typedef void m_friend_lossless_packet_cb(Messenger *m, uint32_t friend_number, uint8_t packet_id, const uint8_t *data, + size_t length, void *user_data); +typedef void m_friend_connectionstatuschange_internal_cb(Messenger *m, uint32_t friend_number, + uint8_t connection_status, void *user_data); +typedef void m_conference_invite_cb(Messenger *m, uint32_t friend_number, const uint8_t *cookie, uint16_t length, + void *user_data); +typedef void m_msi_packet_cb(Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, + void *user_data); +typedef int m_lossy_rtp_packet_cb(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t len, void *object); + +typedef struct RTP_Packet_Handler { + m_lossy_rtp_packet_cb *function; + void *object; +} RTP_Packet_Handler; + +typedef struct Friend { + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + int friendcon_id; + + uint64_t friendrequest_lastsent; // Time at which the last friend request was sent. + uint32_t friendrequest_timeout; // The timeout between successful friendrequest sending attempts. + uint8_t status; // 0 if no friend, 1 if added, 2 if friend request sent, 3 if confirmed friend, 4 if online. + uint8_t info[MAX_FRIEND_REQUEST_DATA_SIZE]; // the data that is sent during the friend requests we do. + uint8_t name[MAX_NAME_LENGTH]; + uint16_t name_length; + bool name_sent; // false if we didn't send our name to this friend, true if we have. + uint8_t statusmessage[MAX_STATUSMESSAGE_LENGTH]; + uint16_t statusmessage_length; + bool statusmessage_sent; + Userstatus userstatus; + bool userstatus_sent; + bool user_istyping; + bool user_istyping_sent; + bool is_typing; + uint16_t info_size; // Length of the info. + uint32_t message_id; // a semi-unique id used in read receipts. + uint32_t friendrequest_nospam; // The nospam number used in the friend request. + uint64_t last_seen_time; + Connection_Status last_connection_udp_tcp; + struct File_Transfers file_sending[MAX_CONCURRENT_FILE_PIPES]; + uint32_t num_sending_files; + struct File_Transfers file_receiving[MAX_CONCURRENT_FILE_PIPES]; + + RTP_Packet_Handler lossy_rtp_packethandlers[PACKET_ID_RANGE_LOSSY_AV_SIZE]; + + struct Receipts *receipts_start; + struct Receipts *receipts_end; + uint64_t toxcore_capabilities; +} Friend; + +struct Messenger { + Logger *log; + Mono_Time *mono_time; + const Random *rng; + const Network *ns; + + Networking_Core *net; + Net_Crypto *net_crypto; + DHT *dht; + + Forwarding *forwarding; + Announcements *announce; + + Onion *onion; + Onion_Announce *onion_a; + Onion_Client *onion_c; + + Friend_Connections *fr_c; + + TCP_Server *tcp_server; + Friend_Requests *fr; + uint8_t name[MAX_NAME_LENGTH]; + uint16_t name_length; + + uint8_t statusmessage[MAX_STATUSMESSAGE_LENGTH]; + uint16_t statusmessage_length; + + Userstatus userstatus; + + Friend *friendlist; + uint32_t numfriends; + + uint64_t lastdump; + uint8_t is_receiving_file; + + bool has_added_relays; // If the first connection has occurred in do_messenger + + uint16_t num_loaded_relays; + Node_format loaded_relays[NUM_SAVED_TCP_RELAYS]; // Relays loaded from config + + m_friend_request_cb *friend_request; + m_friend_message_cb *friend_message; + m_friend_name_cb *friend_namechange; + m_friend_status_message_cb *friend_statusmessagechange; + m_friend_status_cb *friend_userstatuschange; + m_friend_typing_cb *friend_typingchange; + m_friend_read_receipt_cb *read_receipt; + m_friend_connection_status_cb *friend_connectionstatuschange; + m_friend_connectionstatuschange_internal_cb *friend_connectionstatuschange_internal; + void *friend_connectionstatuschange_internal_userdata; + + struct Group_Chats *conferences_object; /* Set by new_groupchats()*/ + m_conference_invite_cb *conference_invite; + + m_file_recv_cb *file_sendrequest; + m_file_recv_control_cb *file_filecontrol; + m_file_recv_chunk_cb *file_filedata; + m_file_chunk_request_cb *file_reqchunk; + + m_msi_packet_cb *msi_packet; + void *msi_packet_userdata; + + m_friend_lossy_packet_cb *lossy_packethandler; + m_friend_lossless_packet_cb *lossless_packethandler; + + m_self_connection_status_cb *core_connection_change; + Onion_Connection_Status last_connection_status; + + Messenger_Options options; +}; + +/** + * Format: `[real_pk (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)]` + * + * @param[out] address FRIEND_ADDRESS_SIZE byte address to give to others. + */ +non_null() +void getaddress(const Messenger *m, uint8_t *address); + +/** + * Add a friend. + * + * Set the data that will be sent along with friend request. + * + * @param address is the address of the friend (returned by getaddress of the friend + * you wish to add) it must be FRIEND_ADDRESS_SIZE bytes. + * TODO(irungentoo): add checksum. + * @param data is the data. + * @param length is the length. + * + * @return the friend number if success. + * @retval FA_TOOLONG if message length is too long. + * @retval FAERR_NOMESSAGE if no message (message length must be >= 1 byte). + * @retval FAERR_OWNKEY if user's own key. + * @retval FAERR_ALREADYSENT if friend request already sent or already a friend. + * @retval FAERR_BADCHECKSUM if bad checksum in address. + * @retval FAERR_SETNEWNOSPAM if the friend was already there but the nospam was different. + * (the nospam for that friend was set to the new one). + * @retval FAERR_NOMEM if increasing the friend list size fails. + */ +non_null() +int32_t m_addfriend(Messenger *m, const uint8_t *address, const uint8_t *data, uint16_t length); + + +/** @brief Add a friend without sending a friendrequest. + * @return the friend number if success. + * @retval -3 if user's own key. + * @retval -4 if friend request already sent or already a friend. + * @retval -6 if bad checksum in address. + * @retval -8 if increasing the friend list size fails. + */ +non_null() +int32_t m_addfriend_norequest(Messenger *m, const uint8_t *real_pk); + +/** @return the friend number associated to that public key. + * @retval -1 if no such friend. + */ +non_null() +int32_t getfriend_id(const Messenger *m, const uint8_t *real_pk); + +/** @brief Copies the public key associated to that friend id into real_pk buffer. + * + * Make sure that real_pk is of size CRYPTO_PUBLIC_KEY_SIZE. + * + * @retval 0 if success. + * @retval -1 if failure. + */ +non_null() +int get_real_pk(const Messenger *m, int32_t friendnumber, uint8_t *real_pk); + +/** @return friend connection id on success. + * @retval -1 if failure. + */ +non_null() +int getfriendcon_id(const Messenger *m, int32_t friendnumber); + +/** @brief Remove a friend. + * + * @retval 0 if success. + * @retval -1 if failure. + */ +non_null() +int m_delfriend(Messenger *m, int32_t friendnumber); + +/** @brief Checks friend's connection status. + * + * @retval CONNECTION_UDP (2) if friend is directly connected to us (Online UDP). + * @retval CONNECTION_TCP (1) if friend is connected to us (Online TCP). + * @retval CONNECTION_NONE (0) if friend is not connected to us (Offline). + * @retval -1 on failure. + */ +non_null() +int m_get_friend_connectionstatus(const Messenger *m, int32_t friendnumber); + +/** + * Checks if there exists a friend with given friendnumber. + * + * @param friendnumber The index in the friend list. + * + * @retval true if friend exists. + * @retval false if friend doesn't exist. + */ +non_null() +bool m_friend_exists(const Messenger *m, int32_t friendnumber); + +/** @brief Send a message of type to an online friend. + * + * @retval -1 if friend not valid. + * @retval -2 if too large. + * @retval -3 if friend not online. + * @retval -4 if send failed (because queue is full). + * @retval -5 if bad type. + * @retval 0 if success. + * + * The value in message_id will be passed to your read_receipt callback when the other receives the message. + */ +non_null(1, 4) nullable(6) +int m_send_message_generic(Messenger *m, int32_t friendnumber, uint8_t type, const uint8_t *message, uint32_t length, + uint32_t *message_id); + + +/** @brief Set the name and name_length of a friend. + * + * name must be a string of maximum MAX_NAME_LENGTH length. + * length must be at least 1 byte. + * length is the length of name with the NULL terminator. + * + * @retval 0 if success. + * @retval -1 if failure. + */ +non_null() +int setfriendname(Messenger *m, int32_t friendnumber, const uint8_t *name, uint16_t length); + +/** @brief Set our nickname. + * + * name must be a string of maximum MAX_NAME_LENGTH length. + * length must be at least 1 byte. + * length is the length of name with the NULL terminator. + * + * @retval 0 if success. + * @retval -1 if failure. + */ +non_null() +int setname(Messenger *m, const uint8_t *name, uint16_t length); + +/** + * @brief Get your nickname. + * + * m - The messenger context to use. + * name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH bytes. + * + * @return length of the name. + * @retval 0 on error. + */ +non_null() +uint16_t getself_name(const Messenger *m, uint8_t *name); + +/** @brief Get name of friendnumber and put it in name. + * + * name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH (128) bytes. + * + * @return length of name if success. + * @retval -1 if failure. + */ +non_null() +int getname(const Messenger *m, int32_t friendnumber, uint8_t *name); + +/** @return the length of name, including null on success. + * @retval -1 on failure. + */ +non_null() int m_get_name_size(const Messenger *m, int32_t friendnumber); +non_null() int m_get_self_name_size(const Messenger *m); + +/** @brief Set our user status. + * You are responsible for freeing status after. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +non_null() int m_set_statusmessage(Messenger *m, const uint8_t *status, uint16_t length); +non_null() int m_set_userstatus(Messenger *m, uint8_t status); + +/** + * Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH. + * + * @return the length of friendnumber's status message, including null on success. + * @retval -1 on failure. + */ +non_null() int m_get_statusmessage_size(const Messenger *m, int32_t friendnumber); +non_null() int m_get_self_statusmessage_size(const Messenger *m); + +/** @brief Copy friendnumber's status message into buf, truncating if size is over maxlen. + * + * Get the size you need to allocate from m_get_statusmessage_size. + * The self variant will copy our own status message. + * + * @return the length of the copied data on success + * @retval -1 on failure. + */ +non_null() int m_copy_statusmessage(const Messenger *m, int32_t friendnumber, uint8_t *buf, uint32_t maxlen); +non_null() int m_copy_self_statusmessage(const Messenger *m, uint8_t *buf); + +/** @brief return one of Userstatus values. + * + * Values unknown to your application should be represented as USERSTATUS_NONE. + * As above, the self variant will return our own Userstatus. + * If friendnumber is invalid, this shall return USERSTATUS_INVALID. + */ +non_null() uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber); +non_null() uint8_t m_get_self_userstatus(const Messenger *m); + +/* get capabilities of friend's toxcore + * return TOX_CAPABILITY_BASIC on any error + */ +uint64_t m_get_friend_toxcore_capabilities(const Messenger *m, int32_t friendnumber); + +/** @brief returns timestamp of last time friendnumber was seen online or 0 if never seen. + * if friendnumber is invalid this function will return UINT64_MAX. + */ +non_null() uint64_t m_get_last_online(const Messenger *m, int32_t friendnumber); + +/** @brief Set our typing status for a friend. + * You are responsible for turning it on or off. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +non_null() +int m_set_usertyping(Messenger *m, int32_t friendnumber, bool is_typing); + +/** @brief Get the typing status of a friend. + * + * @retval -1 if friend number is invalid. + * @retval 0 if friend is not typing. + * @retval 1 if friend is typing. + */ +non_null() +int m_get_istyping(const Messenger *m, int32_t friendnumber); + +/** Set the function that will be executed when a friend request is received. */ +non_null(1) nullable(2) +void m_callback_friendrequest(Messenger *m, m_friend_request_cb *function); + +/** Set the function that will be executed when a message from a friend is received. */ +non_null() void m_callback_friendmessage(Messenger *m, m_friend_message_cb *function); + +/** @brief Set the callback for name changes. + * You are not responsible for freeing newname. + */ +non_null() void m_callback_namechange(Messenger *m, m_friend_name_cb *function); + +/** @brief Set the callback for status message changes. + * + * You are not responsible for freeing newstatus + */ +non_null() void m_callback_statusmessage(Messenger *m, m_friend_status_message_cb *function); + +/** @brief Set the callback for status type changes. */ +non_null() void m_callback_userstatus(Messenger *m, m_friend_status_cb *function); + +/** @brief Set the callback for typing changes. */ +non_null() void m_callback_typingchange(Messenger *m, m_friend_typing_cb *function); + +/** @brief Set the callback for read receipts. + * + * If you are keeping a record of returns from m_sendmessage, + * receipt might be one of those values, meaning the message + * has been received on the other side. + * Since core doesn't track ids for you, receipt may not correspond to any message. + * In that case, you should discard it. + */ +non_null() void m_callback_read_receipt(Messenger *m, m_friend_read_receipt_cb *function); + +/** @brief Set the callback for connection status changes. + * + * Status: + * - 0: friend went offline after being previously online. + * - 1: friend went online. + * + * Note that this callback is not called when adding friends, thus the + * "after being previously online" part. + * It's assumed that when adding friends, their connection status is offline. + */ +non_null() void m_callback_connectionstatus(Messenger *m, m_friend_connection_status_cb *function); + +/** Same as previous but for internal A/V core usage only */ +non_null() void m_callback_connectionstatus_internal_av( + Messenger *m, m_friend_connectionstatuschange_internal_cb *function, void *userdata); + + +/** @brief Set the callback for typing changes. */ +non_null() void m_callback_core_connection(Messenger *m, m_self_connection_status_cb *function); + +/*** CONFERENCES */ + +/** @brief Set the callback for conference invites. */ +non_null(1) nullable(2) +void m_callback_conference_invite(Messenger *m, m_conference_invite_cb *function); + +/** @brief Send a conference invite packet. + * + * return true on success + * return false on failure + */ +non_null() +bool send_conference_invite_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length); + +/*** FILE SENDING */ + + +/** @brief Set the callback for file send requests. */ +non_null() void callback_file_sendrequest(Messenger *m, m_file_recv_cb *function); + + +/** @brief Set the callback for file control requests. */ +non_null() void callback_file_control(Messenger *m, m_file_recv_control_cb *function); + +/** @brief Set the callback for file data. */ +non_null() void callback_file_data(Messenger *m, m_file_recv_chunk_cb *function); + +/** @brief Set the callback for file request chunk. */ +non_null() void callback_file_reqchunk(Messenger *m, m_file_chunk_request_cb *function); + + +/** @brief Copy the file transfer file id to file_id + * + * @retval 0 on success. + * @retval -1 if friend not valid. + * @retval -2 if filenumber not valid + */ +non_null() +int file_get_id(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint8_t *file_id); + +/** @brief Send a file send request. + * + * Maximum filename length is 255 bytes. + * + * @return file number on success + * @retval -1 if friend not found. + * @retval -2 if filename length invalid. + * @retval -3 if no more file sending slots left. + * @retval -4 if could not send packet (friend offline). + */ +non_null() +long int new_filesender(const Messenger *m, int32_t friendnumber, uint32_t file_type, uint64_t filesize, + const uint8_t *file_id, const uint8_t *filename, uint16_t filename_length); + +/** @brief Send a file control request. + * + * @retval 0 on success + * @retval -1 if friend not valid. + * @retval -2 if friend not online. + * @retval -3 if file number invalid. + * @retval -4 if file control is bad. + * @retval -5 if file already paused. + * @retval -6 if resume file failed because it was only paused by the other. + * @retval -7 if resume file failed because it wasn't paused. + * @retval -8 if packet failed to send. + */ +non_null() +int file_control(const Messenger *m, int32_t friendnumber, uint32_t filenumber, unsigned int control); + +/** @brief Send a seek file control request. + * + * @retval 0 on success + * @retval -1 if friend not valid. + * @retval -2 if friend not online. + * @retval -3 if file number invalid. + * @retval -4 if not receiving file. + * @retval -5 if file status wrong. + * @retval -6 if position bad. + * @retval -8 if packet failed to send. + */ +non_null() +int file_seek(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position); + +/** @brief Send file data. + * + * @retval 0 on success + * @retval -1 if friend not valid. + * @retval -2 if friend not online. + * @retval -3 if filenumber invalid. + * @retval -4 if file transfer not transferring. + * @retval -5 if bad data size. + * @retval -6 if packet queue full. + * @retval -7 if wrong position. + */ +non_null(1) nullable(5) +int send_file_data(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data, uint16_t length); + +/*** A/V related */ + +/** @brief Set the callback for msi packets. */ +non_null(1) nullable(2, 3) +void m_callback_msi_packet(Messenger *m, m_msi_packet_cb *function, void *userdata); + +/** @brief Send an msi packet. + * + * @retval true on success + * @retval false on failure + */ +non_null() +bool m_msi_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length); + +/** @brief Set handlers for lossy rtp packets. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null(1) nullable(4, 5) +int m_callback_rtp_packet(Messenger *m, int32_t friendnumber, uint8_t byte, + m_lossy_rtp_packet_cb *function, void *object); + +/*** CUSTOM PACKETS */ + +/** @brief Set handlers for custom lossy packets. */ +non_null() void custom_lossy_packet_registerhandler(Messenger *m, m_friend_lossy_packet_cb *lossy_packethandler); + +/** @brief High level function to send custom lossy packets. + * + * TODO(oxij): this name is confusing, because this function sends both av and custom lossy packets. + * Meanwhile, m_handle_lossy_packet routes custom packets to custom_lossy_packet_registerhandler + * as you would expect from its name. + * + * I.e. custom_lossy_packet_registerhandler's "custom lossy packet" and this "custom lossy packet" + * are not the same set of packets. + * + * @retval -1 if friend invalid. + * @retval -2 if length wrong. + * @retval -3 if first byte invalid. + * @retval -4 if friend offline. + * @retval -5 if packet failed to send because of other error. + * @retval 0 on success. + */ +non_null() +int m_send_custom_lossy_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length); + + +/** @brief Set handlers for custom lossless packets. */ +non_null() +void custom_lossless_packet_registerhandler(Messenger *m, m_friend_lossless_packet_cb *lossless_packethandler); + +/** @brief High level function to send custom lossless packets. + * + * @retval -1 if friend invalid. + * @retval -2 if length wrong. + * @retval -3 if first byte invalid. + * @retval -4 if friend offline. + * @retval -5 if packet failed to send because of other error. + * @retval 0 on success. + */ +non_null() +int send_custom_lossless_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length); + +/*** Messenger constructor/destructor/operations. */ + +typedef enum Messenger_Error { + MESSENGER_ERROR_NONE, + MESSENGER_ERROR_PORT, + MESSENGER_ERROR_TCP_SERVER, + MESSENGER_ERROR_OTHER, +} Messenger_Error; + +/** @brief Run this at startup. + * + * @return allocated instance of Messenger on success. + * @retval 0 if there are problems. + * + * if error is not NULL it will be set to one of the values in the enum above. + */ +non_null() +Messenger *new_messenger(Mono_Time *mono_time, const Random *rng, const Network *ns, Messenger_Options *options, Messenger_Error *error); + +/** @brief Run this before closing shop. + * + * Free all datastructures. + */ +nullable(1) +void kill_messenger(Messenger *m); + +/** @brief The main loop that needs to be run at least 20 times per second. */ +non_null(1) nullable(2) +void do_messenger(Messenger *m, void *userdata); + +/** + * @brief Return the time in milliseconds before `do_messenger()` should be called again + * for optimal performance. + * + * @return time (in ms) before the next `do_messenger()` needs to be run on success. + */ +non_null() +uint32_t messenger_run_interval(const Messenger *m); + +/* SAVING AND LOADING FUNCTIONS: */ + +/** @brief Registers a state plugin for saving, loading, and getting the size of a section of the save. + * + * @retval true on success + * @retval false on error + */ +non_null() +bool m_register_state_plugin(Messenger *m, State_Type type, + m_state_size_cb *size_callback, + m_state_load_cb *load_callback, + m_state_save_cb *save_callback); + +/** return size of the messenger data (for saving). */ +non_null() +uint32_t messenger_size(const Messenger *m); + +/** Save the messenger in data (must be allocated memory of size at least `Messenger_size()`) */ +non_null() +uint8_t *messenger_save(const Messenger *m, uint8_t *data); + +/** @brief Load a state section. + * + * @param data Data to load. + * @param length Length of data. + * @param type Type of section (`STATE_TYPE_*`). + * @param status Result of loading section is stored here if the section is handled. + * @return true iff section handled. + */ +non_null() +bool messenger_load_state_section(Messenger *m, const uint8_t *data, uint32_t length, uint16_t type, + State_Load_Status *status); + +/** @brief Return the number of friends in the instance m. + * + * You should use this to determine how much memory to allocate + * for copy_friendlist. + */ +non_null() +uint32_t count_friendlist(const Messenger *m); + +/** @brief Copy a list of valid friend IDs into the array out_list. + * If out_list is NULL, returns 0. + * Otherwise, returns the number of elements copied. + * If the array was too small, the contents + * of out_list will be truncated to list_size. + */ +non_null() +uint32_t copy_friendlist(const Messenger *m, uint32_t *out_list, uint32_t list_size); + +non_null() +bool m_is_receiving_file(Messenger *m); + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/Messenger.m b/local_pod_repo/toxcore/toxcore/toxcore/Messenger.m new file mode 100644 index 0000000..79a68c9 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/Messenger.m @@ -0,0 +1,3414 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * An implementation of a simple text chat only messenger on the tox network core. + */ +#include "Messenger.h" + +#include +#include +#include +#include +#include + +#include "ccompat.h" +#include "logger.h" +#include "mono_time.h" +#include "network.h" +#include "state.h" +#include "util.h" + +static_assert(MAX_CONCURRENT_FILE_PIPES <= UINT8_MAX + 1, + "uint8_t cannot represent all file transfer numbers"); + +static const Friend empty_friend = {{0}}; + +/** @brief Set the size of the friend list to numfriends. + * + * @retval -1 if realloc fails. + */ +non_null() +static int realloc_friendlist(Messenger *m, uint32_t num) +{ + if (num == 0) { + free(m->friendlist); + m->friendlist = nullptr; + return 0; + } + + Friend *newfriendlist = (Friend *)realloc(m->friendlist, num * sizeof(Friend)); + + if (newfriendlist == nullptr) { + return -1; + } + + m->friendlist = newfriendlist; + return 0; +} + +/** @return the friend number associated to that public key. + * @retval -1 if no such friend. + */ +int32_t getfriend_id(const Messenger *m, const uint8_t *real_pk) +{ + for (uint32_t i = 0; i < m->numfriends; ++i) { + if (m->friendlist[i].status > 0 && pk_equal(real_pk, m->friendlist[i].real_pk)) { + return i; + } + } + + return -1; +} + +/** @brief Copies the public key associated to that friend id into real_pk buffer. + * + * Make sure that real_pk is of size CRYPTO_PUBLIC_KEY_SIZE. + * + * @retval 0 if success. + * @retval -1 if failure. + */ +int get_real_pk(const Messenger *m, int32_t friendnumber, uint8_t *real_pk) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + memcpy(real_pk, m->friendlist[friendnumber].real_pk, CRYPTO_PUBLIC_KEY_SIZE); + return 0; +} + +/** @return friend connection id on success. + * @retval -1 if failure. + */ +int getfriendcon_id(const Messenger *m, int32_t friendnumber) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + return m->friendlist[friendnumber].friendcon_id; +} + +/** + * Format: `[real_pk (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)]` + * + * @param[out] address FRIEND_ADDRESS_SIZE byte address to give to others. + */ +void getaddress(const Messenger *m, uint8_t *address) +{ + pk_copy(address, nc_get_self_public_key(m->net_crypto)); + uint32_t nospam = get_nospam(m->fr); + memcpy(address + CRYPTO_PUBLIC_KEY_SIZE, &nospam, sizeof(nospam)); + uint16_t checksum = data_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum)); + memcpy(address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(nospam), &checksum, sizeof(checksum)); +} + +non_null() +static bool send_online_packet(Messenger *m, int32_t friendnumber) +{ + if (!m_friend_exists(m, friendnumber)) { + return false; + } + + uint8_t buf[TOX_CAPABILITIES_SIZE + 1]; + buf[0] = PACKET_ID_ONLINE; + net_pack_u64(buf + 1, TOX_CAPABILITIES_CURRENT); + + if (write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), buf, (TOX_CAPABILITIES_SIZE + 1), false) == -1) { + return false; + } + + uint8_t packet = PACKET_ID_ONLINE; + return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), &packet, sizeof(packet), false) != -1; +} + +non_null() +static bool send_offline_packet(Messenger *m, int friendcon_id) +{ + uint8_t packet = PACKET_ID_OFFLINE; + return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, friendcon_id), &packet, + sizeof(packet), false) != -1; +} + +non_null(1) nullable(4) +static int m_handle_status(void *object, int i, bool status, void *userdata); +non_null(1, 3) nullable(5) +static int m_handle_packet(void *object, int i, const uint8_t *temp, uint16_t len, void *userdata); +non_null(1, 3) nullable(5) +static int m_handle_lossy_packet(void *object, int friend_num, const uint8_t *packet, uint16_t length, + void *userdata); + +non_null() +static int32_t init_new_friend(Messenger *m, const uint8_t *real_pk, uint8_t status) +{ + if (m->numfriends == UINT32_MAX) { + LOGGER_ERROR(m->log, "Friend list full: we have more than 4 billion friends"); + /* This is technically incorrect, but close enough. */ + return FAERR_NOMEM; + } + + /* Resize the friend list if necessary. */ + if (realloc_friendlist(m, m->numfriends + 1) != 0) { + return FAERR_NOMEM; + } + + m->friendlist[m->numfriends] = empty_friend; + + const int friendcon_id = new_friend_connection(m->fr_c, real_pk); + + if (friendcon_id == -1) { + return FAERR_NOMEM; + } + + for (uint32_t i = 0; i <= m->numfriends; ++i) { + if (m->friendlist[i].status == NOFRIEND) { + m->friendlist[i].status = status; + m->friendlist[i].friendcon_id = friendcon_id; + m->friendlist[i].friendrequest_lastsent = 0; + pk_copy(m->friendlist[i].real_pk, real_pk); + m->friendlist[i].statusmessage_length = 0; + m->friendlist[i].userstatus = USERSTATUS_NONE; + m->friendlist[i].is_typing = false; + m->friendlist[i].message_id = 0; + m->friendlist[i].toxcore_capabilities = TOX_CAPABILITY_BASIC; + friend_connection_callbacks(m->fr_c, friendcon_id, MESSENGER_CALLBACK_INDEX, &m_handle_status, &m_handle_packet, + &m_handle_lossy_packet, m, i); + + if (m->numfriends == i) { + ++m->numfriends; + } + + if (friend_con_connected(m->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED) { + send_online_packet(m, i); + } + + return i; + } + } + + return FAERR_NOMEM; +} + +/** + * Add a friend. + * + * Set the data that will be sent along with friend request. + * + * @param address is the address of the friend (returned by getaddress of the friend + * you wish to add) it must be FRIEND_ADDRESS_SIZE bytes. + * TODO(irungentoo): add checksum. + * @param data is the data. + * @param length is the length. + * + * @return the friend number if success. + * @retval FA_TOOLONG if message length is too long. + * @retval FAERR_NOMESSAGE if no message (message length must be >= 1 byte). + * @retval FAERR_OWNKEY if user's own key. + * @retval FAERR_ALREADYSENT if friend request already sent or already a friend. + * @retval FAERR_BADCHECKSUM if bad checksum in address. + * @retval FAERR_SETNEWNOSPAM if the friend was already there but the nospam was different. + * (the nospam for that friend was set to the new one). + * @retval FAERR_NOMEM if increasing the friend list size fails. + */ +int32_t m_addfriend(Messenger *m, const uint8_t *address, const uint8_t *data, uint16_t length) +{ + if (length > MAX_FRIEND_REQUEST_DATA_SIZE) { + return FAERR_TOOLONG; + } + + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + pk_copy(real_pk, address); + + if (!public_key_valid(real_pk)) { + return FAERR_BADCHECKSUM; + } + + uint16_t check; + const uint16_t checksum = data_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum)); + memcpy(&check, address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t), sizeof(check)); + + if (check != checksum) { + return FAERR_BADCHECKSUM; + } + + if (length < 1) { + return FAERR_NOMESSAGE; + } + + if (pk_equal(real_pk, nc_get_self_public_key(m->net_crypto))) { + return FAERR_OWNKEY; + } + + const int32_t friend_id = getfriend_id(m, real_pk); + + if (friend_id != -1) { + if (m->friendlist[friend_id].status >= FRIEND_CONFIRMED) { + return FAERR_ALREADYSENT; + } + + uint32_t nospam; + memcpy(&nospam, address + CRYPTO_PUBLIC_KEY_SIZE, sizeof(nospam)); + + if (m->friendlist[friend_id].friendrequest_nospam == nospam) { + return FAERR_ALREADYSENT; + } + + m->friendlist[friend_id].friendrequest_nospam = nospam; + return FAERR_SETNEWNOSPAM; + } + + const int32_t ret = init_new_friend(m, real_pk, FRIEND_ADDED); + + if (ret < 0) { + return ret; + } + + m->friendlist[ret].friendrequest_timeout = FRIENDREQUEST_TIMEOUT; + memcpy(m->friendlist[ret].info, data, length); + m->friendlist[ret].info_size = length; + memcpy(&m->friendlist[ret].friendrequest_nospam, address + CRYPTO_PUBLIC_KEY_SIZE, sizeof(uint32_t)); + + return ret; +} + +int32_t m_addfriend_norequest(Messenger *m, const uint8_t *real_pk) +{ + if (getfriend_id(m, real_pk) != -1) { + return FAERR_ALREADYSENT; + } + + if (!public_key_valid(real_pk)) { + return FAERR_BADCHECKSUM; + } + + if (pk_equal(real_pk, nc_get_self_public_key(m->net_crypto))) { + return FAERR_OWNKEY; + } + + return init_new_friend(m, real_pk, FRIEND_CONFIRMED); +} + +non_null() +static int clear_receipts(Messenger *m, int32_t friendnumber) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + struct Receipts *receipts = m->friendlist[friendnumber].receipts_start; + + while (receipts != nullptr) { + struct Receipts *temp_r = receipts->next; + free(receipts); + receipts = temp_r; + } + + m->friendlist[friendnumber].receipts_start = nullptr; + m->friendlist[friendnumber].receipts_end = nullptr; + return 0; +} + +non_null() +static int add_receipt(Messenger *m, int32_t friendnumber, uint32_t packet_num, uint32_t msg_id) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + struct Receipts *new_receipts = (struct Receipts *)calloc(1, sizeof(struct Receipts)); + + if (new_receipts == nullptr) { + return -1; + } + + new_receipts->packet_num = packet_num; + new_receipts->msg_id = msg_id; + + if (m->friendlist[friendnumber].receipts_start == nullptr) { + m->friendlist[friendnumber].receipts_start = new_receipts; + } else { + m->friendlist[friendnumber].receipts_end->next = new_receipts; + } + + m->friendlist[friendnumber].receipts_end = new_receipts; + new_receipts->next = nullptr; + return 0; +} +/** + * return -1 on failure. + * return 0 if packet was received. + */ +non_null() +static int friend_received_packet(const Messenger *m, int32_t friendnumber, uint32_t number) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + return cryptpacket_received(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), number); +} + +non_null(1) nullable(3) +static int do_receipts(Messenger *m, int32_t friendnumber, void *userdata) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + struct Receipts *receipts = m->friendlist[friendnumber].receipts_start; + + while (receipts != nullptr) { + if (friend_received_packet(m, friendnumber, receipts->packet_num) == -1) { + break; + } + + if (m->read_receipt != nullptr) { + m->read_receipt(m, friendnumber, receipts->msg_id, userdata); + } + + struct Receipts *r_next = receipts->next; + + free(receipts); + + m->friendlist[friendnumber].receipts_start = r_next; + + receipts = r_next; + } + + if (m->friendlist[friendnumber].receipts_start == nullptr) { + m->friendlist[friendnumber].receipts_end = nullptr; + } + + return 0; +} + +/** @brief Remove a friend. + * + * @retval 0 if success. + * @retval -1 if failure. + */ +int m_delfriend(Messenger *m, int32_t friendnumber) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (m->friend_connectionstatuschange_internal != nullptr) { + m->friend_connectionstatuschange_internal(m, friendnumber, 0, m->friend_connectionstatuschange_internal_userdata); + } + + clear_receipts(m, friendnumber); + remove_request_received(m->fr, m->friendlist[friendnumber].real_pk); + friend_connection_callbacks(m->fr_c, m->friendlist[friendnumber].friendcon_id, MESSENGER_CALLBACK_INDEX, nullptr, + nullptr, nullptr, nullptr, 0); + + if (friend_con_connected(m->fr_c, m->friendlist[friendnumber].friendcon_id) == FRIENDCONN_STATUS_CONNECTED) { + send_offline_packet(m, m->friendlist[friendnumber].friendcon_id); + } + + kill_friend_connection(m->fr_c, m->friendlist[friendnumber].friendcon_id); + m->friendlist[friendnumber] = empty_friend; + + uint32_t i; + + for (i = m->numfriends; i != 0; --i) { + if (m->friendlist[i - 1].status != NOFRIEND) { + break; + } + } + + m->numfriends = i; + + if (realloc_friendlist(m, m->numfriends) != 0) { + return FAERR_NOMEM; + } + + return 0; +} + +int m_get_friend_connectionstatus(const Messenger *m, int32_t friendnumber) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return CONNECTION_NONE; + } + + bool direct_connected = false; + uint32_t num_online_relays = 0; + const int crypt_conn_id = friend_connection_crypt_connection_id(m->fr_c, m->friendlist[friendnumber].friendcon_id); + + if (!crypto_connection_status(m->net_crypto, crypt_conn_id, &direct_connected, &num_online_relays)) { + return CONNECTION_NONE; + } + + if (direct_connected) { + return CONNECTION_UDP; + } + + if (num_online_relays != 0) { + return CONNECTION_TCP; + } + + /* if we have a valid friend connection but do not have an established connection + * we leave the connection status unchanged until the friend connection is either + * established or dropped. + */ + return m->friendlist[friendnumber].last_connection_udp_tcp; +} + +/** + * Checks if there exists a friend with given friendnumber. + * + * @param friendnumber The index in the friend list. + * + * @retval true if friend exists. + * @retval false if friend doesn't exist. + */ +bool m_friend_exists(const Messenger *m, int32_t friendnumber) +{ + return (unsigned int)friendnumber < m->numfriends && m->friendlist[friendnumber].status != 0; +} + +/** @brief Send a message of type to an online friend. + * + * @retval -1 if friend not valid. + * @retval -2 if too large. + * @retval -3 if friend not online. + * @retval -4 if send failed (because queue is full). + * @retval -5 if bad type. + * @retval 0 if success. + * + * The value in message_id will be passed to your read_receipt callback when the other receives the message. + */ +int m_send_message_generic(Messenger *m, int32_t friendnumber, uint8_t type, const uint8_t *message, uint32_t length, + uint32_t *message_id) +{ + if (type > MESSAGE_HIGH_LEVEL_ACK) { + LOGGER_WARNING(m->log, "message type %d is invalid", type); + return -5; + } + + if (!m_friend_exists(m, friendnumber)) { + LOGGER_WARNING(m->log, "friend number %d is invalid", friendnumber); + return -1; + } + + if (length >= MAX_CRYPTO_DATA_SIZE) { + LOGGER_WARNING(m->log, "message length %u is too large", length); + return -2; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + LOGGER_WARNING(m->log, "friend %d is not online", friendnumber); + return -3; + } + + VLA(uint8_t, packet, length + 1); + packet[0] = PACKET_ID_MESSAGE + type; + + assert(message != nullptr); + memcpy(packet + 1, message, length); + + const int64_t packet_num = write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), packet, length + 1, false); + + if (packet_num == -1) { + return -4; + } + + const uint32_t msg_id = ++m->friendlist[friendnumber].message_id; + + add_receipt(m, friendnumber, packet_num, msg_id); + + if (message_id != nullptr) { + *message_id = msg_id; + } + + return 0; +} + +non_null() +static bool write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data, + uint32_t length, bool congestion_control) +{ + if (!m_friend_exists(m, friendnumber)) { + return false; + } + + if (length >= MAX_CRYPTO_DATA_SIZE || m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return false; + } + + VLA(uint8_t, packet, length + 1); + packet[0] = packet_id; + + assert(data != nullptr); + memcpy(packet + 1, data, length); + + return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), packet, length + 1, congestion_control) != -1; +} + +/** @brief Send a name packet to friendnumber. + * length is the length with the NULL terminator. + */ +non_null() +static bool m_sendname(const Messenger *m, int32_t friendnumber, const uint8_t *name, uint16_t length) +{ + if (length > MAX_NAME_LENGTH) { + return false; + } + + return write_cryptpacket_id(m, friendnumber, PACKET_ID_NICKNAME, name, length, false); +} + +/** @brief Set the name and name_length of a friend. + * + * name must be a string of maximum MAX_NAME_LENGTH length. + * length must be at least 1 byte. + * length is the length of name with the NULL terminator. + * + * @retval 0 if success. + * @retval -1 if failure. + */ +int setfriendname(Messenger *m, int32_t friendnumber, const uint8_t *name, uint16_t length) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (length > MAX_NAME_LENGTH || length == 0) { + return -1; + } + + m->friendlist[friendnumber].name_length = length; + memcpy(m->friendlist[friendnumber].name, name, length); + return 0; +} + +/** @brief Set our nickname. + * + * name must be a string of maximum MAX_NAME_LENGTH length. + * length must be at least 1 byte. + * length is the length of name with the NULL terminator. + * + * @retval 0 if success. + * @retval -1 if failure. + */ +int setname(Messenger *m, const uint8_t *name, uint16_t length) +{ + if (length > MAX_NAME_LENGTH) { + return -1; + } + + if (m->name_length == length && (length == 0 || memcmp(name, m->name, length) == 0)) { + return 0; + } + + if (length > 0) { + memcpy(m->name, name, length); + } + + m->name_length = length; + + for (uint32_t i = 0; i < m->numfriends; ++i) { + m->friendlist[i].name_sent = false; + } + + return 0; +} + +/** + * @brief Get your nickname. + * + * m - The messenger context to use. + * name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH bytes. + * + * @return length of the name. + * @retval 0 on error. + */ +uint16_t getself_name(const Messenger *m, uint8_t *name) +{ + if (name == nullptr) { + return 0; + } + + memcpy(name, m->name, m->name_length); + + return m->name_length; +} + +/** @brief Get name of friendnumber and put it in name. + * + * name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH (128) bytes. + * + * @return length of name if success. + * @retval -1 if failure. + */ +int getname(const Messenger *m, int32_t friendnumber, uint8_t *name) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + memcpy(name, m->friendlist[friendnumber].name, m->friendlist[friendnumber].name_length); + return m->friendlist[friendnumber].name_length; +} + +int m_get_name_size(const Messenger *m, int32_t friendnumber) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + return m->friendlist[friendnumber].name_length; +} + +int m_get_self_name_size(const Messenger *m) +{ + return m->name_length; +} + +int m_set_statusmessage(Messenger *m, const uint8_t *status, uint16_t length) +{ + if (length > MAX_STATUSMESSAGE_LENGTH) { + return -1; + } + + if (m->statusmessage_length == length && (length == 0 || memcmp(m->statusmessage, status, length) == 0)) { + return 0; + } + + if (length > 0) { + memcpy(m->statusmessage, status, length); + } + + m->statusmessage_length = length; + + for (uint32_t i = 0; i < m->numfriends; ++i) { + m->friendlist[i].statusmessage_sent = false; + } + + return 0; +} + +int m_set_userstatus(Messenger *m, uint8_t status) +{ + if (status >= USERSTATUS_INVALID) { + return -1; + } + + if (m->userstatus == status) { + return 0; + } + + m->userstatus = (Userstatus)status; + + for (uint32_t i = 0; i < m->numfriends; ++i) { + m->friendlist[i].userstatus_sent = false; + } + + return 0; +} + +/** + * Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH. + * + * @return the length of friendnumber's status message, including null on success. + * @retval -1 on failure. + */ +int m_get_statusmessage_size(const Messenger *m, int32_t friendnumber) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + return m->friendlist[friendnumber].statusmessage_length; +} + +/** @brief Copy friendnumber's status message into buf, truncating if size is over maxlen. + * + * Get the size you need to allocate from m_get_statusmessage_size. + * The self variant will copy our own status message. + * + * @return the length of the copied data on success + * @retval -1 on failure. + */ +int m_copy_statusmessage(const Messenger *m, int32_t friendnumber, uint8_t *buf, uint32_t maxlen) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + // TODO(iphydf): This should be uint16_t and min_u16. If maxlen exceeds + // uint16_t's range, it won't affect the result. + const uint32_t msglen = min_u32(maxlen, m->friendlist[friendnumber].statusmessage_length); + + memcpy(buf, m->friendlist[friendnumber].statusmessage, msglen); + memset(buf + msglen, 0, maxlen - msglen); + return msglen; +} + +/** @return the size of friendnumber's user status. + * Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH. + */ +int m_get_self_statusmessage_size(const Messenger *m) +{ + return m->statusmessage_length; +} + +int m_copy_self_statusmessage(const Messenger *m, uint8_t *buf) +{ + memcpy(buf, m->statusmessage, m->statusmessage_length); + return m->statusmessage_length; +} + +uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber) +{ + if (!m_friend_exists(m, friendnumber)) { + return USERSTATUS_INVALID; + } + + uint8_t status = m->friendlist[friendnumber].userstatus; + + if (status >= USERSTATUS_INVALID) { + status = USERSTATUS_NONE; + } + + return status; +} + +uint8_t m_get_self_userstatus(const Messenger *m) +{ + return m->userstatus; +} + +uint64_t m_get_last_online(const Messenger *m, int32_t friendnumber) +{ + if (!m_friend_exists(m, friendnumber)) { + return UINT64_MAX; + } + + return m->friendlist[friendnumber].last_seen_time; +} + +int m_set_usertyping(Messenger *m, int32_t friendnumber, bool is_typing) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (m->friendlist[friendnumber].user_istyping == is_typing) { + return 0; + } + + m->friendlist[friendnumber].user_istyping = is_typing; + m->friendlist[friendnumber].user_istyping_sent = false; + + return 0; +} + +int m_get_istyping(const Messenger *m, int32_t friendnumber) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + return m->friendlist[friendnumber].is_typing ? 1 : 0; +} + +non_null() +static bool send_statusmessage(const Messenger *m, int32_t friendnumber, const uint8_t *status, uint16_t length) +{ + return write_cryptpacket_id(m, friendnumber, PACKET_ID_STATUSMESSAGE, status, length, false); +} + +non_null() +static bool send_userstatus(const Messenger *m, int32_t friendnumber, uint8_t status) +{ + return write_cryptpacket_id(m, friendnumber, PACKET_ID_USERSTATUS, &status, sizeof(status), false); +} + +non_null() +static bool send_user_istyping(const Messenger *m, int32_t friendnumber, bool is_typing) +{ + const uint8_t typing = is_typing ? 1 : 0; + return write_cryptpacket_id(m, friendnumber, PACKET_ID_TYPING, &typing, sizeof(typing), false); +} + +non_null() +static int set_friend_statusmessage(const Messenger *m, int32_t friendnumber, const uint8_t *status, uint16_t length) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (length > MAX_STATUSMESSAGE_LENGTH) { + return -1; + } + + if (length > 0) { + memcpy(m->friendlist[friendnumber].statusmessage, status, length); + } + + m->friendlist[friendnumber].statusmessage_length = length; + return 0; +} + +non_null() +static void set_friend_userstatus(const Messenger *m, int32_t friendnumber, uint8_t status) +{ + m->friendlist[friendnumber].userstatus = (Userstatus)status; +} + +non_null() +static void set_friend_typing(const Messenger *m, int32_t friendnumber, bool is_typing) +{ + m->friendlist[friendnumber].is_typing = is_typing; +} + +/** Set the function that will be executed when a friend request is received. */ +void m_callback_friendrequest(Messenger *m, m_friend_request_cb *function) +{ + m->friend_request = function; +} + +/** Set the function that will be executed when a message from a friend is received. */ +void m_callback_friendmessage(Messenger *m, m_friend_message_cb *function) +{ + m->friend_message = function; +} + +void m_callback_namechange(Messenger *m, m_friend_name_cb *function) +{ + m->friend_namechange = function; +} + +void m_callback_statusmessage(Messenger *m, m_friend_status_message_cb *function) +{ + m->friend_statusmessagechange = function; +} + +void m_callback_userstatus(Messenger *m, m_friend_status_cb *function) +{ + m->friend_userstatuschange = function; +} + +void m_callback_typingchange(Messenger *m, m_friend_typing_cb *function) +{ + m->friend_typingchange = function; +} + +void m_callback_read_receipt(Messenger *m, m_friend_read_receipt_cb *function) +{ + m->read_receipt = function; +} + +void m_callback_connectionstatus(Messenger *m, m_friend_connection_status_cb *function) +{ + m->friend_connectionstatuschange = function; +} + +void m_callback_core_connection(Messenger *m, m_self_connection_status_cb *function) +{ + m->core_connection_change = function; +} + +void m_callback_connectionstatus_internal_av(Messenger *m, m_friend_connectionstatuschange_internal_cb *function, + void *userdata) +{ + m->friend_connectionstatuschange_internal = function; + m->friend_connectionstatuschange_internal_userdata = userdata; +} + +non_null(1) nullable(3) +static void check_friend_tcp_udp(Messenger *m, int32_t friendnumber, void *userdata) +{ + const int last_connection_udp_tcp = m->friendlist[friendnumber].last_connection_udp_tcp; + + const int ret = m_get_friend_connectionstatus(m, friendnumber); + + if (ret == -1) { + return; + } + + if (last_connection_udp_tcp != ret) { + if (m->friend_connectionstatuschange != nullptr) { + m->friend_connectionstatuschange(m, friendnumber, ret, userdata); + } + } + + m->friendlist[friendnumber].last_connection_udp_tcp = (Connection_Status)ret; +} + +non_null() +static void break_files(const Messenger *m, int32_t friendnumber); + +non_null(1) nullable(4) +static void check_friend_connectionstatus(Messenger *m, int32_t friendnumber, uint8_t status, void *userdata) +{ + if (status == NOFRIEND) { + return; + } + + const bool was_online = m->friendlist[friendnumber].status == FRIEND_ONLINE; + const bool is_online = status == FRIEND_ONLINE; + + if (is_online != was_online) { + if (was_online) { + break_files(m, friendnumber); + clear_receipts(m, friendnumber); + } else { + m->friendlist[friendnumber].name_sent = false; + m->friendlist[friendnumber].userstatus_sent = false; + m->friendlist[friendnumber].statusmessage_sent = false; + m->friendlist[friendnumber].user_istyping_sent = false; + } + + m->friendlist[friendnumber].status = status; + + check_friend_tcp_udp(m, friendnumber, userdata); + + if (m->friend_connectionstatuschange_internal != nullptr) { + m->friend_connectionstatuschange_internal(m, friendnumber, is_online, + m->friend_connectionstatuschange_internal_userdata); + } + } +} + +non_null(1) nullable(4) +static void set_friend_status(Messenger *m, int32_t friendnumber, uint8_t status, void *userdata) +{ + check_friend_connectionstatus(m, friendnumber, status, userdata); + m->friendlist[friendnumber].status = status; +} + +/*** CONFERENCES */ + + +/** @brief Set the callback for conference invites. */ +void m_callback_conference_invite(Messenger *m, m_conference_invite_cb *function) +{ + m->conference_invite = function; +} + + +/** @brief Send a conference invite packet. + * + * return true on success + * return false on failure + */ +bool send_conference_invite_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length) +{ + return write_cryptpacket_id(m, friendnumber, PACKET_ID_INVITE_CONFERENCE, data, length, false); +} + +/*** FILE SENDING */ + + +/** @brief Set the callback for file send requests. */ +void callback_file_sendrequest(Messenger *m, m_file_recv_cb *function) +{ + m->file_sendrequest = function; +} + +/** @brief Set the callback for file control requests. */ +void callback_file_control(Messenger *m, m_file_recv_control_cb *function) +{ + m->file_filecontrol = function; +} + +/** @brief Set the callback for file data. */ +void callback_file_data(Messenger *m, m_file_recv_chunk_cb *function) +{ + m->file_filedata = function; +} + +/** @brief Set the callback for file request chunk. */ +void callback_file_reqchunk(Messenger *m, m_file_chunk_request_cb *function) +{ + m->file_reqchunk = function; +} + +#define MAX_FILENAME_LENGTH 255 + +/** @brief Copy the file transfer file id to file_id + * + * @retval 0 on success. + * @retval -1 if friend not valid. + * @retval -2 if filenumber not valid + */ +int file_get_id(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint8_t *file_id) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -2; + } + + uint32_t temp_filenum; + bool inbound; + uint8_t file_number; + + if (filenumber >= (1 << 16)) { + inbound = true; + temp_filenum = (filenumber >> 16) - 1; + } else { + inbound = false; + temp_filenum = filenumber; + } + + if (temp_filenum >= MAX_CONCURRENT_FILE_PIPES) { + return -2; + } + + file_number = temp_filenum; + + const struct File_Transfers *const ft = inbound + ? &m->friendlist[friendnumber].file_receiving[file_number] + : &m->friendlist[friendnumber].file_sending[file_number]; + + if (ft->status == FILESTATUS_NONE) { + return -2; + } + + memcpy(file_id, ft->id, FILE_ID_LENGTH); + return 0; +} + +/** @brief Send a file send request. + * Maximum filename length is 255 bytes. + * @retval 1 on success + * @retval 0 on failure + */ +non_null() +static bool file_sendrequest(const Messenger *m, int32_t friendnumber, uint8_t filenumber, uint32_t file_type, + uint64_t filesize, const uint8_t *file_id, const uint8_t *filename, uint16_t filename_length) +{ + if (!m_friend_exists(m, friendnumber)) { + return false; + } + + if (filename_length > MAX_FILENAME_LENGTH) { + return false; + } + + VLA(uint8_t, packet, 1 + sizeof(file_type) + sizeof(filesize) + FILE_ID_LENGTH + filename_length); + packet[0] = filenumber; + file_type = net_htonl(file_type); + memcpy(packet + 1, &file_type, sizeof(file_type)); + net_pack_u64(packet + 1 + sizeof(file_type), filesize); + memcpy(packet + 1 + sizeof(file_type) + sizeof(filesize), file_id, FILE_ID_LENGTH); + + if (filename_length > 0) { + memcpy(packet + 1 + sizeof(file_type) + sizeof(filesize) + FILE_ID_LENGTH, filename, filename_length); + } + + return write_cryptpacket_id(m, friendnumber, PACKET_ID_FILE_SENDREQUEST, packet, SIZEOF_VLA(packet), false); +} + +/** @brief Send a file send request. + * + * Maximum filename length is 255 bytes. + * + * @return file number on success + * @retval -1 if friend not found. + * @retval -2 if filename length invalid. + * @retval -3 if no more file sending slots left. + * @retval -4 if could not send packet (friend offline). + */ +long int new_filesender(const Messenger *m, int32_t friendnumber, uint32_t file_type, uint64_t filesize, + const uint8_t *file_id, const uint8_t *filename, uint16_t filename_length) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (filename_length > MAX_FILENAME_LENGTH) { + return -2; + } + + uint32_t i; + + for (i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) { + if (m->friendlist[friendnumber].file_sending[i].status == FILESTATUS_NONE) { + break; + } + } + + if (i == MAX_CONCURRENT_FILE_PIPES) { + return -3; + } + + if (!file_sendrequest(m, friendnumber, i, file_type, filesize, file_id, filename, filename_length)) { + return -4; + } + + struct File_Transfers *ft = &m->friendlist[friendnumber].file_sending[i]; + + ft->status = FILESTATUS_NOT_ACCEPTED; + + ft->size = filesize; + + ft->transferred = 0; + + ft->requested = 0; + + ft->paused = FILE_PAUSE_NOT; + + memcpy(ft->id, file_id, FILE_ID_LENGTH); + + return i; +} + +non_null(1) nullable(6) +static bool send_file_control_packet(const Messenger *m, int32_t friendnumber, bool inbound, uint8_t filenumber, + uint8_t control_type, const uint8_t *data, uint16_t data_length) +{ + assert(data_length == 0 || data != nullptr); + + if ((unsigned int)(1 + 3 + data_length) > MAX_CRYPTO_DATA_SIZE) { + return false; + } + + VLA(uint8_t, packet, 3 + data_length); + + packet[0] = inbound ? 1 : 0; + packet[1] = filenumber; + packet[2] = control_type; + + if (data_length > 0) { + memcpy(packet + 3, data, data_length); + } + + return write_cryptpacket_id(m, friendnumber, PACKET_ID_FILE_CONTROL, packet, SIZEOF_VLA(packet), false); +} + +/** @brief Send a file control request. + * + * @retval 0 on success + * @retval -1 if friend not valid. + * @retval -2 if friend not online. + * @retval -3 if file number invalid. + * @retval -4 if file control is bad. + * @retval -5 if file already paused. + * @retval -6 if resume file failed because it was only paused by the other. + * @retval -7 if resume file failed because it wasn't paused. + * @retval -8 if packet failed to send. + */ +int file_control(const Messenger *m, int32_t friendnumber, uint32_t filenumber, unsigned int control) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -2; + } + + uint32_t temp_filenum; + bool inbound; + uint8_t file_number; + + if (filenumber >= (1 << 16)) { + inbound = true; + temp_filenum = (filenumber >> 16) - 1; + } else { + inbound = false; + temp_filenum = filenumber; + } + + if (temp_filenum >= MAX_CONCURRENT_FILE_PIPES) { + return -3; + } + + file_number = temp_filenum; + + struct File_Transfers *ft; + + if (inbound) { + ft = &m->friendlist[friendnumber].file_receiving[file_number]; + } else { + ft = &m->friendlist[friendnumber].file_sending[file_number]; + } + + if (ft->status == FILESTATUS_NONE) { + return -3; + } + + if (control > FILECONTROL_KILL) { + return -4; + } + + if (control == FILECONTROL_PAUSE && ((ft->paused & FILE_PAUSE_US) != 0 || ft->status != FILESTATUS_TRANSFERRING)) { + return -5; + } + + if (control == FILECONTROL_ACCEPT) { + if (ft->status == FILESTATUS_TRANSFERRING) { + if ((ft->paused & FILE_PAUSE_US) == 0) { + if ((ft->paused & FILE_PAUSE_OTHER) != 0) { + return -6; + } + + return -7; + } + } else { + if (ft->status != FILESTATUS_NOT_ACCEPTED) { + return -7; + } + + if (!inbound) { + return -6; + } + } + } + + if (send_file_control_packet(m, friendnumber, inbound, file_number, control, nullptr, 0)) { + switch (control) { + case FILECONTROL_KILL: { + if (!inbound && (ft->status == FILESTATUS_TRANSFERRING || ft->status == FILESTATUS_FINISHED)) { + // We are actively sending that file, remove from list + --m->friendlist[friendnumber].num_sending_files; + } + + ft->status = FILESTATUS_NONE; + break; + } + case FILECONTROL_PAUSE: { + ft->paused |= FILE_PAUSE_US; + break; + } + case FILECONTROL_ACCEPT: { + ft->status = FILESTATUS_TRANSFERRING; + + if ((ft->paused & FILE_PAUSE_US) != 0) { + ft->paused ^= FILE_PAUSE_US; + } + break; + } + } + } else { + return -8; + } + + return 0; +} + +/** @brief Send a seek file control request. + * + * @retval 0 on success + * @retval -1 if friend not valid. + * @retval -2 if friend not online. + * @retval -3 if file number invalid. + * @retval -4 if not receiving file. + * @retval -5 if file status wrong. + * @retval -6 if position bad. + * @retval -8 if packet failed to send. + */ +int file_seek(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -2; + } + + if (filenumber < (1 << 16)) { + // Not receiving. + return -4; + } + + const uint32_t temp_filenum = (filenumber >> 16) - 1; + + if (temp_filenum >= MAX_CONCURRENT_FILE_PIPES) { + return -3; + } + + assert(temp_filenum <= UINT8_MAX); + const uint8_t file_number = temp_filenum; + + // We're always receiving at this point. + struct File_Transfers *ft = &m->friendlist[friendnumber].file_receiving[file_number]; + + if (ft->status == FILESTATUS_NONE) { + return -3; + } + + if (ft->status != FILESTATUS_NOT_ACCEPTED) { + return -5; + } + + if (position >= ft->size) { + return -6; + } + + uint8_t sending_pos[sizeof(uint64_t)]; + net_pack_u64(sending_pos, position); + + if (send_file_control_packet(m, friendnumber, true, file_number, FILECONTROL_SEEK, sending_pos, + sizeof(sending_pos))) { + ft->transferred = position; + } else { + return -8; + } + + return 0; +} + +/** @return packet number on success. + * @retval -1 on failure. + */ +non_null(1) nullable(4) +static int64_t send_file_data_packet(const Messenger *m, int32_t friendnumber, uint8_t filenumber, const uint8_t *data, + uint16_t length) +{ + assert(length == 0 || data != nullptr); + + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + VLA(uint8_t, packet, 2 + length); + packet[0] = PACKET_ID_FILE_DATA; + packet[1] = filenumber; + + if (length > 0) { + memcpy(packet + 2, data, length); + } + + return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), packet, SIZEOF_VLA(packet), true); +} + +#define MAX_FILE_DATA_SIZE (MAX_CRYPTO_DATA_SIZE - 2) +#define MIN_SLOTS_FREE (CRYPTO_MIN_QUEUE_LENGTH / 4) +/** @brief Send file data. + * + * @retval 0 on success + * @retval -1 if friend not valid. + * @retval -2 if friend not online. + * @retval -3 if filenumber invalid. + * @retval -4 if file transfer not transferring. + * @retval -5 if bad data size. + * @retval -6 if packet queue full. + * @retval -7 if wrong position. + */ +int send_file_data(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position, + const uint8_t *data, uint16_t length) +{ + assert(length == 0 || data != nullptr); + + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -2; + } + + if (filenumber >= MAX_CONCURRENT_FILE_PIPES) { + return -3; + } + + struct File_Transfers *ft = &m->friendlist[friendnumber].file_sending[filenumber]; + + if (ft->status != FILESTATUS_TRANSFERRING) { + return -4; + } + + if (length > MAX_FILE_DATA_SIZE) { + return -5; + } + + if (ft->size - ft->transferred < length) { + return -5; + } + + if (ft->size != UINT64_MAX && length != MAX_FILE_DATA_SIZE && (ft->transferred + length) != ft->size) { + return -5; + } + + if (position != ft->transferred || (ft->requested <= position && ft->size != 0)) { + return -7; + } + + /* Prevent file sending from filling up the entire buffer preventing messages from being sent. + * TODO(irungentoo): remove */ + if (crypto_num_free_sendqueue_slots(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id)) < MIN_SLOTS_FREE) { + return -6; + } + + const int64_t ret = send_file_data_packet(m, friendnumber, filenumber, data, length); + + if (ret != -1) { + // TODO(irungentoo): record packet ids to check if other received complete file. + ft->transferred += length; + + if (length != MAX_FILE_DATA_SIZE || ft->size == ft->transferred) { + ft->status = FILESTATUS_FINISHED; + ft->last_packet_number = ret; + } + + return 0; + } + + return -6; +} + +/** + * Iterate over all file transfers and request chunks (from the client) for each + * of them. + * + * The free_slots parameter is updated by this function. + * + * @param m Our messenger object. + * @param friendnumber The friend we're sending files to. + * @param userdata The client userdata to pass along to chunk request callbacks. + * @param free_slots A pointer to the number of free send queue slots in the + * crypto connection. + * @return true if there's still work to do, false otherwise. + * + */ +non_null() +static bool do_all_filetransfers(Messenger *m, int32_t friendnumber, void *userdata, uint32_t *free_slots) +{ + Friend *const friendcon = &m->friendlist[friendnumber]; + + // Iterate over file transfers as long as we're sending files + for (uint32_t i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) { + if (friendcon->num_sending_files == 0) { + // no active file transfers anymore + return false; + } + + if (*free_slots == 0) { + // send buffer full enough + return false; + } + + struct File_Transfers *const ft = &friendcon->file_sending[i]; + + if (ft->status == FILESTATUS_NONE || ft->status == FILESTATUS_NOT_ACCEPTED) { + // Filetransfers not actively sending, nothing to do + continue; + } + + if (max_speed_reached(m->net_crypto, friend_connection_crypt_connection_id( + m->fr_c, friendcon->friendcon_id))) { + LOGGER_DEBUG(m->log, "maximum connection speed reached"); + // connection doesn't support any more data + return false; + } + + // If the file transfer is complete, we request a chunk of size 0. + if (ft->status == FILESTATUS_FINISHED && friend_received_packet(m, friendnumber, ft->last_packet_number) == 0) { + if (m->file_reqchunk != nullptr) { + m->file_reqchunk(m, friendnumber, i, ft->transferred, 0, userdata); + } + + // Now it's inactive, we're no longer sending this. + ft->status = FILESTATUS_NONE; + --friendcon->num_sending_files; + } else if (ft->status == FILESTATUS_TRANSFERRING && ft->paused == FILE_PAUSE_NOT) { + if (ft->size == 0) { + /* Send 0 data to friend if file is 0 length. */ + send_file_data(m, friendnumber, i, 0, nullptr, 0); + continue; + } + + if (ft->size == ft->requested) { + // This file transfer is done. + continue; + } + + const uint16_t length = min_u64(ft->size - ft->requested, MAX_FILE_DATA_SIZE); + const uint64_t position = ft->requested; + ft->requested += length; + + if (m->file_reqchunk != nullptr) { + m->file_reqchunk(m, friendnumber, i, position, length, userdata); + } + + // The allocated slot is no longer free. + --*free_slots; + } + } + + return true; +} + +non_null(1) nullable(3) +static void do_reqchunk_filecb(Messenger *m, int32_t friendnumber, void *userdata) +{ + // We're not currently doing any file transfers. + if (m->friendlist[friendnumber].num_sending_files == 0) { + return; + } + + // The number of packet slots left in the sendbuffer. + // This is a per friend count (CRYPTO_PACKET_BUFFER_SIZE). + uint32_t free_slots = crypto_num_free_sendqueue_slots( + m->net_crypto, + friend_connection_crypt_connection_id( + m->fr_c, + m->friendlist[friendnumber].friendcon_id)); + + // We keep MIN_SLOTS_FREE slots free for other packets, otherwise file + // transfers might block other traffic for a long time. + free_slots = max_s32(0, (int32_t)free_slots - MIN_SLOTS_FREE); + + // Maximum number of outer loops below. If the client doesn't send file + // chunks from within the chunk request callback handler, we never realise + // that the file transfer has finished and may end up in an infinite loop. + // + // Request up to that number of chunks per file from the client + // + // TODO(Jfreegman): set this cap dynamically + const uint32_t max_ft_loops = 128; + + for (uint32_t i = 0; i < max_ft_loops; ++i) { + if (!do_all_filetransfers(m, friendnumber, userdata, &free_slots)) { + break; + } + + if (free_slots == 0) { + // stop when the buffer is full enough + break; + } + } +} + + +/** @brief Run this when the friend disconnects. + * Kill all current file transfers. + */ +static void break_files(const Messenger *m, int32_t friendnumber) +{ + Friend *const f = &m->friendlist[friendnumber]; + + // TODO(irungentoo): Inform the client which file transfers get killed with a callback? + for (uint32_t i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) { + f->file_sending[i].status = FILESTATUS_NONE; + f->file_receiving[i].status = FILESTATUS_NONE; + } +} + +non_null() +static struct File_Transfers *get_file_transfer(bool outbound, uint8_t filenumber, + uint32_t *real_filenumber, Friend *sender) +{ + struct File_Transfers *ft; + + if (outbound) { + *real_filenumber = filenumber; + ft = &sender->file_sending[filenumber]; + } else { + *real_filenumber = (filenumber + 1) << 16; + ft = &sender->file_receiving[filenumber]; + } + + if (ft->status == FILESTATUS_NONE) { + return nullptr; + } + + return ft; +} + +/** @retval -1 on failure + * @retval 0 on success. + */ +non_null(1, 6) nullable(8) +static int handle_filecontrol(Messenger *m, int32_t friendnumber, bool outbound, uint8_t filenumber, + uint8_t control_type, const uint8_t *data, uint16_t length, void *userdata) +{ + uint32_t real_filenumber; + struct File_Transfers *ft = get_file_transfer(outbound, filenumber, &real_filenumber, &m->friendlist[friendnumber]); + + if (ft == nullptr) { + LOGGER_DEBUG(m->log, "file control (friend %d, file %d): file transfer does not exist; telling the other to kill it", + friendnumber, filenumber); + send_file_control_packet(m, friendnumber, !outbound, filenumber, FILECONTROL_KILL, nullptr, 0); + return -1; + } + + switch (control_type) { + case FILECONTROL_ACCEPT: { + if (outbound && ft->status == FILESTATUS_NOT_ACCEPTED) { + ft->status = FILESTATUS_TRANSFERRING; + ++m->friendlist[friendnumber].num_sending_files; + } else { + if ((ft->paused & FILE_PAUSE_OTHER) != 0) { + ft->paused ^= FILE_PAUSE_OTHER; + } else { + LOGGER_DEBUG(m->log, "file control (friend %d, file %d): friend told us to resume file transfer that wasn't paused", + friendnumber, filenumber); + return -1; + } + } + + if (m->file_filecontrol != nullptr) { + m->file_filecontrol(m, friendnumber, real_filenumber, control_type, userdata); + } + + return 0; + } + + case FILECONTROL_PAUSE: { + if ((ft->paused & FILE_PAUSE_OTHER) != 0 || ft->status != FILESTATUS_TRANSFERRING) { + LOGGER_DEBUG(m->log, "file control (friend %d, file %d): friend told us to pause file transfer that is already paused", + friendnumber, filenumber); + return -1; + } + + ft->paused |= FILE_PAUSE_OTHER; + + if (m->file_filecontrol != nullptr) { + m->file_filecontrol(m, friendnumber, real_filenumber, control_type, userdata); + } + + return 0; + } + + case FILECONTROL_KILL: { + if (m->file_filecontrol != nullptr) { + m->file_filecontrol(m, friendnumber, real_filenumber, control_type, userdata); + } + + if (outbound && (ft->status == FILESTATUS_TRANSFERRING || ft->status == FILESTATUS_FINISHED)) { + --m->friendlist[friendnumber].num_sending_files; + } + + ft->status = FILESTATUS_NONE; + + return 0; + } + + case FILECONTROL_SEEK: { + uint64_t position; + + if (length != sizeof(position)) { + LOGGER_DEBUG(m->log, "file control (friend %d, file %d): expected payload of length %d, but got %d", + friendnumber, filenumber, (uint32_t)sizeof(position), length); + return -1; + } + + /* seek can only be sent by the receiver to seek before resuming broken transfers. */ + if (ft->status != FILESTATUS_NOT_ACCEPTED || !outbound) { + LOGGER_DEBUG(m->log, + "file control (friend %d, file %d): seek was either sent by a sender or by the receiver after accepting", + friendnumber, filenumber); + return -1; + } + + net_unpack_u64(data, &position); + + if (position >= ft->size) { + LOGGER_DEBUG(m->log, + "file control (friend %d, file %d): seek position %ld exceeds file size %ld", + friendnumber, filenumber, (unsigned long)position, (unsigned long)ft->size); + return -1; + } + + ft->requested = position; + ft->transferred = position; + return 0; + } + + default: { + LOGGER_DEBUG(m->log, "file control (friend %d, file %d): invalid file control: %d", + friendnumber, filenumber, control_type); + return -1; + } + } +} + +/** @brief Set the callback for msi packets. */ +void m_callback_msi_packet(Messenger *m, m_msi_packet_cb *function, void *userdata) +{ + m->msi_packet = function; + m->msi_packet_userdata = userdata; +} + +/** @brief Send an msi packet. + * + * @retval true on success + * @retval false on failure + */ +bool m_msi_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length) +{ + return write_cryptpacket_id(m, friendnumber, PACKET_ID_MSI, data, length, false); +} + +static int m_handle_lossy_packet(void *object, int friend_num, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Messenger *m = (Messenger *)object; + + if (!m_friend_exists(m, friend_num)) { + return 1; + } + + if (packet[0] <= PACKET_ID_RANGE_LOSSY_AV_END) { + const RTP_Packet_Handler *const ph = + &m->friendlist[friend_num].lossy_rtp_packethandlers[packet[0] % PACKET_ID_RANGE_LOSSY_AV_SIZE]; + + if (ph->function != nullptr) { + return ph->function(m, friend_num, packet, length, ph->object); + } + + return 1; + } + + if (m->lossy_packethandler != nullptr) { + m->lossy_packethandler(m, friend_num, packet[0], packet, length, userdata); + } + + return 1; +} + +void custom_lossy_packet_registerhandler(Messenger *m, m_friend_lossy_packet_cb *lossy_packethandler) +{ + m->lossy_packethandler = lossy_packethandler; +} + +int m_callback_rtp_packet(Messenger *m, int32_t friendnumber, uint8_t byte, m_lossy_rtp_packet_cb *function, + void *object) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (byte < PACKET_ID_RANGE_LOSSY_AV_START || byte > PACKET_ID_RANGE_LOSSY_AV_END) { + return -1; + } + + m->friendlist[friendnumber].lossy_rtp_packethandlers[byte % PACKET_ID_RANGE_LOSSY_AV_SIZE].function = function; + m->friendlist[friendnumber].lossy_rtp_packethandlers[byte % PACKET_ID_RANGE_LOSSY_AV_SIZE].object = object; + return 0; +} + + +/** @brief High level function to send custom lossy packets. + * + * TODO(oxij): this name is confusing, because this function sends both av and custom lossy packets. + * Meanwhile, m_handle_lossy_packet routes custom packets to custom_lossy_packet_registerhandler + * as you would expect from its name. + * + * I.e. custom_lossy_packet_registerhandler's "custom lossy packet" and this "custom lossy packet" + * are not the same set of packets. + * + * @retval -1 if friend invalid. + * @retval -2 if length wrong. + * @retval -3 if first byte invalid. + * @retval -4 if friend offline. + * @retval -5 if packet failed to send because of other error. + * @retval 0 on success. + */ +int m_send_custom_lossy_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { + return -2; + } + + // TODO(oxij): send_lossy_cryptpacket makes this check already, similarly for other similar places + if (data[0] < PACKET_ID_RANGE_LOSSY_START || data[0] > PACKET_ID_RANGE_LOSSY_END) { + return -3; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -4; + } + + if (send_lossy_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), data, length) == -1) { + return -5; + } + + return 0; +} + +non_null(1, 3) nullable(5) +static int handle_custom_lossless_packet(void *object, int friend_num, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Messenger *m = (Messenger *)object; + + if (!m_friend_exists(m, friend_num)) { + return -1; + } + + if (packet[0] < PACKET_ID_RANGE_LOSSLESS_CUSTOM_START || packet[0] > PACKET_ID_RANGE_LOSSLESS_CUSTOM_END) { + return -1; + } + + if (m->lossless_packethandler != nullptr) { + m->lossless_packethandler(m, friend_num, packet[0], packet, length, userdata); + } + + return 1; +} + +void custom_lossless_packet_registerhandler(Messenger *m, m_friend_lossless_packet_cb *lossless_packethandler) +{ + m->lossless_packethandler = lossless_packethandler; +} + +int send_custom_lossless_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length) +{ + if (!m_friend_exists(m, friendnumber)) { + return -1; + } + + if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { + return -2; + } + + if ((data[0] < PACKET_ID_RANGE_LOSSLESS_CUSTOM_START || data[0] > PACKET_ID_RANGE_LOSSLESS_CUSTOM_END) + && data[0] != PACKET_ID_MSI) { + return -3; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -4; + } + + if (write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), data, length, true) == -1) { + return -5; + } + + return 0; +} + +/** Function to filter out some friend requests*/ +non_null() +static int friend_already_added(const uint8_t *real_pk, void *data) +{ + const Messenger *m = (const Messenger *)data; + + if (getfriend_id(m, real_pk) == -1) { + return 0; + } + + return -1; +} + +/** @brief Check for and handle a timed-out friend request. + * + * If the request has timed-out then the friend status is set back to FRIEND_ADDED. + * @param i friendlist index of the timed-out friend + * @param t time + */ +non_null(1) nullable(4) +static void check_friend_request_timed_out(Messenger *m, uint32_t i, uint64_t t, void *userdata) +{ + Friend *f = &m->friendlist[i]; + + if (f->friendrequest_lastsent + f->friendrequest_timeout < t) { + set_friend_status(m, i, FRIEND_ADDED, userdata); + /* Double the default timeout every time if friendrequest is assumed + * to have been sent unsuccessfully. + */ + f->friendrequest_timeout *= 2; + } +} + +static int m_handle_status(void *object, int i, bool status, void *userdata) +{ + Messenger *m = (Messenger *)object; + + if (status) { /* Went online. */ + send_online_packet(m, i); + } else { /* Went offline. */ + if (m->friendlist[i].status == FRIEND_ONLINE) { + set_friend_status(m, i, FRIEND_CONFIRMED, userdata); + } + } + + return 0; +} + +/* get capabilities of friend's toxcore + * return TOX_CAPABILITY_BASIC on any error + */ +uint64_t m_get_friend_toxcore_capabilities(const Messenger *m, int32_t friendnumber) +{ + if (!m_friend_exists(m, friendnumber)) { + return TOX_CAPABILITY_BASIC; + } + + // return toxcore_capabilities for friend, not matter if ONLINE or OFFLINE + return m->friendlist[friendnumber].toxcore_capabilities; +} + +static int m_handle_packet(void *object, int i, const uint8_t *temp, uint16_t len, void *userdata) +{ + if (len == 0) { + return -1; + } + + Messenger *m = (Messenger *)object; + const uint8_t packet_id = temp[0]; + const uint8_t *data = temp + 1; + const uint16_t data_length = len - 1; + + if (m->friendlist[i].status != FRIEND_ONLINE) { + if (packet_id == PACKET_ID_ONLINE) { + if (len == (TOX_CAPABILITIES_SIZE + 1)) { + uint64_t received_caps; + net_unpack_u64(data, &received_caps); + m->friendlist[i].toxcore_capabilities = received_caps; + LOGGER_DEBUG(m->log, "got capabilties: %llu friendnum: %d", + (long long unsigned int)m->friendlist[i].toxcore_capabilities, (int)i); + } else if (len == 1) { + set_friend_status(m, i, FRIEND_ONLINE, userdata); + send_online_packet(m, i); + LOGGER_DEBUG(m->log, "got online packet for friendnum: %d", (int)i); + } else { + return -1; + } + } else { + return -1; + } + } + + switch (packet_id) { + case PACKET_ID_OFFLINE: { + if (data_length > 0) { + break; + } + + set_friend_status(m, i, FRIEND_CONFIRMED, userdata); + break; + } + + case PACKET_ID_NICKNAME: { + if (data_length > MAX_NAME_LENGTH) { + break; + } + + /* Make sure the NULL terminator is present. */ + VLA(uint8_t, data_terminated, data_length + 1); + memcpy(data_terminated, data, data_length); + data_terminated[data_length] = 0; + + /* inform of namechange before we overwrite the old name */ + if (m->friend_namechange != nullptr) { + m->friend_namechange(m, i, data_terminated, data_length, userdata); + } + + memcpy(m->friendlist[i].name, data_terminated, data_length); + m->friendlist[i].name_length = data_length; + + break; + } + + case PACKET_ID_STATUSMESSAGE: { + if (data_length > MAX_STATUSMESSAGE_LENGTH) { + break; + } + + /* Make sure the NULL terminator is present. */ + VLA(uint8_t, data_terminated, data_length + 1); + memcpy(data_terminated, data, data_length); + data_terminated[data_length] = 0; + + if (m->friend_statusmessagechange != nullptr) { + m->friend_statusmessagechange(m, i, data_terminated, data_length, userdata); + } + + set_friend_statusmessage(m, i, data_terminated, data_length); + break; + } + + case PACKET_ID_USERSTATUS: { + if (data_length != 1) { + break; + } + + const Userstatus status = (Userstatus)data[0]; + + if (status >= USERSTATUS_INVALID) { + break; + } + + if (m->friend_userstatuschange != nullptr) { + m->friend_userstatuschange(m, i, status, userdata); + } + + set_friend_userstatus(m, i, status); + break; + } + + case PACKET_ID_TYPING: { + if (data_length != 1) { + break; + } + + const bool typing = data[0] != 0; + + set_friend_typing(m, i, typing); + + if (m->friend_typingchange != nullptr) { + m->friend_typingchange(m, i, typing, userdata); + } + + break; + } + + case PACKET_ID_MESSAGE: // fall-through + case PACKET_ID_ACTION: + case PACKET_ID_HIGH_LEVEL_ACK: { + if (data_length == 0) { + break; + } + + const uint8_t *message = data; + const uint16_t message_length = data_length; + + /* Make sure the NULL terminator is present. */ + VLA(uint8_t, message_terminated, message_length + 1); + memcpy(message_terminated, message, message_length); + message_terminated[message_length] = 0; + const uint8_t type = packet_id - PACKET_ID_MESSAGE; + + if (m->friend_message != nullptr) { + m->friend_message(m, i, type, message_terminated, message_length, userdata); + } + + break; + } + + case PACKET_ID_INVITE_CONFERENCE: { + if (data_length == 0) { + break; + } + + if (m->conference_invite != nullptr) { + m->conference_invite(m, i, data, data_length, userdata); + } + + break; + } + + case PACKET_ID_FILE_SENDREQUEST: { + const unsigned int head_length = 1 + sizeof(uint32_t) + sizeof(uint64_t) + FILE_ID_LENGTH; + + if (data_length < head_length) { + break; + } + + const uint8_t filenumber = data[0]; + +#if UINT8_MAX >= MAX_CONCURRENT_FILE_PIPES + + if (filenumber >= MAX_CONCURRENT_FILE_PIPES) { + break; + } + +#endif + + uint64_t filesize; + uint32_t file_type; + const uint16_t filename_length = data_length - head_length; + + if (filename_length > MAX_FILENAME_LENGTH) { + break; + } + + memcpy(&file_type, data + 1, sizeof(file_type)); + file_type = net_ntohl(file_type); + + net_unpack_u64(data + 1 + sizeof(uint32_t), &filesize); + struct File_Transfers *ft = &m->friendlist[i].file_receiving[filenumber]; + + if (ft->status != FILESTATUS_NONE) { + break; + } + + ft->status = FILESTATUS_NOT_ACCEPTED; + ft->size = filesize; + ft->transferred = 0; + ft->paused = FILE_PAUSE_NOT; + memcpy(ft->id, data + 1 + sizeof(uint32_t) + sizeof(uint64_t), FILE_ID_LENGTH); + + VLA(uint8_t, filename_terminated, filename_length + 1); + const uint8_t *filename = nullptr; + + if (filename_length > 0) { + /* Force NULL terminate file name. */ + memcpy(filename_terminated, data + head_length, filename_length); + filename_terminated[filename_length] = 0; + filename = filename_terminated; + } + + uint32_t real_filenumber = filenumber; + real_filenumber += 1; + real_filenumber <<= 16; + + if (m->file_sendrequest != nullptr) { + m->file_sendrequest(m, i, real_filenumber, file_type, filesize, filename, filename_length, + userdata); + } + + break; + } + + case PACKET_ID_FILE_CONTROL: { + if (data_length < 3) { + break; + } + + // On the other side, "outbound" is "inbound", i.e. if they send 1, + // that means "inbound" on their side, but we call it "outbound" + // here. + const bool outbound = data[0] == 1; + uint8_t filenumber = data[1]; + const uint8_t control_type = data[2]; + +#if UINT8_MAX >= MAX_CONCURRENT_FILE_PIPES + + if (filenumber >= MAX_CONCURRENT_FILE_PIPES) { + break; + } + +#endif + + if (handle_filecontrol(m, i, outbound, filenumber, control_type, data + 3, data_length - 3, userdata) == -1) { + // TODO(iphydf): Do something different here? Right now, this + // check is pointless. + break; + } + + break; + } + + case PACKET_ID_FILE_DATA: { + if (data_length < 1) { + break; + } + + uint8_t filenumber = data[0]; + +#if UINT8_MAX >= MAX_CONCURRENT_FILE_PIPES + + if (filenumber >= MAX_CONCURRENT_FILE_PIPES) { + break; + } + +#endif + + struct File_Transfers *ft = &m->friendlist[i].file_receiving[filenumber]; + + if (ft->status != FILESTATUS_TRANSFERRING) { + break; + } + + uint64_t position = ft->transferred; + uint32_t real_filenumber = filenumber; + real_filenumber += 1; + real_filenumber <<= 16; + uint16_t file_data_length = data_length - 1; + const uint8_t *file_data; + + if (file_data_length == 0) { + file_data = nullptr; + } else { + file_data = data + 1; + } + + /* Prevent more data than the filesize from being passed to clients. */ + if ((ft->transferred + file_data_length) > ft->size) { + file_data_length = ft->size - ft->transferred; + } + + if (m->file_filedata != nullptr) { + m->file_filedata(m, i, real_filenumber, position, file_data, file_data_length, userdata); + } + + ft->transferred += file_data_length; + + if (file_data_length > 0 && (ft->transferred >= ft->size || file_data_length != MAX_FILE_DATA_SIZE)) { + file_data_length = 0; + file_data = nullptr; + position = ft->transferred; + + /* Full file received. */ + if (m->file_filedata != nullptr) { + m->file_filedata(m, i, real_filenumber, position, file_data, file_data_length, userdata); + } + } + + /* Data is zero, filetransfer is over. */ + if (file_data_length == 0) { + ft->status = FILESTATUS_NONE; + } + + break; + } + + case PACKET_ID_MSI: { + if (data_length == 0) { + break; + } + + if (m->msi_packet != nullptr) { + m->msi_packet(m, i, data, data_length, m->msi_packet_userdata); + } + + break; + } + + default: { + handle_custom_lossless_packet(object, i, temp, len, userdata); + break; + } + } + + return 0; +} + +non_null(1) nullable(2) +static void do_friends(Messenger *m, void *userdata) +{ + const uint64_t temp_time = mono_time_get(m->mono_time); + + for (uint32_t i = 0; i < m->numfriends; ++i) { + if (m->friendlist[i].status == FRIEND_ADDED) { + const int fr = send_friend_request_packet(m->fr_c, m->friendlist[i].friendcon_id, m->friendlist[i].friendrequest_nospam, + m->friendlist[i].info, + m->friendlist[i].info_size); + + if (fr >= 0) { + set_friend_status(m, i, FRIEND_REQUESTED, userdata); + m->friendlist[i].friendrequest_lastsent = temp_time; + } + } + + if (m->friendlist[i].status == FRIEND_REQUESTED + || m->friendlist[i].status == FRIEND_CONFIRMED) { /* friend is not online. */ + if (m->friendlist[i].status == FRIEND_REQUESTED) { + /* If we didn't connect to friend after successfully sending him a friend request the request is deemed + * unsuccessful so we set the status back to FRIEND_ADDED and try again. + */ + check_friend_request_timed_out(m, i, temp_time, userdata); + } + } + + if (m->friendlist[i].status == FRIEND_ONLINE) { /* friend is online. */ + if (!m->friendlist[i].name_sent) { + if (m_sendname(m, i, m->name, m->name_length)) { + m->friendlist[i].name_sent = true; + } + } + + if (!m->friendlist[i].statusmessage_sent) { + if (send_statusmessage(m, i, m->statusmessage, m->statusmessage_length)) { + m->friendlist[i].statusmessage_sent = true; + } + } + + if (!m->friendlist[i].userstatus_sent) { + if (send_userstatus(m, i, m->userstatus)) { + m->friendlist[i].userstatus_sent = true; + } + } + + if (!m->friendlist[i].user_istyping_sent) { + if (send_user_istyping(m, i, m->friendlist[i].user_istyping)) { + m->friendlist[i].user_istyping_sent = true; + } + } + + check_friend_tcp_udp(m, i, userdata); + do_receipts(m, i, userdata); + do_reqchunk_filecb(m, i, userdata); + + m->friendlist[i].last_seen_time = (uint64_t) time(nullptr); + } + } +} + +non_null(1) nullable(2) +static void m_connection_status_callback(Messenger *m, void *userdata) +{ + const Onion_Connection_Status conn_status = onion_connection_status(m->onion_c); + + if (conn_status != m->last_connection_status) { + if (m->core_connection_change != nullptr) { + m->core_connection_change(m, conn_status, userdata); + } + + m->last_connection_status = conn_status; + } +} + + +#define DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS 60UL + +#define IDSTRING_LEN (CRYPTO_PUBLIC_KEY_SIZE * 2 + 1) +/** id_str should be of length at least IDSTRING_LEN */ +non_null() +static char *id_to_string(const uint8_t *pk, char *id_str, size_t length) +{ + if (length < IDSTRING_LEN) { + snprintf(id_str, length, "Bad buf length"); + return id_str; + } + + for (uint32_t i = 0; i < CRYPTO_PUBLIC_KEY_SIZE; ++i) { + snprintf(&id_str[i * 2], length - i * 2, "%02X", pk[i]); + } + + id_str[CRYPTO_PUBLIC_KEY_SIZE * 2] = '\0'; + return id_str; +} + +/** @brief Minimum messenger run interval in ms + * TODO(mannol): A/V + */ +#define MIN_RUN_INTERVAL 50 + +/** + * @brief Return the time in milliseconds before `do_messenger()` should be called again + * for optimal performance. + * + * @return time (in ms) before the next `do_messenger()` needs to be run on success. + */ +uint32_t messenger_run_interval(const Messenger *m) +{ + const uint32_t crypto_interval = crypto_run_interval(m->net_crypto); + + if (crypto_interval > MIN_RUN_INTERVAL) { + return MIN_RUN_INTERVAL; + } + + return crypto_interval; +} + +/** @brief The main loop that needs to be run at least 20 times per second. */ +void do_messenger(Messenger *m, void *userdata) +{ + // Add the TCP relays, but only if this is the first time calling do_messenger + if (!m->has_added_relays) { + m->has_added_relays = true; + + for (uint16_t i = 0; i < m->num_loaded_relays; ++i) { + add_tcp_relay(m->net_crypto, &m->loaded_relays[i].ip_port, m->loaded_relays[i].public_key); + } + + m->num_loaded_relays = 0; + + if (m->tcp_server != nullptr) { + /* Add self tcp server. */ + IP_Port local_ip_port; + local_ip_port.port = m->options.tcp_server_port; + local_ip_port.ip.family = net_family_ipv4(); + local_ip_port.ip.ip.v4 = get_ip4_loopback(); + add_tcp_relay(m->net_crypto, &local_ip_port, tcp_server_public_key(m->tcp_server)); + } + } + + if (!m->options.udp_disabled) { + networking_poll(m->net, userdata); + do_dht(m->dht); + } + + if (m->tcp_server != nullptr) { + do_TCP_server(m->tcp_server, m->mono_time); + } + + do_net_crypto(m->net_crypto, userdata); + do_onion_client(m->onion_c); + do_friend_connections(m->fr_c, userdata); + do_friends(m, userdata); + m_connection_status_callback(m, userdata); + + if (mono_time_get(m->mono_time) > m->lastdump + DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS) { + m->lastdump = mono_time_get(m->mono_time); + uint32_t last_pinged; + + for (uint32_t client = 0; client < LCLIENT_LIST; ++client) { + const Client_data *cptr = dht_get_close_client(m->dht, client); + const IPPTsPng *const assocs[] = { &cptr->assoc4, &cptr->assoc6, nullptr }; + + for (const IPPTsPng * const *it = assocs; *it != nullptr; ++it) { + const IPPTsPng *const assoc = *it; + + if (ip_isset(&assoc->ip_port.ip)) { + last_pinged = m->lastdump - assoc->last_pinged; + + if (last_pinged > 999) { + last_pinged = 999; + } + + Ip_Ntoa ip_str; + char id_str[IDSTRING_LEN]; + LOGGER_TRACE(m->log, "C[%2u] %s:%u [%3u] %s", + client, net_ip_ntoa(&assoc->ip_port.ip, &ip_str), + net_ntohs(assoc->ip_port.port), last_pinged, + id_to_string(cptr->public_key, id_str, sizeof(id_str))); + } + } + } + + + /* dht contains additional "friends" (requests) */ + const uint32_t num_dhtfriends = dht_get_num_friends(m->dht); + VLA(int32_t, m2dht, num_dhtfriends); + VLA(int32_t, dht2m, num_dhtfriends); + + for (uint32_t friend_idx = 0; friend_idx < num_dhtfriends; ++friend_idx) { + m2dht[friend_idx] = -1; + dht2m[friend_idx] = -1; + + if (friend_idx >= m->numfriends) { + continue; + } + + for (uint32_t dhtfriend = 0; dhtfriend < dht_get_num_friends(m->dht); ++dhtfriend) { + if (pk_equal(m->friendlist[friend_idx].real_pk, dht_get_friend_public_key(m->dht, dhtfriend))) { + assert(dhtfriend < INT32_MAX); + m2dht[friend_idx] = (int32_t)dhtfriend; + break; + } + } + } + + for (uint32_t friend_idx = 0; friend_idx < num_dhtfriends; ++friend_idx) { + if (m2dht[friend_idx] >= 0) { + assert(friend_idx < INT32_MAX); + dht2m[m2dht[friend_idx]] = (int32_t)friend_idx; + } + } + + if (m->numfriends != dht_get_num_friends(m->dht)) { + LOGGER_TRACE(m->log, "Friend num in DHT %u != friend num in msger %u", dht_get_num_friends(m->dht), m->numfriends); + } + + for (uint32_t friend_idx = 0; friend_idx < num_dhtfriends; ++friend_idx) { + const Friend *const msgfptr = dht2m[friend_idx] >= 0 ? &m->friendlist[dht2m[friend_idx]] : nullptr; + const DHT_Friend *const dhtfptr = dht_get_friend(m->dht, friend_idx); + + if (msgfptr != nullptr) { + char id_str[IDSTRING_LEN]; + LOGGER_TRACE(m->log, "F[%2u:%2u] <%s> %s", + dht2m[friend_idx], friend_idx, msgfptr->name, + id_to_string(msgfptr->real_pk, id_str, sizeof(id_str))); + } else { + char id_str[IDSTRING_LEN]; + LOGGER_TRACE(m->log, "F[--:%2u] %s", friend_idx, + id_to_string(dht_friend_public_key(dhtfptr), id_str, sizeof(id_str))); + } + + for (uint32_t client = 0; client < MAX_FRIEND_CLIENTS; ++client) { + const Client_data *cptr = dht_friend_client(dhtfptr, client); + const IPPTsPng *const assocs[] = {&cptr->assoc4, &cptr->assoc6}; + + for (size_t a = 0; a < sizeof(assocs) / sizeof(assocs[0]); ++a) { + const IPPTsPng *const assoc = assocs[a]; + + if (ip_isset(&assoc->ip_port.ip)) { + last_pinged = m->lastdump - assoc->last_pinged; + + if (last_pinged > 999) { + last_pinged = 999; + } + + Ip_Ntoa ip_str; + char id_str[IDSTRING_LEN]; + LOGGER_TRACE(m->log, "F[%2u] => C[%2u] %s:%u [%3u] %s", + friend_idx, client, net_ip_ntoa(&assoc->ip_port.ip, &ip_str), + net_ntohs(assoc->ip_port.port), last_pinged, + id_to_string(cptr->public_key, id_str, sizeof(id_str))); + } + } + } + } + } +} + +/** new messenger format for load/save, more robust and forward compatible */ + +#define SAVED_FRIEND_REQUEST_SIZE 1024 +#define NUM_SAVED_PATH_NODES 8 + +struct Saved_Friend { + uint8_t status; + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t info[SAVED_FRIEND_REQUEST_SIZE]; // the data that is sent during the friend requests we do. + uint16_t info_size; // Length of the info. + uint8_t name[MAX_NAME_LENGTH]; + uint16_t name_length; + uint8_t statusmessage[MAX_STATUSMESSAGE_LENGTH]; + uint16_t statusmessage_length; + uint8_t userstatus; + uint32_t friendrequest_nospam; + uint8_t last_seen_time[sizeof(uint64_t)]; +}; + +static uint32_t friend_size(void) +{ + uint32_t data = 0; + const struct Saved_Friend *const temp = nullptr; + +#define VALUE_MEMBER(data, name) \ + do { \ + data += sizeof(name); \ + } while (0) +#define ARRAY_MEMBER(data, name) \ + do { \ + data += sizeof(name); \ + } while (0) + + // Exactly the same in friend_load, friend_save, and friend_size + VALUE_MEMBER(data, temp->status); + ARRAY_MEMBER(data, temp->real_pk); + ARRAY_MEMBER(data, temp->info); + ++data; // padding + VALUE_MEMBER(data, temp->info_size); + ARRAY_MEMBER(data, temp->name); + VALUE_MEMBER(data, temp->name_length); + ARRAY_MEMBER(data, temp->statusmessage); + ++data; // padding + VALUE_MEMBER(data, temp->statusmessage_length); + VALUE_MEMBER(data, temp->userstatus); + data += 3; // padding + VALUE_MEMBER(data, temp->friendrequest_nospam); + ARRAY_MEMBER(data, temp->last_seen_time); + +#undef VALUE_MEMBER +#undef ARRAY_MEMBER + + return data; +} + +non_null() +static uint8_t *friend_save(const struct Saved_Friend *temp, uint8_t *data) +{ +#define VALUE_MEMBER(data, name) \ + do { \ + memcpy(data, &name, sizeof(name)); \ + data += sizeof(name); \ + } while (0) + +#define ARRAY_MEMBER(data, name) \ + do { \ + memcpy(data, name, sizeof(name)); \ + data += sizeof(name); \ + } while (0) + + // Exactly the same in friend_load, friend_save, and friend_size + VALUE_MEMBER(data, temp->status); + ARRAY_MEMBER(data, temp->real_pk); + ARRAY_MEMBER(data, temp->info); + ++data; // padding + VALUE_MEMBER(data, temp->info_size); + ARRAY_MEMBER(data, temp->name); + VALUE_MEMBER(data, temp->name_length); + ARRAY_MEMBER(data, temp->statusmessage); + ++data; // padding + VALUE_MEMBER(data, temp->statusmessage_length); + VALUE_MEMBER(data, temp->userstatus); + data += 3; // padding + VALUE_MEMBER(data, temp->friendrequest_nospam); + ARRAY_MEMBER(data, temp->last_seen_time); + +#undef VALUE_MEMBER +#undef ARRAY_MEMBER + + return data; +} + + +non_null() +static const uint8_t *friend_load(struct Saved_Friend *temp, const uint8_t *data) +{ +#define VALUE_MEMBER(data, name) \ + do { \ + memcpy(&name, data, sizeof(name)); \ + data += sizeof(name); \ + } while (0) + +#define ARRAY_MEMBER(data, name) \ + do { \ + memcpy(name, data, sizeof(name)); \ + data += sizeof(name); \ + } while (0) + + // Exactly the same in friend_load, friend_save, and friend_size + VALUE_MEMBER(data, temp->status); + ARRAY_MEMBER(data, temp->real_pk); + ARRAY_MEMBER(data, temp->info); + ++data; // padding + VALUE_MEMBER(data, temp->info_size); + ARRAY_MEMBER(data, temp->name); + VALUE_MEMBER(data, temp->name_length); + ARRAY_MEMBER(data, temp->statusmessage); + ++data; // padding + VALUE_MEMBER(data, temp->statusmessage_length); + VALUE_MEMBER(data, temp->userstatus); + data += 3; // padding + VALUE_MEMBER(data, temp->friendrequest_nospam); + ARRAY_MEMBER(data, temp->last_seen_time); + +#undef VALUE_MEMBER +#undef ARRAY_MEMBER + + return data; +} + + +non_null() +static uint32_t m_state_plugins_size(const Messenger *m) +{ + const uint32_t size32 = sizeof(uint32_t); + const uint32_t sizesubhead = size32 * 2; + + uint32_t size = 0; + + for (const Messenger_State_Plugin *plugin = m->options.state_plugins; + plugin != m->options.state_plugins + m->options.state_plugins_length; + ++plugin) { + size += sizesubhead + plugin->size(m); + } + + return size; +} + +/** @brief Registers a state plugin for saving, loading, and getting the size of a section of the save. + * + * @retval true on success + * @retval false on error + */ +bool m_register_state_plugin(Messenger *m, State_Type type, m_state_size_cb *size_callback, + m_state_load_cb *load_callback, + m_state_save_cb *save_callback) +{ + Messenger_State_Plugin *temp = (Messenger_State_Plugin *)realloc(m->options.state_plugins, + sizeof(Messenger_State_Plugin) * (m->options.state_plugins_length + 1)); + + if (temp == nullptr) { + return false; + } + + m->options.state_plugins = temp; + ++m->options.state_plugins_length; + + const uint8_t index = m->options.state_plugins_length - 1; + m->options.state_plugins[index].type = type; + m->options.state_plugins[index].size = size_callback; + m->options.state_plugins[index].load = load_callback; + m->options.state_plugins[index].save = save_callback; + + return true; +} + +non_null() +static uint32_t m_plugin_size(const Messenger *m, State_Type type) +{ + for (uint8_t i = 0; i < m->options.state_plugins_length; ++i) { + const Messenger_State_Plugin plugin = m->options.state_plugins[i]; + + if (plugin.type == type) { + return plugin.size(m); + } + } + + LOGGER_ERROR(m->log, "Unknown type encountered: %u", type); + + return UINT32_MAX; +} + +/** return size of the messenger data (for saving). */ +uint32_t messenger_size(const Messenger *m) +{ + return m_state_plugins_size(m); +} + +/** Save the messenger in data (must be allocated memory of size at least `Messenger_size()`) */ +uint8_t *messenger_save(const Messenger *m, uint8_t *data) +{ + for (uint8_t i = 0; i < m->options.state_plugins_length; ++i) { + const Messenger_State_Plugin plugin = m->options.state_plugins[i]; + data = plugin.save(m, data); + } + + return data; +} + +// nospam state plugin +non_null() +static uint32_t nospam_keys_size(const Messenger *m) +{ + return sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE; +} + +non_null() +static State_Load_Status load_nospam_keys(Messenger *m, const uint8_t *data, uint32_t length) +{ + if (length != m_plugin_size(m, STATE_TYPE_NOSPAMKEYS)) { + return STATE_LOAD_STATUS_ERROR; + } + + uint32_t nospam; + lendian_bytes_to_host32(&nospam, data); + set_nospam(m->fr, nospam); + load_secret_key(m->net_crypto, data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE); + + if (!pk_equal(data + sizeof(uint32_t), nc_get_self_public_key(m->net_crypto))) { + return STATE_LOAD_STATUS_ERROR; + } + + return STATE_LOAD_STATUS_CONTINUE; +} + +non_null() +static uint8_t *save_nospam_keys(const Messenger *m, uint8_t *data) +{ + const uint32_t len = m_plugin_size(m, STATE_TYPE_NOSPAMKEYS); + static_assert(sizeof(get_nospam(m->fr)) == sizeof(uint32_t), "nospam doesn't fit in a 32 bit int"); + data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_NOSPAMKEYS); + const uint32_t nospam = get_nospam(m->fr); + host_to_lendian_bytes32(data, nospam); + save_keys(m->net_crypto, data + sizeof(uint32_t)); + data += len; + return data; +} + +// DHT state plugin +non_null() +static uint32_t m_dht_size(const Messenger *m) +{ + return dht_size(m->dht); +} + +non_null() +static uint8_t *save_dht(const Messenger *m, uint8_t *data) +{ + const uint32_t len = m_plugin_size(m, STATE_TYPE_DHT); + data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_DHT); + dht_save(m->dht, data); + data += len; + return data; +} + +non_null() +static State_Load_Status m_dht_load(Messenger *m, const uint8_t *data, uint32_t length) +{ + dht_load(m->dht, data, length); // TODO(endoffile78): Should we throw an error if dht_load fails? + return STATE_LOAD_STATUS_CONTINUE; +} + +// friendlist state plugin +non_null() +static uint32_t saved_friendslist_size(const Messenger *m) +{ + return count_friendlist(m) * friend_size(); +} + +non_null() +static uint8_t *friends_list_save(const Messenger *m, uint8_t *data) +{ + const uint32_t len = m_plugin_size(m, STATE_TYPE_FRIENDS); + data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_FRIENDS); + + uint32_t num = 0; + uint8_t *cur_data = data; + + for (uint32_t i = 0; i < m->numfriends; ++i) { + if (m->friendlist[i].status > 0) { + struct Saved_Friend temp = { 0 }; + temp.status = m->friendlist[i].status; + memcpy(temp.real_pk, m->friendlist[i].real_pk, CRYPTO_PUBLIC_KEY_SIZE); + + if (temp.status < 3) { + // TODO(iphydf): Use uint16_t and min_u16 here. + const size_t friendrequest_length = + min_u32(m->friendlist[i].info_size, + min_u32(SAVED_FRIEND_REQUEST_SIZE, MAX_FRIEND_REQUEST_DATA_SIZE)); + memcpy(temp.info, m->friendlist[i].info, friendrequest_length); + + temp.info_size = net_htons(m->friendlist[i].info_size); + temp.friendrequest_nospam = m->friendlist[i].friendrequest_nospam; + } else { + temp.status = 3; + memcpy(temp.name, m->friendlist[i].name, m->friendlist[i].name_length); + temp.name_length = net_htons(m->friendlist[i].name_length); + memcpy(temp.statusmessage, m->friendlist[i].statusmessage, m->friendlist[i].statusmessage_length); + temp.statusmessage_length = net_htons(m->friendlist[i].statusmessage_length); + temp.userstatus = m->friendlist[i].userstatus; + + net_pack_u64(temp.last_seen_time, m->friendlist[i].last_seen_time); + } + + uint8_t *next_data = friend_save(&temp, cur_data); + assert(next_data - cur_data == friend_size()); +#ifdef __LP64__ + assert(memcmp(cur_data, &temp, friend_size()) == 0); +#endif + cur_data = next_data; + ++num; + } + } + + assert(cur_data - data == num * friend_size()); + data += len; + + return data; +} + +non_null() +static State_Load_Status friends_list_load(Messenger *m, const uint8_t *data, uint32_t length) +{ + const uint32_t l_friend_size = friend_size(); + + if (length % l_friend_size != 0) { + return STATE_LOAD_STATUS_ERROR; // TODO(endoffile78): error or continue? + } + + const uint32_t num = length / l_friend_size; + const uint8_t *cur_data = data; + + for (uint32_t i = 0; i < num; ++i) { + struct Saved_Friend temp = { 0 }; + const uint8_t *next_data = friend_load(&temp, cur_data); + assert(next_data - cur_data == l_friend_size); + + cur_data = next_data; + + if (temp.status >= 3) { + const int fnum = m_addfriend_norequest(m, temp.real_pk); + + if (fnum < 0) { + continue; + } + + setfriendname(m, fnum, temp.name, net_ntohs(temp.name_length)); + set_friend_statusmessage(m, fnum, temp.statusmessage, net_ntohs(temp.statusmessage_length)); + set_friend_userstatus(m, fnum, temp.userstatus); + net_unpack_u64(temp.last_seen_time, &m->friendlist[fnum].last_seen_time); + } else if (temp.status != 0) { + /* TODO(irungentoo): This is not a good way to do this. */ + uint8_t address[FRIEND_ADDRESS_SIZE]; + pk_copy(address, temp.real_pk); + memcpy(address + CRYPTO_PUBLIC_KEY_SIZE, &temp.friendrequest_nospam, sizeof(uint32_t)); + uint16_t checksum = data_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum)); + memcpy(address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t), &checksum, sizeof(checksum)); + m_addfriend(m, address, temp.info, net_ntohs(temp.info_size)); + } + } + + return STATE_LOAD_STATUS_CONTINUE; +} + +// name state plugin +non_null() +static uint32_t name_size(const Messenger *m) +{ + return m->name_length; +} + +non_null() +static uint8_t *save_name(const Messenger *m, uint8_t *data) +{ + const uint32_t len = m_plugin_size(m, STATE_TYPE_NAME); + data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_NAME); + memcpy(data, m->name, len); + data += len; + return data; +} + +non_null() +static State_Load_Status load_name(Messenger *m, const uint8_t *data, uint32_t length) +{ + if (length > 0 && length <= MAX_NAME_LENGTH) { + setname(m, data, length); + } + + return STATE_LOAD_STATUS_CONTINUE; +} + +// status message state plugin +non_null() +static uint32_t status_message_size(const Messenger *m) +{ + return m->statusmessage_length; +} + +non_null() +static uint8_t *save_status_message(const Messenger *m, uint8_t *data) +{ + const uint32_t len = m_plugin_size(m, STATE_TYPE_STATUSMESSAGE); + data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_STATUSMESSAGE); + memcpy(data, m->statusmessage, len); + data += len; + return data; +} + +non_null() +static State_Load_Status load_status_message(Messenger *m, const uint8_t *data, uint32_t length) +{ + if (length > 0 && length <= MAX_STATUSMESSAGE_LENGTH) { + m_set_statusmessage(m, data, length); + } + + return STATE_LOAD_STATUS_CONTINUE; +} + +// status state plugin +non_null() +static uint32_t status_size(const Messenger *m) +{ + return 1; +} + +non_null() +static uint8_t *save_status(const Messenger *m, uint8_t *data) +{ + const uint32_t len = m_plugin_size(m, STATE_TYPE_STATUS); + data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_STATUS); + *data = m->userstatus; + data += len; + return data; +} + +non_null() +static State_Load_Status load_status(Messenger *m, const uint8_t *data, uint32_t length) +{ + if (length == 1) { + m_set_userstatus(m, *data); + } + + return STATE_LOAD_STATUS_CONTINUE; +} + +// TCP Relay state plugin +non_null() +static uint32_t tcp_relay_size(const Messenger *m) +{ + return NUM_SAVED_TCP_RELAYS * packed_node_size(net_family_tcp_ipv6()); +} + +non_null() +static uint8_t *save_tcp_relays(const Messenger *m, uint8_t *data) +{ + Node_format relays[NUM_SAVED_TCP_RELAYS] = {{{0}}}; + uint8_t *temp_data = data; + data = state_write_section_header(temp_data, STATE_COOKIE_TYPE, 0, STATE_TYPE_TCP_RELAY); + + if (m->num_loaded_relays > 0) { + memcpy(relays, m->loaded_relays, sizeof(Node_format) * m->num_loaded_relays); + } + + uint32_t num = m->num_loaded_relays; + num += copy_connected_tcp_relays(m->net_crypto, relays + num, NUM_SAVED_TCP_RELAYS - num); + + const int l = pack_nodes(m->log, data, NUM_SAVED_TCP_RELAYS * packed_node_size(net_family_tcp_ipv6()), relays, num); + + if (l > 0) { + const uint32_t len = l; + data = state_write_section_header(temp_data, STATE_COOKIE_TYPE, len, STATE_TYPE_TCP_RELAY); + data += len; + } + + return data; +} + +non_null() +static State_Load_Status load_tcp_relays(Messenger *m, const uint8_t *data, uint32_t length) +{ + if (length > 0) { + const int num = unpack_nodes(m->loaded_relays, NUM_SAVED_TCP_RELAYS, nullptr, data, length, true); + + if (num == -1) { + m->num_loaded_relays = 0; + return STATE_LOAD_STATUS_CONTINUE; + } + + m->num_loaded_relays = num; + m->has_added_relays = false; + } + + return STATE_LOAD_STATUS_CONTINUE; +} + +// path node state plugin +non_null() +static uint32_t path_node_size(const Messenger *m) +{ + return NUM_SAVED_PATH_NODES * packed_node_size(net_family_tcp_ipv6()); +} + +non_null() +static uint8_t *save_path_nodes(const Messenger *m, uint8_t *data) +{ + Node_format nodes[NUM_SAVED_PATH_NODES]; + uint8_t *temp_data = data; + data = state_write_section_header(data, STATE_COOKIE_TYPE, 0, STATE_TYPE_PATH_NODE); + memset(nodes, 0, sizeof(nodes)); + const unsigned int num = onion_backup_nodes(m->onion_c, nodes, NUM_SAVED_PATH_NODES); + const int l = pack_nodes(m->log, data, NUM_SAVED_PATH_NODES * packed_node_size(net_family_tcp_ipv6()), nodes, num); + + if (l > 0) { + const uint32_t len = l; + data = state_write_section_header(temp_data, STATE_COOKIE_TYPE, len, STATE_TYPE_PATH_NODE); + data += len; + } + + return data; +} + +non_null() +static State_Load_Status load_path_nodes(Messenger *m, const uint8_t *data, uint32_t length) +{ + if (length > 0) { + Node_format nodes[NUM_SAVED_PATH_NODES]; + const int num = unpack_nodes(nodes, NUM_SAVED_PATH_NODES, nullptr, data, length, false); + + if (num == -1) { + return STATE_LOAD_STATUS_CONTINUE; + } + + for (int i = 0; i < num; ++i) { + onion_add_bs_path_node(m->onion_c, &nodes[i].ip_port, nodes[i].public_key); + } + } + + return STATE_LOAD_STATUS_CONTINUE; +} + +non_null() +static void m_register_default_plugins(Messenger *m) +{ + m_register_state_plugin(m, STATE_TYPE_NOSPAMKEYS, nospam_keys_size, load_nospam_keys, save_nospam_keys); + m_register_state_plugin(m, STATE_TYPE_DHT, m_dht_size, m_dht_load, save_dht); + m_register_state_plugin(m, STATE_TYPE_FRIENDS, saved_friendslist_size, friends_list_load, friends_list_save); + m_register_state_plugin(m, STATE_TYPE_NAME, name_size, load_name, save_name); + m_register_state_plugin(m, STATE_TYPE_STATUSMESSAGE, status_message_size, load_status_message, + save_status_message); + m_register_state_plugin(m, STATE_TYPE_STATUS, status_size, load_status, save_status); + m_register_state_plugin(m, STATE_TYPE_TCP_RELAY, tcp_relay_size, load_tcp_relays, save_tcp_relays); + m_register_state_plugin(m, STATE_TYPE_PATH_NODE, path_node_size, load_path_nodes, save_path_nodes); +} + +bool messenger_load_state_section(Messenger *m, const uint8_t *data, uint32_t length, uint16_t type, + State_Load_Status *status) +{ + for (uint8_t i = 0; i < m->options.state_plugins_length; ++i) { + const Messenger_State_Plugin *const plugin = &m->options.state_plugins[i]; + + if (plugin->type == type) { + *status = plugin->load(m, data, length); + return true; + } + } + + return false; +} + +/** @brief Return the number of friends in the instance m. + * + * You should use this to determine how much memory to allocate + * for copy_friendlist. + */ +uint32_t count_friendlist(const Messenger *m) +{ + uint32_t ret = 0; + + for (uint32_t i = 0; i < m->numfriends; ++i) { + if (m->friendlist[i].status > 0) { + ++ret; + } + } + + return ret; +} + +/** @brief Copy a list of valid friend IDs into the array out_list. + * If out_list is NULL, returns 0. + * Otherwise, returns the number of elements copied. + * If the array was too small, the contents + * of out_list will be truncated to list_size. + */ +uint32_t copy_friendlist(Messenger const *m, uint32_t *out_list, uint32_t list_size) +{ + if (out_list == nullptr) { + return 0; + } + + if (m->numfriends == 0) { + return 0; + } + + uint32_t ret = 0; + + for (uint32_t i = 0; i < m->numfriends; ++i) { + if (ret >= list_size) { + break; /* Abandon ship */ + } + + if (m->friendlist[i].status > 0) { + out_list[ret] = i; + ++ret; + } + } + + return ret; +} + +static fr_friend_request_cb m_handle_friend_request; +non_null(1, 2, 3) nullable(5) +static void m_handle_friend_request( + void *object, const uint8_t *public_key, const uint8_t *message, size_t length, void *user_data) +{ + Messenger *m = (Messenger *)object; + assert(m != nullptr); + m->friend_request(m, public_key, message, length, user_data); +} + +/** @brief Run this at startup. + * + * @return allocated instance of Messenger on success. + * @retval 0 if there are problems. + * + * if error is not NULL it will be set to one of the values in the enum above. + */ +Messenger *new_messenger(Mono_Time *mono_time, const Random *rng, const Network *ns, Messenger_Options *options, Messenger_Error *error) +{ + if (options == nullptr) { + return nullptr; + } + + if (error != nullptr) { + *error = MESSENGER_ERROR_OTHER; + } + + Messenger *m = (Messenger *)calloc(1, sizeof(Messenger)); + + if (m == nullptr) { + return nullptr; + } + + m->mono_time = mono_time; + m->rng = rng; + m->ns = ns; + + m->fr = friendreq_new(); + + if (m->fr == nullptr) { + free(m); + return nullptr; + } + + m->log = logger_new(); + + if (m->log == nullptr) { + friendreq_kill(m->fr); + free(m); + return nullptr; + } + + logger_callback_log(m->log, options->log_callback, options->log_context, options->log_user_data); + + unsigned int net_err = 0; + + if (!options->udp_disabled && options->proxy_info.proxy_type != TCP_PROXY_NONE) { + // We don't currently support UDP over proxy. + LOGGER_INFO(m->log, "UDP enabled and proxy set: disabling UDP"); + options->udp_disabled = true; + } + + if (options->udp_disabled) { + m->net = new_networking_no_udp(m->log, m->ns); + } else { + IP ip; + ip_init(&ip, options->ipv6enabled); + m->net = new_networking_ex(m->log, m->ns, &ip, options->port_range[0], options->port_range[1], &net_err); + } + + if (m->net == nullptr) { + friendreq_kill(m->fr); + logger_kill(m->log); + free(m); + + if (error != nullptr && net_err == 1) { + *error = MESSENGER_ERROR_PORT; + } + + return nullptr; + } + + m->dht = new_dht(m->log, m->rng, m->ns, m->mono_time, m->net, options->hole_punching_enabled, options->local_discovery_enabled); + + if (m->dht == nullptr) { + kill_networking(m->net); + friendreq_kill(m->fr); + logger_kill(m->log); + free(m); + return nullptr; + } + + m->net_crypto = new_net_crypto(m->log, m->rng, m->ns, m->mono_time, m->dht, &options->proxy_info); + + if (m->net_crypto == nullptr) { + kill_dht(m->dht); + kill_networking(m->net); + friendreq_kill(m->fr); + logger_kill(m->log); + free(m); + return nullptr; + } + + if (options->dht_announcements_enabled) { + m->forwarding = new_forwarding(m->log, m->rng, m->mono_time, m->dht); + m->announce = new_announcements(m->log, m->rng, m->mono_time, m->forwarding); + } else { + m->forwarding = nullptr; + m->announce = nullptr; + } + + m->onion = new_onion(m->log, m->mono_time, m->rng, m->dht); + m->onion_a = new_onion_announce(m->log, m->rng, m->mono_time, m->dht); + m->onion_c = new_onion_client(m->log, m->rng, m->mono_time, m->net_crypto); + m->fr_c = new_friend_connections(m->log, m->mono_time, m->ns, m->onion_c, options->local_discovery_enabled); + + if ((options->dht_announcements_enabled && (m->forwarding == nullptr || m->announce == nullptr)) || + m->onion == nullptr || m->onion_a == nullptr || m->onion_c == nullptr || m->fr_c == nullptr) { + kill_friend_connections(m->fr_c); + kill_onion(m->onion); + kill_onion_announce(m->onion_a); + kill_onion_client(m->onion_c); + kill_announcements(m->announce); + kill_forwarding(m->forwarding); + kill_net_crypto(m->net_crypto); + kill_dht(m->dht); + kill_networking(m->net); + friendreq_kill(m->fr); + logger_kill(m->log); + free(m); + return nullptr; + } + + if (options->tcp_server_port != 0) { + m->tcp_server = new_TCP_server(m->log, m->rng, m->ns, options->ipv6enabled, 1, &options->tcp_server_port, + dht_get_self_secret_key(m->dht), m->onion, m->forwarding); + + if (m->tcp_server == nullptr) { + kill_friend_connections(m->fr_c); + kill_onion(m->onion); + kill_onion_announce(m->onion_a); + kill_onion_client(m->onion_c); + kill_announcements(m->announce); + kill_forwarding(m->forwarding); + kill_net_crypto(m->net_crypto); + kill_dht(m->dht); + kill_networking(m->net); + friendreq_kill(m->fr); + logger_kill(m->log); + free(m); + + if (error != nullptr) { + *error = MESSENGER_ERROR_TCP_SERVER; + } + + return nullptr; + } + } + + m->options = *options; + friendreq_init(m->fr, m->fr_c); + set_nospam(m->fr, random_u32(m->rng)); + set_filter_function(m->fr, &friend_already_added, m); + + m->lastdump = 0; + m->is_receiving_file = 0; + + m_register_default_plugins(m); + callback_friendrequest(m->fr, m_handle_friend_request, m); + + if (error != nullptr) { + *error = MESSENGER_ERROR_NONE; + } + + return m; +} + +/** @brief Run this before closing shop. + * + * Free all datastructures. + */ +void kill_messenger(Messenger *m) +{ + if (m == nullptr) { + return; + } + + if (m->tcp_server != nullptr) { + kill_TCP_server(m->tcp_server); + } + + kill_friend_connections(m->fr_c); + kill_onion(m->onion); + kill_onion_announce(m->onion_a); + kill_onion_client(m->onion_c); + kill_announcements(m->announce); + kill_forwarding(m->forwarding); + kill_net_crypto(m->net_crypto); + kill_dht(m->dht); + kill_networking(m->net); + + for (uint32_t i = 0; i < m->numfriends; ++i) { + clear_receipts(m, i); + } + + logger_kill(m->log); + free(m->friendlist); + friendreq_kill(m->fr); + + free(m->options.state_plugins); + free(m); +} + +bool m_is_receiving_file(Messenger *m) +{ + // Only run the expensive loop below once every 64 tox_iterate calls. + const uint8_t skip_count = 64; + + if (m->is_receiving_file != 0) { + --m->is_receiving_file; + return true; + } + + // TODO(iphydf): This is a very expensive loop. Consider keeping track of + // the number of live file transfers. + for (size_t friend_number = 0; friend_number < m->numfriends; ++friend_number) { + for (size_t i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) { + if (m->friendlist[friend_number].file_receiving[i].status == FILESTATUS_TRANSFERRING) { + m->is_receiving_file = skip_count; + return true; + } + } + } + + return false; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/TCP_client.h b/local_pod_repo/toxcore/toxcore/toxcore/TCP_client.h new file mode 100644 index 0000000..fdc91de --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/TCP_client.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * Implementation of the TCP relay client part of Tox. + */ +#ifndef C_TOXCORE_TOXCORE_TCP_CLIENT_H +#define C_TOXCORE_TOXCORE_TCP_CLIENT_H + +#include "crypto_core.h" +#include "forwarding.h" +#include "mono_time.h" +#include "network.h" + +#define TCP_CONNECTION_TIMEOUT 10 + +typedef enum TCP_Proxy_Type { + TCP_PROXY_NONE, + TCP_PROXY_HTTP, + TCP_PROXY_SOCKS5, +} TCP_Proxy_Type; + +typedef struct TCP_Proxy_Info { + IP_Port ip_port; + uint8_t proxy_type; // a value from TCP_PROXY_TYPE +} TCP_Proxy_Info; + +typedef enum TCP_Client_Status { + TCP_CLIENT_NO_STATUS, + TCP_CLIENT_PROXY_HTTP_CONNECTING, + TCP_CLIENT_PROXY_SOCKS5_CONNECTING, + TCP_CLIENT_PROXY_SOCKS5_UNCONFIRMED, + TCP_CLIENT_CONNECTING, + TCP_CLIENT_UNCONFIRMED, + TCP_CLIENT_CONFIRMED, + TCP_CLIENT_DISCONNECTED, +} TCP_Client_Status; + +typedef struct TCP_Client_Connection TCP_Client_Connection; + +non_null() +const uint8_t *tcp_con_public_key(const TCP_Client_Connection *con); +non_null() +IP_Port tcp_con_ip_port(const TCP_Client_Connection *con); +non_null() +TCP_Client_Status tcp_con_status(const TCP_Client_Connection *con); + +non_null() +void *tcp_con_custom_object(const TCP_Client_Connection *con); +non_null() +uint32_t tcp_con_custom_uint(const TCP_Client_Connection *con); +non_null() +void tcp_con_set_custom_object(TCP_Client_Connection *con, void *object); +non_null() +void tcp_con_set_custom_uint(TCP_Client_Connection *con, uint32_t value); + +/** Create new TCP connection to ip_port/public_key */ +non_null(1, 2, 3, 4, 5, 6, 7, 8) nullable(9) +TCP_Client_Connection *new_TCP_connection( + const Logger *logger, const Mono_Time *mono_time, const Random *rng, const Network *ns, const IP_Port *ip_port, + const uint8_t *public_key, const uint8_t *self_public_key, const uint8_t *self_secret_key, + const TCP_Proxy_Info *proxy_info); + +/** Run the TCP connection */ +non_null(1, 2, 3) nullable(4) +void do_TCP_connection(const Logger *logger, const Mono_Time *mono_time, + TCP_Client_Connection *tcp_connection, void *userdata); + +/** Kill the TCP connection */ +nullable(1) +void kill_TCP_connection(TCP_Client_Connection *tcp_connection); + +typedef int tcp_onion_response_cb(void *object, const uint8_t *data, uint16_t length, void *userdata); + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +non_null() +int send_onion_request(const Logger *logger, TCP_Client_Connection *con, const uint8_t *data, uint16_t length); +non_null() +void onion_response_handler(TCP_Client_Connection *con, tcp_onion_response_cb *onion_callback, void *object); + +non_null() +int send_forward_request_tcp(const Logger *logger, TCP_Client_Connection *con, const IP_Port *dest, const uint8_t *data, + uint16_t length); +non_null() +void forwarding_handler(TCP_Client_Connection *con, forwarded_response_cb *forwarded_response_callback, void *object); + +typedef int tcp_routing_response_cb(void *object, uint8_t connection_id, const uint8_t *public_key); +typedef int tcp_routing_status_cb(void *object, uint32_t number, uint8_t connection_id, uint8_t status); + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +non_null() +int send_routing_request(const Logger *logger, TCP_Client_Connection *con, const uint8_t *public_key); +non_null() +void routing_response_handler(TCP_Client_Connection *con, tcp_routing_response_cb *response_callback, void *object); +non_null() +void routing_status_handler(TCP_Client_Connection *con, tcp_routing_status_cb *status_callback, void *object); + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +non_null() +int send_disconnect_request(const Logger *logger, TCP_Client_Connection *con, uint8_t con_id); + +/** @brief Set the number that will be used as an argument in the callbacks related to con_id. + * + * When not set by this function, the number is -1. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +int set_tcp_connection_number(TCP_Client_Connection *con, uint8_t con_id, uint32_t number); + +typedef int tcp_routing_data_cb(void *object, uint32_t number, uint8_t connection_id, const uint8_t *data, + uint16_t length, void *userdata); + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure. + */ +non_null() +int send_data(const Logger *logger, TCP_Client_Connection *con, uint8_t con_id, const uint8_t *data, uint16_t length); +non_null() +void routing_data_handler(TCP_Client_Connection *con, tcp_routing_data_cb *data_callback, void *object); + +typedef int tcp_oob_data_cb(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length, + void *userdata); + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure. + */ +non_null() +int send_oob_packet(const Logger *logger, TCP_Client_Connection *con, const uint8_t *public_key, const uint8_t *data, + uint16_t length); +non_null() +void oob_data_handler(TCP_Client_Connection *con, tcp_oob_data_cb *oob_data_callback, void *object); + + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/TCP_client.m b/local_pod_repo/toxcore/toxcore/toxcore/TCP_client.m new file mode 100644 index 0000000..1bc0964 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/TCP_client.m @@ -0,0 +1,1005 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * Implementation of the TCP relay client part of Tox. + */ +#include "TCP_client.h" + +#include +#include +#include + +#include "TCP_common.h" +#include "ccompat.h" +#include "mono_time.h" +#include "util.h" + +typedef struct TCP_Client_Conn { + // TODO(iphydf): Add an enum for this. + uint8_t status; /* 0 if not used, 1 if other is offline, 2 if other is online. */ + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint32_t number; +} TCP_Client_Conn; + +struct TCP_Client_Connection { + TCP_Connection con; + TCP_Client_Status status; + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* our public key */ + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* public key of the server */ + IP_Port ip_port; /* The ip and port of the server */ + TCP_Proxy_Info proxy_info; + uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ + uint16_t next_packet_length; + + uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + uint64_t kill_at; + + uint64_t last_pinged; + uint64_t ping_id; + + uint64_t ping_response_id; + uint64_t ping_request_id; + + TCP_Client_Conn connections[NUM_CLIENT_CONNECTIONS]; + tcp_routing_response_cb *response_callback; + void *response_callback_object; + tcp_routing_status_cb *status_callback; + void *status_callback_object; + tcp_routing_data_cb *data_callback; + void *data_callback_object; + tcp_oob_data_cb *oob_data_callback; + void *oob_data_callback_object; + + tcp_onion_response_cb *onion_callback; + void *onion_callback_object; + + forwarded_response_cb *forwarded_response_callback; + void *forwarded_response_callback_object; + + /* Can be used by user. */ + void *custom_object; + uint32_t custom_uint; +}; + +const uint8_t *tcp_con_public_key(const TCP_Client_Connection *con) +{ + return con->public_key; +} + +IP_Port tcp_con_ip_port(const TCP_Client_Connection *con) +{ + return con->ip_port; +} + +TCP_Client_Status tcp_con_status(const TCP_Client_Connection *con) +{ + return con->status; +} +void *tcp_con_custom_object(const TCP_Client_Connection *con) +{ + return con->custom_object; +} +uint32_t tcp_con_custom_uint(const TCP_Client_Connection *con) +{ + return con->custom_uint; +} +void tcp_con_set_custom_object(TCP_Client_Connection *con, void *object) +{ + con->custom_object = object; +} +void tcp_con_set_custom_uint(TCP_Client_Connection *con, uint32_t value) +{ + con->custom_uint = value; +} + +/** + * @retval true on success + * @retval false on failure + */ +non_null() +static bool connect_sock_to(const Logger *logger, Socket sock, const IP_Port *ip_port, const TCP_Proxy_Info *proxy_info) +{ + if (proxy_info->proxy_type != TCP_PROXY_NONE) { + return net_connect(logger, sock, &proxy_info->ip_port); + } else { + return net_connect(logger, sock, ip_port); + } +} + +/** + * @retval 1 on success. + * @retval 0 on failure. + */ +non_null() +static int proxy_http_generate_connection_request(TCP_Client_Connection *tcp_conn) +{ + char one[] = "CONNECT "; + char two[] = " HTTP/1.1\nHost: "; + char three[] = "\r\n\r\n"; + + char ip[TOX_INET6_ADDRSTRLEN]; + + if (!ip_parse_addr(&tcp_conn->ip_port.ip, ip, sizeof(ip))) { + return 0; + } + + const uint16_t port = net_ntohs(tcp_conn->ip_port.port); + const int written = snprintf((char *)tcp_conn->con.last_packet, MAX_PACKET_SIZE, "%s%s:%hu%s%s:%hu%s", one, ip, port, + two, ip, port, three); + + if (written < 0 || MAX_PACKET_SIZE < written) { + return 0; + } + + tcp_conn->con.last_packet_length = written; + tcp_conn->con.last_packet_sent = 0; + return 1; +} + +/** + * @retval 1 on success. + * @retval 0 if no data received. + * @retval -1 on failure (connection refused). + */ +non_null() +static int proxy_http_read_connection_response(const Logger *logger, const TCP_Client_Connection *tcp_conn) +{ + char success[] = "200"; + uint8_t data[16]; // draining works the best if the length is a power of 2 + + const int ret = read_TCP_packet(logger, tcp_conn->con.ns, tcp_conn->con.sock, data, sizeof(data) - 1, + &tcp_conn->con.ip_port); + + if (ret == -1) { + return 0; + } + + data[sizeof(data) - 1] = 0; + + if (strstr((const char *)data, success) != nullptr) { + // drain all data + uint16_t data_left = net_socket_data_recv_buffer(tcp_conn->con.ns, tcp_conn->con.sock); + + while (data_left > 0) { + uint8_t temp_data[16]; + const uint16_t temp_data_size = min_u16(data_left, sizeof(temp_data)); + + if (read_TCP_packet(logger, tcp_conn->con.ns, tcp_conn->con.sock, temp_data, temp_data_size, + &tcp_conn->con.ip_port) == -1) { + LOGGER_ERROR(logger, "failed to drain TCP data (but ignoring failure)"); + return 1; + } + + data_left -= temp_data_size; + } + + return 1; + } + + return -1; +} + +#define TCP_SOCKS5_PROXY_HS_VERSION_SOCKS5 0x05 +#define TCP_SOCKS5_PROXY_HS_COMM_ESTABLISH_REQUEST 0x01 +#define TCP_SOCKS5_PROXY_HS_COMM_REQUEST_GRANTED 0x00 +#define TCP_SOCKS5_PROXY_HS_AUTH_METHODS_SUPPORTED 0x01 +#define TCP_SOCKS5_PROXY_HS_NO_AUTH 0x00 +#define TCP_SOCKS5_PROXY_HS_RESERVED 0x00 +#define TCP_SOCKS5_PROXY_HS_ADDR_TYPE_IPV4 0x01 +#define TCP_SOCKS5_PROXY_HS_ADDR_TYPE_IPV6 0x04 + +non_null() +static void proxy_socks5_generate_greetings(TCP_Client_Connection *tcp_conn) +{ + tcp_conn->con.last_packet[0] = TCP_SOCKS5_PROXY_HS_VERSION_SOCKS5; + tcp_conn->con.last_packet[1] = TCP_SOCKS5_PROXY_HS_AUTH_METHODS_SUPPORTED; + tcp_conn->con.last_packet[2] = TCP_SOCKS5_PROXY_HS_NO_AUTH; + + tcp_conn->con.last_packet_length = 3; + tcp_conn->con.last_packet_sent = 0; +} + +/** + * @retval 1 on success. + * @retval 0 if no data received. + * @retval -1 on failure (connection refused). + */ +non_null() +static int socks5_read_handshake_response(const Logger *logger, const TCP_Client_Connection *tcp_conn) +{ + uint8_t data[2]; + const int ret = read_TCP_packet(logger, tcp_conn->con.ns, tcp_conn->con.sock, data, sizeof(data), &tcp_conn->con.ip_port); + + if (ret == -1) { + return 0; + } + + if (data[0] == TCP_SOCKS5_PROXY_HS_VERSION_SOCKS5 && data[1] == TCP_SOCKS5_PROXY_HS_COMM_REQUEST_GRANTED) { + return 1; + } + + return -1; +} + +non_null() +static void proxy_socks5_generate_connection_request(TCP_Client_Connection *tcp_conn) +{ + tcp_conn->con.last_packet[0] = TCP_SOCKS5_PROXY_HS_VERSION_SOCKS5; + tcp_conn->con.last_packet[1] = TCP_SOCKS5_PROXY_HS_COMM_ESTABLISH_REQUEST; + tcp_conn->con.last_packet[2] = TCP_SOCKS5_PROXY_HS_RESERVED; + uint16_t length = 3; + + if (net_family_is_ipv4(tcp_conn->ip_port.ip.family)) { + tcp_conn->con.last_packet[3] = TCP_SOCKS5_PROXY_HS_ADDR_TYPE_IPV4; + ++length; + memcpy(tcp_conn->con.last_packet + length, tcp_conn->ip_port.ip.ip.v4.uint8, sizeof(IP4)); + length += sizeof(IP4); + } else { + tcp_conn->con.last_packet[3] = TCP_SOCKS5_PROXY_HS_ADDR_TYPE_IPV6; + ++length; + memcpy(tcp_conn->con.last_packet + length, tcp_conn->ip_port.ip.ip.v6.uint8, sizeof(IP6)); + length += sizeof(IP6); + } + + memcpy(tcp_conn->con.last_packet + length, &tcp_conn->ip_port.port, sizeof(uint16_t)); + length += sizeof(uint16_t); + + tcp_conn->con.last_packet_length = length; + tcp_conn->con.last_packet_sent = 0; +} + +/** + * @retval 1 on success. + * @retval 0 if no data received. + * @retval -1 on failure (connection refused). + */ +non_null() +static int proxy_socks5_read_connection_response(const Logger *logger, const TCP_Client_Connection *tcp_conn) +{ + if (net_family_is_ipv4(tcp_conn->ip_port.ip.family)) { + uint8_t data[4 + sizeof(IP4) + sizeof(uint16_t)]; + const int ret = read_TCP_packet(logger, tcp_conn->con.ns, tcp_conn->con.sock, data, sizeof(data), &tcp_conn->con.ip_port); + + if (ret == -1) { + return 0; + } + + if (data[0] == TCP_SOCKS5_PROXY_HS_VERSION_SOCKS5 && data[1] == TCP_SOCKS5_PROXY_HS_COMM_REQUEST_GRANTED) { + return 1; + } + } else { + uint8_t data[4 + sizeof(IP6) + sizeof(uint16_t)]; + int ret = read_TCP_packet(logger, tcp_conn->con.ns, tcp_conn->con.sock, data, sizeof(data), &tcp_conn->con.ip_port); + + if (ret == -1) { + return 0; + } + + if (data[0] == TCP_SOCKS5_PROXY_HS_VERSION_SOCKS5 && data[1] == TCP_SOCKS5_PROXY_HS_COMM_REQUEST_GRANTED) { + return 1; + } + } + + return -1; +} + +/** + * @retval 0 on success. + * @retval -1 on failure. + */ +non_null() +static int generate_handshake(TCP_Client_Connection *tcp_conn) +{ + uint8_t plain[CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE]; + crypto_new_keypair(tcp_conn->con.rng, plain, tcp_conn->temp_secret_key); + random_nonce(tcp_conn->con.rng, tcp_conn->con.sent_nonce); + memcpy(plain + CRYPTO_PUBLIC_KEY_SIZE, tcp_conn->con.sent_nonce, CRYPTO_NONCE_SIZE); + memcpy(tcp_conn->con.last_packet, tcp_conn->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + random_nonce(tcp_conn->con.rng, tcp_conn->con.last_packet + CRYPTO_PUBLIC_KEY_SIZE); + const int len = encrypt_data_symmetric(tcp_conn->con.shared_key, tcp_conn->con.last_packet + CRYPTO_PUBLIC_KEY_SIZE, plain, + sizeof(plain), tcp_conn->con.last_packet + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); + + if (len != sizeof(plain) + CRYPTO_MAC_SIZE) { + return -1; + } + + tcp_conn->con.last_packet_length = CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + sizeof(plain) + CRYPTO_MAC_SIZE; + tcp_conn->con.last_packet_sent = 0; + return 0; +} + +/** + * @param data must be of length TCP_SERVER_HANDSHAKE_SIZE + * + * @retval 0 on success. + * @retval -1 on failure. + */ +non_null() +static int handle_handshake(TCP_Client_Connection *tcp_conn, const uint8_t *data) +{ + uint8_t plain[CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE]; + const int len = decrypt_data_symmetric(tcp_conn->con.shared_key, data, data + CRYPTO_NONCE_SIZE, + TCP_SERVER_HANDSHAKE_SIZE - CRYPTO_NONCE_SIZE, plain); + + if (len != sizeof(plain)) { + return -1; + } + + memcpy(tcp_conn->recv_nonce, plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_NONCE_SIZE); + encrypt_precompute(plain, tcp_conn->temp_secret_key, tcp_conn->con.shared_key); + crypto_memzero(tcp_conn->temp_secret_key, CRYPTO_SECRET_KEY_SIZE); + return 0; +} + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +int send_routing_request(const Logger *logger, TCP_Client_Connection *con, const uint8_t *public_key) +{ + uint8_t packet[1 + CRYPTO_PUBLIC_KEY_SIZE]; + packet[0] = TCP_PACKET_ROUTING_REQUEST; + memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE); + return write_packet_TCP_secure_connection(logger, &con->con, packet, sizeof(packet), true); +} + +void routing_response_handler(TCP_Client_Connection *con, tcp_routing_response_cb *response_callback, void *object) +{ + con->response_callback = response_callback; + con->response_callback_object = object; +} + +void routing_status_handler(TCP_Client_Connection *con, tcp_routing_status_cb *status_callback, void *object) +{ + con->status_callback = status_callback; + con->status_callback_object = object; +} + +non_null() static int tcp_send_ping_response(const Logger *logger, TCP_Client_Connection *con); +non_null() static int tcp_send_ping_request(const Logger *logger, TCP_Client_Connection *con); + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure. + */ +int send_data(const Logger *logger, TCP_Client_Connection *con, uint8_t con_id, const uint8_t *data, uint16_t length) +{ + if (con_id >= NUM_CLIENT_CONNECTIONS) { + return -1; + } + + if (con->connections[con_id].status != 2) { + return -1; + } + + if (tcp_send_ping_response(logger, con) == 0 || tcp_send_ping_request(logger, con) == 0) { + return 0; + } + + VLA(uint8_t, packet, 1 + length); + packet[0] = con_id + NUM_RESERVED_PORTS; + memcpy(packet + 1, data, length); + return write_packet_TCP_secure_connection(logger, &con->con, packet, SIZEOF_VLA(packet), false); +} + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure. + */ +int send_oob_packet(const Logger *logger, TCP_Client_Connection *con, const uint8_t *public_key, const uint8_t *data, + uint16_t length) +{ + if (length == 0 || length > TCP_MAX_OOB_DATA_LENGTH) { + return -1; + } + + VLA(uint8_t, packet, 1 + CRYPTO_PUBLIC_KEY_SIZE + length); + packet[0] = TCP_PACKET_OOB_SEND; + memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, data, length); + return write_packet_TCP_secure_connection(logger, &con->con, packet, SIZEOF_VLA(packet), false); +} + + +/** @brief Set the number that will be used as an argument in the callbacks related to con_id. + * + * When not set by this function, the number is -1. + * + * return 0 on success. + * return -1 on failure. + */ +int set_tcp_connection_number(TCP_Client_Connection *con, uint8_t con_id, uint32_t number) +{ + if (con_id >= NUM_CLIENT_CONNECTIONS) { + return -1; + } + + if (con->connections[con_id].status == 0) { + return -1; + } + + con->connections[con_id].number = number; + return 0; +} + +void routing_data_handler(TCP_Client_Connection *con, tcp_routing_data_cb *data_callback, void *object) +{ + con->data_callback = data_callback; + con->data_callback_object = object; +} + +void oob_data_handler(TCP_Client_Connection *con, tcp_oob_data_cb *oob_data_callback, void *object) +{ + con->oob_data_callback = oob_data_callback; + con->oob_data_callback_object = object; +} + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +non_null() +static int client_send_disconnect_notification(const Logger *logger, TCP_Client_Connection *con, uint8_t id) +{ + uint8_t packet[1 + 1]; + packet[0] = TCP_PACKET_DISCONNECT_NOTIFICATION; + packet[1] = id; + return write_packet_TCP_secure_connection(logger, &con->con, packet, sizeof(packet), true); +} + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +static int tcp_send_ping_request(const Logger *logger, TCP_Client_Connection *con) +{ + if (con->ping_request_id == 0) { + return 1; + } + + uint8_t packet[1 + sizeof(uint64_t)]; + packet[0] = TCP_PACKET_PING; + memcpy(packet + 1, &con->ping_request_id, sizeof(uint64_t)); + const int ret = write_packet_TCP_secure_connection(logger, &con->con, packet, sizeof(packet), true); + + if (ret == 1) { + con->ping_request_id = 0; + } + + return ret; +} + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +static int tcp_send_ping_response(const Logger *logger, TCP_Client_Connection *con) +{ + if (con->ping_response_id == 0) { + return 1; + } + + uint8_t packet[1 + sizeof(uint64_t)]; + packet[0] = TCP_PACKET_PONG; + memcpy(packet + 1, &con->ping_response_id, sizeof(uint64_t)); + const int ret = write_packet_TCP_secure_connection(logger, &con->con, packet, sizeof(packet), true); + + if (ret == 1) { + con->ping_response_id = 0; + } + + return ret; +} + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +int send_disconnect_request(const Logger *logger, TCP_Client_Connection *con, uint8_t con_id) +{ + if (con_id >= NUM_CLIENT_CONNECTIONS) { + return -1; + } + + con->connections[con_id].status = 0; + con->connections[con_id].number = 0; + return client_send_disconnect_notification(logger, con, con_id + NUM_RESERVED_PORTS); +} + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +int send_onion_request(const Logger *logger, TCP_Client_Connection *con, const uint8_t *data, uint16_t length) +{ + VLA(uint8_t, packet, 1 + length); + packet[0] = TCP_PACKET_ONION_REQUEST; + memcpy(packet + 1, data, length); + return write_packet_TCP_secure_connection(logger, &con->con, packet, SIZEOF_VLA(packet), false); +} + +void onion_response_handler(TCP_Client_Connection *con, tcp_onion_response_cb *onion_callback, void *object) +{ + con->onion_callback = onion_callback; + con->onion_callback_object = object; +} + +/** @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +int send_forward_request_tcp(const Logger *logger, TCP_Client_Connection *con, const IP_Port *dest, const uint8_t *data, uint16_t length) +{ + if (length > MAX_FORWARD_DATA_SIZE) { + return -1; + } + + VLA(uint8_t, packet, 1 + MAX_PACKED_IPPORT_SIZE + length); + packet[0] = TCP_PACKET_FORWARD_REQUEST; + const int ipport_length = pack_ip_port(logger, packet + 1, MAX_PACKED_IPPORT_SIZE, dest); + + if (ipport_length == -1) { + return 0; + } + + memcpy(packet + 1 + ipport_length, data, length); + return write_packet_TCP_secure_connection(logger, &con->con, packet, 1 + ipport_length + length, false); +} + +void forwarding_handler(TCP_Client_Connection *con, forwarded_response_cb *forwarded_response_callback, void *object) +{ + con->forwarded_response_callback = forwarded_response_callback; + con->forwarded_response_callback_object = object; +} + +/** Create new TCP connection to ip_port/public_key */ +TCP_Client_Connection *new_TCP_connection( + const Logger *logger, const Mono_Time *mono_time, const Random *rng, const Network *ns, const IP_Port *ip_port, + const uint8_t *public_key, const uint8_t *self_public_key, const uint8_t *self_secret_key, + const TCP_Proxy_Info *proxy_info) +{ + if (!net_family_is_ipv4(ip_port->ip.family) && !net_family_is_ipv6(ip_port->ip.family)) { + return nullptr; + } + + const TCP_Proxy_Info default_proxyinfo = {{{{0}}}}; + + if (proxy_info == nullptr) { + proxy_info = &default_proxyinfo; + } + + Family family = ip_port->ip.family; + + if (proxy_info->proxy_type != TCP_PROXY_NONE) { + family = proxy_info->ip_port.ip.family; + } + + const Socket sock = net_socket(ns, family, TOX_SOCK_STREAM, TOX_PROTO_TCP); + + if (!sock_valid(sock)) { + return nullptr; + } + + if (!set_socket_nosigpipe(ns, sock)) { + kill_sock(ns, sock); + return nullptr; + } + + if (!(set_socket_nonblock(ns, sock) && connect_sock_to(logger, sock, ip_port, proxy_info))) { + kill_sock(ns, sock); + return nullptr; + } + + TCP_Client_Connection *temp = (TCP_Client_Connection *)calloc(1, sizeof(TCP_Client_Connection)); + + if (temp == nullptr) { + kill_sock(ns, sock); + return nullptr; + } + + temp->con.ns = ns; + temp->con.rng = rng; + temp->con.sock = sock; + temp->con.ip_port = *ip_port; + memcpy(temp->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(temp->self_public_key, self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + encrypt_precompute(temp->public_key, self_secret_key, temp->con.shared_key); + temp->ip_port = *ip_port; + temp->proxy_info = *proxy_info; + + switch (proxy_info->proxy_type) { + case TCP_PROXY_HTTP: { + temp->status = TCP_CLIENT_PROXY_HTTP_CONNECTING; + proxy_http_generate_connection_request(temp); + break; + } + + case TCP_PROXY_SOCKS5: { + temp->status = TCP_CLIENT_PROXY_SOCKS5_CONNECTING; + proxy_socks5_generate_greetings(temp); + break; + } + + case TCP_PROXY_NONE: { + temp->status = TCP_CLIENT_CONNECTING; + + if (generate_handshake(temp) == -1) { + kill_sock(ns, sock); + free(temp); + return nullptr; + } + + break; + } + } + + temp->kill_at = mono_time_get(mono_time) + TCP_CONNECTION_TIMEOUT; + + return temp; +} + +non_null() +static int handle_TCP_client_routing_response(TCP_Client_Connection *conn, const uint8_t *data, uint16_t length) +{ + if (length != 1 + 1 + CRYPTO_PUBLIC_KEY_SIZE) { + return -1; + } + + if (data[1] < NUM_RESERVED_PORTS) { + return 0; + } + + const uint8_t con_id = data[1] - NUM_RESERVED_PORTS; + + if (conn->connections[con_id].status != 0) { + return 0; + } + + conn->connections[con_id].status = 1; + conn->connections[con_id].number = -1; + memcpy(conn->connections[con_id].public_key, data + 2, CRYPTO_PUBLIC_KEY_SIZE); + + if (conn->response_callback != nullptr) { + conn->response_callback(conn->response_callback_object, con_id, conn->connections[con_id].public_key); + } + + return 0; +} + +non_null() +static int handle_TCP_client_connection_notification(TCP_Client_Connection *conn, const uint8_t *data, uint16_t length) +{ + if (length != 1 + 1) { + return -1; + } + + if (data[1] < NUM_RESERVED_PORTS) { + return -1; + } + + const uint8_t con_id = data[1] - NUM_RESERVED_PORTS; + + if (conn->connections[con_id].status != 1) { + return 0; + } + + conn->connections[con_id].status = 2; + + if (conn->status_callback != nullptr) { + conn->status_callback(conn->status_callback_object, conn->connections[con_id].number, con_id, + conn->connections[con_id].status); + } + + return 0; +} + +non_null() +static int handle_TCP_client_disconnect_notification(TCP_Client_Connection *conn, const uint8_t *data, uint16_t length) +{ + if (length != 1 + 1) { + return -1; + } + + if (data[1] < NUM_RESERVED_PORTS) { + return -1; + } + + const uint8_t con_id = data[1] - NUM_RESERVED_PORTS; + + if (conn->connections[con_id].status == 0) { + return 0; + } + + if (conn->connections[con_id].status != 2) { + return 0; + } + + conn->connections[con_id].status = 1; + + if (conn->status_callback != nullptr) { + conn->status_callback(conn->status_callback_object, conn->connections[con_id].number, con_id, + conn->connections[con_id].status); + } + + return 0; +} + +non_null() +static int handle_TCP_client_ping(const Logger *logger, TCP_Client_Connection *conn, const uint8_t *data, uint16_t length) +{ + if (length != 1 + sizeof(uint64_t)) { + return -1; + } + + uint64_t ping_id; + memcpy(&ping_id, data + 1, sizeof(uint64_t)); + conn->ping_response_id = ping_id; + tcp_send_ping_response(logger, conn); + return 0; +} + +non_null() +static int handle_TCP_client_pong(TCP_Client_Connection *conn, const uint8_t *data, uint16_t length) +{ + if (length != 1 + sizeof(uint64_t)) { + return -1; + } + + uint64_t ping_id; + memcpy(&ping_id, data + 1, sizeof(uint64_t)); + + if (ping_id != 0) { + if (ping_id == conn->ping_id) { + conn->ping_id = 0; + } + + return 0; + } + + return -1; +} + +non_null(1, 2) nullable(4) +static int handle_TCP_client_oob_recv(TCP_Client_Connection *conn, const uint8_t *data, uint16_t length, void *userdata) +{ + if (length <= 1 + CRYPTO_PUBLIC_KEY_SIZE) { + return -1; + } + + if (conn->oob_data_callback != nullptr) { + conn->oob_data_callback(conn->oob_data_callback_object, data + 1, data + 1 + CRYPTO_PUBLIC_KEY_SIZE, + length - (1 + CRYPTO_PUBLIC_KEY_SIZE), userdata); + } + + return 0; +} + +/** + * @retval 0 on success + * @retval -1 on failure + */ +non_null(1, 2, 3) nullable(5) +static int handle_TCP_client_packet(const Logger *logger, TCP_Client_Connection *conn, const uint8_t *data, + uint16_t length, void *userdata) +{ + if (length <= 1) { + return -1; + } + + switch (data[0]) { + case TCP_PACKET_ROUTING_RESPONSE: + return handle_TCP_client_routing_response(conn, data, length); + + case TCP_PACKET_CONNECTION_NOTIFICATION: + return handle_TCP_client_connection_notification(conn, data, length); + + case TCP_PACKET_DISCONNECT_NOTIFICATION: + return handle_TCP_client_disconnect_notification(conn, data, length); + + case TCP_PACKET_PING: + return handle_TCP_client_ping(logger, conn, data, length); + + case TCP_PACKET_PONG: + return handle_TCP_client_pong(conn, data, length); + + case TCP_PACKET_OOB_RECV: + return handle_TCP_client_oob_recv(conn, data, length, userdata); + + case TCP_PACKET_ONION_RESPONSE: { + if (conn->onion_callback != nullptr) { + conn->onion_callback(conn->onion_callback_object, data + 1, length - 1, userdata); + } + return 0; + } + + case TCP_PACKET_FORWARDING: { + if (conn->forwarded_response_callback != nullptr) { + conn->forwarded_response_callback(conn->forwarded_response_callback_object, data + 1, length - 1, userdata); + } + return 0; + } + + default: { + if (data[0] < NUM_RESERVED_PORTS) { + return -1; + } + + const uint8_t con_id = data[0] - NUM_RESERVED_PORTS; + + if (conn->data_callback != nullptr) { + conn->data_callback(conn->data_callback_object, conn->connections[con_id].number, con_id, data + 1, length - 1, + userdata); + } + } + } + + return 0; +} + +non_null(1, 2) nullable(3) +static bool tcp_process_packet(const Logger *logger, TCP_Client_Connection *conn, void *userdata) +{ + uint8_t packet[MAX_PACKET_SIZE]; + const int len = read_packet_TCP_secure_connection(logger, conn->con.ns, conn->con.sock, &conn->next_packet_length, conn->con.shared_key, conn->recv_nonce, packet, sizeof(packet), &conn->ip_port); + + if (len == 0) { + return false; + } + + if (len == -1) { + conn->status = TCP_CLIENT_DISCONNECTED; + return false; + } + + if (handle_TCP_client_packet(logger, conn, packet, len, userdata) == -1) { + conn->status = TCP_CLIENT_DISCONNECTED; + return false; + } + + return true; +} + +non_null(1, 2, 3) nullable(4) +static int do_confirmed_TCP(const Logger *logger, TCP_Client_Connection *conn, const Mono_Time *mono_time, + void *userdata) +{ + send_pending_data(logger, &conn->con); + tcp_send_ping_response(logger, conn); + tcp_send_ping_request(logger, conn); + + if (mono_time_is_timeout(mono_time, conn->last_pinged, TCP_PING_FREQUENCY)) { + uint64_t ping_id = random_u64(conn->con.rng); + + if (ping_id == 0) { + ++ping_id; + } + + conn->ping_request_id = ping_id; + conn->ping_id = ping_id; + tcp_send_ping_request(logger, conn); + conn->last_pinged = mono_time_get(mono_time); + } + + if (conn->ping_id != 0 && mono_time_is_timeout(mono_time, conn->last_pinged, TCP_PING_TIMEOUT)) { + conn->status = TCP_CLIENT_DISCONNECTED; + return 0; + } + + while (tcp_process_packet(logger, conn, userdata)) { + // Keep reading until error or out of data. + continue; + } + + return 0; +} + +/** Run the TCP connection */ +void do_TCP_connection(const Logger *logger, const Mono_Time *mono_time, + TCP_Client_Connection *tcp_connection, void *userdata) +{ + if (tcp_connection->status == TCP_CLIENT_DISCONNECTED) { + return; + } + + if (tcp_connection->status == TCP_CLIENT_PROXY_HTTP_CONNECTING) { + if (send_pending_data(logger, &tcp_connection->con) == 0) { + const int ret = proxy_http_read_connection_response(logger, tcp_connection); + + if (ret == -1) { + tcp_connection->kill_at = 0; + tcp_connection->status = TCP_CLIENT_DISCONNECTED; + } + + if (ret == 1) { + generate_handshake(tcp_connection); + tcp_connection->status = TCP_CLIENT_CONNECTING; + } + } + } + + if (tcp_connection->status == TCP_CLIENT_PROXY_SOCKS5_CONNECTING) { + if (send_pending_data(logger, &tcp_connection->con) == 0) { + int ret = socks5_read_handshake_response(logger, tcp_connection); + + if (ret == -1) { + tcp_connection->kill_at = 0; + tcp_connection->status = TCP_CLIENT_DISCONNECTED; + } + + if (ret == 1) { + proxy_socks5_generate_connection_request(tcp_connection); + tcp_connection->status = TCP_CLIENT_PROXY_SOCKS5_UNCONFIRMED; + } + } + } + + if (tcp_connection->status == TCP_CLIENT_PROXY_SOCKS5_UNCONFIRMED) { + if (send_pending_data(logger, &tcp_connection->con) == 0) { + int ret = proxy_socks5_read_connection_response(logger, tcp_connection); + + if (ret == -1) { + tcp_connection->kill_at = 0; + tcp_connection->status = TCP_CLIENT_DISCONNECTED; + } + + if (ret == 1) { + generate_handshake(tcp_connection); + tcp_connection->status = TCP_CLIENT_CONNECTING; + } + } + } + + if (tcp_connection->status == TCP_CLIENT_CONNECTING) { + if (send_pending_data(logger, &tcp_connection->con) == 0) { + tcp_connection->status = TCP_CLIENT_UNCONFIRMED; + } + } + + if (tcp_connection->status == TCP_CLIENT_UNCONFIRMED) { + uint8_t data[TCP_SERVER_HANDSHAKE_SIZE]; + const int len = read_TCP_packet(logger, tcp_connection->con.ns, tcp_connection->con.sock, data, sizeof(data), &tcp_connection->con.ip_port); + + if (sizeof(data) == len) { + if (handle_handshake(tcp_connection, data) == 0) { + tcp_connection->kill_at = UINT64_MAX; + tcp_connection->status = TCP_CLIENT_CONFIRMED; + } else { + tcp_connection->kill_at = 0; + tcp_connection->status = TCP_CLIENT_DISCONNECTED; + } + } + } + + if (tcp_connection->status == TCP_CLIENT_CONFIRMED) { + do_confirmed_TCP(logger, tcp_connection, mono_time, userdata); + } + + if (tcp_connection->kill_at <= mono_time_get(mono_time)) { + tcp_connection->status = TCP_CLIENT_DISCONNECTED; + } +} + +/** Kill the TCP connection */ +void kill_TCP_connection(TCP_Client_Connection *tcp_connection) +{ + if (tcp_connection == nullptr) { + return; + } + + wipe_priority_list(tcp_connection->con.priority_queue_start); + kill_sock(tcp_connection->con.ns, tcp_connection->con.sock); + crypto_memzero(tcp_connection, sizeof(TCP_Client_Connection)); + free(tcp_connection); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/TCP_common.h b/local_pod_repo/toxcore/toxcore/toxcore/TCP_common.h new file mode 100644 index 0000000..88c0cb6 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/TCP_common.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +#ifndef C_TOXCORE_TOXCORE_TCP_COMMON_H +#define C_TOXCORE_TOXCORE_TCP_COMMON_H + +#include "crypto_core.h" +#include "network.h" + +typedef struct TCP_Priority_List TCP_Priority_List; +struct TCP_Priority_List { + TCP_Priority_List *next; + uint16_t size; + uint16_t sent; + uint8_t *data; +}; + +nullable(1) +void wipe_priority_list(TCP_Priority_List *p); + +#define NUM_RESERVED_PORTS 16 +#define NUM_CLIENT_CONNECTIONS (256 - NUM_RESERVED_PORTS) + +#define TCP_PACKET_ROUTING_REQUEST 0 +#define TCP_PACKET_ROUTING_RESPONSE 1 +#define TCP_PACKET_CONNECTION_NOTIFICATION 2 +#define TCP_PACKET_DISCONNECT_NOTIFICATION 3 +#define TCP_PACKET_PING 4 +#define TCP_PACKET_PONG 5 +#define TCP_PACKET_OOB_SEND 6 +#define TCP_PACKET_OOB_RECV 7 +#define TCP_PACKET_ONION_REQUEST 8 +#define TCP_PACKET_ONION_RESPONSE 9 +#define TCP_PACKET_FORWARD_REQUEST 10 +#define TCP_PACKET_FORWARDING 11 + +#define TCP_HANDSHAKE_PLAIN_SIZE (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE) +#define TCP_SERVER_HANDSHAKE_SIZE (CRYPTO_NONCE_SIZE + TCP_HANDSHAKE_PLAIN_SIZE + CRYPTO_MAC_SIZE) +#define TCP_CLIENT_HANDSHAKE_SIZE (CRYPTO_PUBLIC_KEY_SIZE + TCP_SERVER_HANDSHAKE_SIZE) +#define TCP_MAX_OOB_DATA_LENGTH 1024 + +/** frequency to ping connected nodes and timeout in seconds */ +#define TCP_PING_FREQUENCY 30 +#define TCP_PING_TIMEOUT 10 + +#define MAX_PACKET_SIZE 2048 + +typedef struct TCP_Connection { + const Random *rng; + const Network *ns; + Socket sock; + IP_Port ip_port; // for debugging. + uint8_t sent_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of sent packets. */ + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t last_packet[2 + MAX_PACKET_SIZE]; + uint16_t last_packet_length; + uint16_t last_packet_sent; + + TCP_Priority_List *priority_queue_start; + TCP_Priority_List *priority_queue_end; +} TCP_Connection; + +/** + * @retval 0 if pending data was sent completely + * @retval -1 if it wasn't + */ +non_null() +int send_pending_data_nonpriority(const Logger *logger, TCP_Connection *con); + +/** + * @retval 0 if pending data was sent completely + * @retval -1 if it wasn't + */ +non_null() +int send_pending_data(const Logger *logger, TCP_Connection *con); + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +non_null() +int write_packet_TCP_secure_connection( + const Logger *logger, TCP_Connection *con, const uint8_t *data, uint16_t length, + bool priority); + +/** @brief Read length bytes from socket. + * + * return length on success + * return -1 on failure/no data in buffer. + */ +non_null() +int read_TCP_packet( + const Logger *logger, const Network *ns, Socket sock, uint8_t *data, uint16_t length, const IP_Port *ip_port); + +/** + * @return length of received packet on success. + * @retval 0 if could not read any packet. + * @retval -1 on failure (connection must be killed). + */ +non_null() +int read_packet_TCP_secure_connection( + const Logger *logger, const Network *ns, Socket sock, uint16_t *next_packet_length, + const uint8_t *shared_key, uint8_t *recv_nonce, uint8_t *data, + uint16_t max_len, const IP_Port *ip_port); + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/TCP_common.m b/local_pod_repo/toxcore/toxcore/toxcore/TCP_common.m new file mode 100644 index 0000000..349e35f --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/TCP_common.m @@ -0,0 +1,305 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +#include "TCP_common.h" + +#include +#include + +#include "ccompat.h" + +void wipe_priority_list(TCP_Priority_List *p) +{ + while (p != nullptr) { + TCP_Priority_List *pp = p; + p = p->next; + free(pp->data); + free(pp); + } +} + +/** + * @retval 0 if pending data was sent completely + * @retval -1 if it wasn't + */ +int send_pending_data_nonpriority(const Logger *logger, TCP_Connection *con) +{ + if (con->last_packet_length == 0) { + return 0; + } + + const uint16_t left = con->last_packet_length - con->last_packet_sent; + const int len = net_send(con->ns, logger, con->sock, con->last_packet + con->last_packet_sent, left, &con->ip_port); + + if (len <= 0) { + return -1; + } + + if (len == left) { + con->last_packet_length = 0; + con->last_packet_sent = 0; + return 0; + } + + con->last_packet_sent += len; + return -1; +} + +/** + * @retval 0 if pending data was sent completely + * @retval -1 if it wasn't + */ +int send_pending_data(const Logger *logger, TCP_Connection *con) +{ + /* finish sending current non-priority packet */ + if (send_pending_data_nonpriority(logger, con) == -1) { + return -1; + } + + TCP_Priority_List *p = con->priority_queue_start; + + while (p != nullptr) { + const uint16_t left = p->size - p->sent; + const int len = net_send(con->ns, logger, con->sock, p->data + p->sent, left, &con->ip_port); + + if (len != left) { + if (len > 0) { + p->sent += len; + } + + break; + } + + TCP_Priority_List *pp = p; + p = p->next; + free(pp->data); + free(pp); + } + + con->priority_queue_start = p; + + if (p == nullptr) { + con->priority_queue_end = nullptr; + return 0; + } + + return -1; +} + +/** + * @retval false on failure (only if calloc fails) + * @retval true on success + */ +non_null() +static bool add_priority(TCP_Connection *con, const uint8_t *packet, uint16_t size, uint16_t sent) +{ + TCP_Priority_List *p = con->priority_queue_end; + TCP_Priority_List *new_list = (TCP_Priority_List *)calloc(1, sizeof(TCP_Priority_List)); + + if (new_list == nullptr) { + return false; + } + + new_list->next = nullptr; + new_list->size = size; + new_list->sent = sent; + new_list->data = (uint8_t *)malloc(size); + + if (new_list->data == nullptr) { + free(new_list); + return false; + } + + memcpy(new_list->data, packet, size); + + if (p != nullptr) { + p->next = new_list; + } else { + con->priority_queue_start = new_list; + } + + con->priority_queue_end = new_list; + return true; +} + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +int write_packet_TCP_secure_connection(const Logger *logger, TCP_Connection *con, const uint8_t *data, uint16_t length, + bool priority) +{ + if (length + CRYPTO_MAC_SIZE > MAX_PACKET_SIZE) { + return -1; + } + + bool sendpriority = true; + + if (send_pending_data(logger, con) == -1) { + if (priority) { + sendpriority = false; + } else { + return 0; + } + } + + VLA(uint8_t, packet, sizeof(uint16_t) + length + CRYPTO_MAC_SIZE); + + uint16_t c_length = net_htons(length + CRYPTO_MAC_SIZE); + memcpy(packet, &c_length, sizeof(uint16_t)); + int len = encrypt_data_symmetric(con->shared_key, con->sent_nonce, data, length, packet + sizeof(uint16_t)); + + if ((unsigned int)len != (SIZEOF_VLA(packet) - sizeof(uint16_t))) { + return -1; + } + + if (priority) { + len = sendpriority ? net_send(con->ns, logger, con->sock, packet, SIZEOF_VLA(packet), &con->ip_port) : 0; + + if (len <= 0) { + len = 0; + } + + increment_nonce(con->sent_nonce); + + if ((unsigned int)len == SIZEOF_VLA(packet)) { + return 1; + } + + return add_priority(con, packet, SIZEOF_VLA(packet), len) ? 1 : 0; + } + + len = net_send(con->ns, logger, con->sock, packet, SIZEOF_VLA(packet), &con->ip_port); + + if (len <= 0) { + return 0; + } + + increment_nonce(con->sent_nonce); + + if ((unsigned int)len == SIZEOF_VLA(packet)) { + return 1; + } + + memcpy(con->last_packet, packet, SIZEOF_VLA(packet)); + con->last_packet_length = SIZEOF_VLA(packet); + con->last_packet_sent = len; + return 1; +} + +/** @brief Read length bytes from socket. + * + * return length on success + * return -1 on failure/no data in buffer. + */ +int read_TCP_packet(const Logger *logger, const Network *ns, Socket sock, uint8_t *data, uint16_t length, const IP_Port *ip_port) +{ + const uint16_t count = net_socket_data_recv_buffer(ns, sock); + + if (count < length) { + LOGGER_TRACE(logger, "recv buffer has %d bytes, but requested %d bytes", count, length); + return -1; + } + + const int len = net_recv(ns, logger, sock, data, length, ip_port); + + if (len != length) { + LOGGER_ERROR(logger, "FAIL recv packet"); + return -1; + } + + return len; +} + +/** @brief Read the next two bytes in TCP stream then convert them to + * length (host byte order). + * + * return length on success + * return 0 if nothing has been read from socket. + * return -1 on failure. + */ +non_null() +static uint16_t read_TCP_length(const Logger *logger, const Network *ns, Socket sock, const IP_Port *ip_port) +{ + const uint16_t count = net_socket_data_recv_buffer(ns, sock); + + if (count >= sizeof(uint16_t)) { + uint8_t length_buf[sizeof(uint16_t)]; + const int len = net_recv(ns, logger, sock, length_buf, sizeof(length_buf), ip_port); + + if (len != sizeof(uint16_t)) { + LOGGER_ERROR(logger, "FAIL recv packet"); + return 0; + } + + uint16_t length; + net_unpack_u16(length_buf, &length); + + if (length > MAX_PACKET_SIZE) { + LOGGER_ERROR(logger, "TCP packet too large: %d > %d", length, MAX_PACKET_SIZE); + return -1; + } + + return length; + } + + return 0; +} + +/** + * @return length of received packet on success. + * @retval 0 if could not read any packet. + * @retval -1 on failure (connection must be killed). + */ +int read_packet_TCP_secure_connection( + const Logger *logger, const Network *ns, Socket sock, uint16_t *next_packet_length, + const uint8_t *shared_key, uint8_t *recv_nonce, uint8_t *data, + uint16_t max_len, const IP_Port *ip_port) +{ + if (*next_packet_length == 0) { + const uint16_t len = read_TCP_length(logger, ns, sock, ip_port); + + if (len == (uint16_t) -1) { + return -1; + } + + if (len == 0) { + return 0; + } + + *next_packet_length = len; + } + + if (max_len + CRYPTO_MAC_SIZE < *next_packet_length) { + LOGGER_DEBUG(logger, "packet too large"); + return -1; + } + + VLA(uint8_t, data_encrypted, *next_packet_length); + const int len_packet = read_TCP_packet(logger, ns, sock, data_encrypted, *next_packet_length, ip_port); + + if (len_packet == -1) { + return 0; + } + + if (len_packet != *next_packet_length) { + LOGGER_ERROR(logger, "invalid packet length: %d, expected %d", len_packet, *next_packet_length); + return 0; + } + + *next_packet_length = 0; + + const int len = decrypt_data_symmetric(shared_key, recv_nonce, data_encrypted, len_packet, data); + + if (len + CRYPTO_MAC_SIZE != len_packet) { + LOGGER_ERROR(logger, "decrypted length %d does not match expected length %d", len + CRYPTO_MAC_SIZE, len_packet); + return -1; + } + + increment_nonce(recv_nonce); + + return len; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/TCP_connection.h b/local_pod_repo/toxcore/toxcore/toxcore/TCP_connection.h new file mode 100644 index 0000000..65ead4d --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/TCP_connection.h @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2015 Tox project. + */ + +/** + * Handles TCP relay connections between two Tox clients. + */ +#ifndef C_TOXCORE_TOXCORE_TCP_CONNECTION_H +#define C_TOXCORE_TOXCORE_TCP_CONNECTION_H + +#include + +#include "DHT.h" // for Node_format +#include "TCP_client.h" +#include "TCP_common.h" + +#define TCP_CONN_NONE 0 +#define TCP_CONN_VALID 1 + +/** NOTE: only used by TCP_con */ +#define TCP_CONN_CONNECTED 2 + +/** Connection is not connected but can be quickly reconnected in case it is needed. */ +#define TCP_CONN_SLEEPING 3 + +#define TCP_CONNECTIONS_STATUS_NONE 0 +#define TCP_CONNECTIONS_STATUS_REGISTERED 1 +#define TCP_CONNECTIONS_STATUS_ONLINE 2 + +#define MAX_FRIEND_TCP_CONNECTIONS 6 + +/** Time until connection to friend gets killed (if it doesn't get locked within that time) */ +#define TCP_CONNECTION_ANNOUNCE_TIMEOUT TCP_CONNECTION_TIMEOUT + +/** @brief The amount of recommended connections for each friend + * NOTE: Must be at most (MAX_FRIEND_TCP_CONNECTIONS / 2) + */ +#define RECOMMENDED_FRIEND_TCP_CONNECTIONS (MAX_FRIEND_TCP_CONNECTIONS / 2) + +/** Number of TCP connections used for onion purposes. */ +#define NUM_ONION_TCP_CONNECTIONS RECOMMENDED_FRIEND_TCP_CONNECTIONS + +typedef struct TCP_Conn_to { + uint32_t tcp_connection; + uint8_t status; + uint8_t connection_id; +} TCP_Conn_to; + +typedef struct TCP_Connection_to { + uint8_t status; + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer */ + + TCP_Conn_to connections[MAX_FRIEND_TCP_CONNECTIONS]; + + int id; /* id used in callbacks. */ +} TCP_Connection_to; + +typedef struct TCP_con { + uint8_t status; + TCP_Client_Connection *connection; + uint64_t connected_time; + uint32_t lock_count; + uint32_t sleep_count; + bool onion; + + /* Only used when connection is sleeping. */ + IP_Port ip_port; + uint8_t relay_pk[CRYPTO_PUBLIC_KEY_SIZE]; + bool unsleep; /* set to 1 to unsleep connection. */ +} TCP_con; + +typedef struct TCP_Connections TCP_Connections; + +non_null() +const uint8_t *tcp_connections_public_key(const TCP_Connections *tcp_c); + +non_null() +uint32_t tcp_connections_count(const TCP_Connections *tcp_c); + +/** Returns the number of connected TCP relays */ +non_null() +uint32_t tcp_connected_relays_count(const TCP_Connections *tcp_c); + +/** @brief Send a packet to the TCP connection. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int send_packet_tcp_connection(const TCP_Connections *tcp_c, int connections_number, const uint8_t *packet, + uint16_t length); + +/** @brief Return a TCP connection number for use in send_tcp_onion_request. + * + * TODO(irungentoo): This number is just the index of an array that the elements + * can change without warning. + * + * return TCP connection number on success. + * return -1 on failure. + */ +non_null() +int get_random_tcp_onion_conn_number(const TCP_Connections *tcp_c); + +/** @brief Put IP_Port of a random onion TCP connection in ip_port. + * + * return true on success. + * return false on failure. + */ +non_null() +bool tcp_get_random_conn_ip_port(const TCP_Connections *tcp_c, IP_Port *ip_port); + +/** @brief Send an onion packet via the TCP relay corresponding to tcp_connections_number. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +int tcp_send_onion_request(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *data, + uint16_t length); + +/** @brief Set if we want TCP_connection to allocate some connection for onion use. + * + * If status is 1, allocate some connections. if status is 0, don't. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +int set_tcp_onion_status(TCP_Connections *tcp_c, bool status); + +/** + * Send a forward request to the TCP relay with IP_Port tcp_forwarder, + * requesting to forward data via a chain of dht nodes starting with dht_node. + * A chain_length of 0 means that dht_node is the final destination of data. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +int tcp_send_forward_request(const Logger *logger, TCP_Connections *tcp_c, const IP_Port *tcp_forwarder, + const IP_Port *dht_node, + const uint8_t *chain_keys, uint16_t chain_length, + const uint8_t *data, uint16_t data_length); + +/** @brief Send an oob packet via the TCP relay corresponding to tcp_connections_number. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +int tcp_send_oob_packet(const TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *public_key, + const uint8_t *packet, uint16_t length); + +typedef int tcp_data_cb(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); + +non_null() +int tcp_send_oob_packet_using_relay(const TCP_Connections *tcp_c, const uint8_t *relay_pk, const uint8_t *public_key, + const uint8_t *packet, uint16_t length); + +/** @brief Set the callback for TCP data packets. */ +non_null() +void set_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_data_cb *tcp_data_callback, void *object); + +typedef int tcp_onion_cb(void *object, const uint8_t *data, uint16_t length, void *userdata); + +/** @brief Set the callback for TCP onion packets. */ +non_null(1) nullable(2, 3) +void set_onion_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_onion_cb *tcp_onion_callback, void *object); + +/** @brief Set the callback for TCP forwarding packets. */ +non_null(1) nullable(2, 3) +void set_forwarding_packet_tcp_connection_callback(TCP_Connections *tcp_c, + forwarded_response_cb *tcp_forwarded_response_callback, + void *object); + + +typedef int tcp_oob_cb(void *object, const uint8_t *public_key, unsigned int tcp_connections_number, + const uint8_t *data, uint16_t length, void *userdata); + +/** @brief Set the callback for TCP oob data packets. */ +non_null() +void set_oob_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_oob_cb *tcp_oob_callback, void *object); + +/** @brief Encode tcp_connections_number as a custom ip_port. + * + * return ip_port. + */ +IP_Port tcp_connections_number_to_ip_port(unsigned int tcp_connections_number); + +/** @brief Decode ip_port created by tcp_connections_number_to_ip_port to tcp_connections_number. + * + * return true on success. + * return false if ip_port is invalid. + */ +non_null() +bool ip_port_to_tcp_connections_number(const IP_Port *ip_port, unsigned int *tcp_connections_number); + +/** @brief Create a new TCP connection to public_key. + * + * public_key must be the counterpart to the secret key that the other peer used with `new_tcp_connections()`. + * + * id is the id in the callbacks for that connection. + * + * return connections_number on success. + * return -1 on failure. + */ +non_null() +int new_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key, int id); + +/** + * @retval 0 on success. + * @retval -1 on failure. + */ +non_null() +int kill_tcp_connection_to(TCP_Connections *tcp_c, int connections_number); + +/** @brief Set connection status. + * + * status of 1 means we are using the connection. + * status of 0 means we are not using it. + * + * Unused tcp connections will be disconnected from but kept in case they are needed. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +int set_tcp_connection_to_status(const TCP_Connections *tcp_c, int connections_number, bool status); + +/** + * @return number of online tcp relays tied to the connection on success. + * @retval 0 on failure. + */ +non_null() +uint32_t tcp_connection_to_online_tcp_relays(const TCP_Connections *tcp_c, int connections_number); + +/** @brief Add a TCP relay tied to a connection. + * + * NOTE: This can only be used during the tcp_oob_callback. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +int add_tcp_number_relay_connection(const TCP_Connections *tcp_c, int connections_number, + unsigned int tcp_connections_number); + +/** @brief Add a TCP relay tied to a connection. + * + * This should be called with the same relay by two peers who want to create a TCP connection with each other. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +int add_tcp_relay_connection(TCP_Connections *tcp_c, int connections_number, const IP_Port *ip_port, + const uint8_t *relay_pk); + +/** @brief Add a TCP relay to the TCP_Connections instance. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +int add_tcp_relay_global(TCP_Connections *tcp_c, const IP_Port *ip_port, const uint8_t *relay_pk); + +/** @brief Copy a maximum of max_num TCP relays we are connected to to tcp_relays. + * + * NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6. + * + * return number of relays copied to tcp_relays on success. + * return 0 on failure. + */ +non_null() +uint32_t tcp_copy_connected_relays(const TCP_Connections *tcp_c, Node_format *tcp_relays, uint16_t max_num); + +/** @brief Copy a maximum of `max_num` TCP relays we are connected to starting at idx. + * + * @param idx is the index in the TCP relay array for `tcp_c` designated. + * If idx is greater than the array length a modulo operation is performed. + * + * Returns the number of relays successfully copied. + */ +non_null() +uint32_t tcp_copy_connected_relays_index(const TCP_Connections *tcp_c, Node_format *tcp_relays, uint16_t max_num, + uint32_t idx); + +/** @brief Returns a new TCP_Connections object associated with the secret_key. + * + * In order for others to connect to this instance `new_tcp_connection_to()` must be called with the + * public_key associated with secret_key. + * + * Returns NULL on failure. + */ +non_null() +TCP_Connections *new_tcp_connections( + const Logger *logger, const Random *rng, const Network *ns, Mono_Time *mono_time, + const uint8_t *secret_key, const TCP_Proxy_Info *proxy_info); + +non_null() +int kill_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number); + +non_null(1, 2) nullable(3) +void do_tcp_connections(const Logger *logger, TCP_Connections *tcp_c, void *userdata); + +nullable(1) +void kill_tcp_connections(TCP_Connections *tcp_c); + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/TCP_connection.m b/local_pod_repo/toxcore/toxcore/toxcore/TCP_connection.m new file mode 100644 index 0000000..c67df1b --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/TCP_connection.m @@ -0,0 +1,1709 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2015 Tox project. + */ + +/** + * Handles TCP relay connections between two Tox clients. + */ +#include "TCP_connection.h" + +#include +#include +#include + +#include "TCP_client.h" +#include "ccompat.h" +#include "mono_time.h" +#include "util.h" + +struct TCP_Connections { + const Logger *logger; + const Random *rng; + Mono_Time *mono_time; + const Network *ns; + DHT *dht; + + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + TCP_Connection_to *connections; + uint32_t connections_length; /* Length of connections array. */ + + TCP_con *tcp_connections; + uint32_t tcp_connections_length; /* Length of tcp_connections array. */ + + tcp_data_cb *tcp_data_callback; + void *tcp_data_callback_object; + + tcp_oob_cb *tcp_oob_callback; + void *tcp_oob_callback_object; + + tcp_onion_cb *tcp_onion_callback; + void *tcp_onion_callback_object; + + forwarded_response_cb *tcp_forwarded_response_callback; + void *tcp_forwarded_response_callback_object; + + TCP_Proxy_Info proxy_info; + + bool onion_status; + uint16_t onion_num_conns; +}; + + +static const TCP_Connection_to empty_tcp_connection_to = {0}; +static const TCP_con empty_tcp_con = {0}; + + +const uint8_t *tcp_connections_public_key(const TCP_Connections *tcp_c) +{ + return tcp_c->self_public_key; +} + + +uint32_t tcp_connections_count(const TCP_Connections *tcp_c) +{ + return tcp_c->tcp_connections_length; +} + +/** @brief Set the size of the array to num. + * + * @retval -1 if realloc fails. + * @retval 0 if it succeeds. + */ +non_null() +static int realloc_TCP_Connection_to(TCP_Connection_to **array, size_t num) +{ + if (num == 0) { + free(*array); + *array = nullptr; + return 0; + } + + TCP_Connection_to *temp_pointer = + (TCP_Connection_to *)realloc(*array, num * sizeof(TCP_Connection_to)); + + if (temp_pointer == nullptr) { + return -1; + } + + *array = temp_pointer; + + return 0; +} + +non_null() +static int realloc_TCP_con(TCP_con **array, size_t num) +{ + if (num == 0) { + free(*array); + *array = nullptr; + return 0; + } + + TCP_con *temp_pointer = (TCP_con *)realloc(*array, num * sizeof(TCP_con)); + + if (temp_pointer == nullptr) { + return -1; + } + + *array = temp_pointer; + + return 0; +} + + +/** + * Return true if the connections_number is valid. + */ +non_null() +static bool connections_number_is_valid(const TCP_Connections *tcp_c, int connections_number) +{ + if ((unsigned int)connections_number >= tcp_c->connections_length) { + return false; + } + + if (tcp_c->connections == nullptr) { + return false; + } + + return tcp_c->connections[connections_number].status != TCP_CONN_NONE; +} + +/** + * Return true if the tcp_connections_number is valid. + */ +non_null() +static bool tcp_connections_number_is_valid(const TCP_Connections *tcp_c, int tcp_connections_number) +{ + if ((uint32_t)tcp_connections_number >= tcp_c->tcp_connections_length) { + return false; + } + + if (tcp_c->tcp_connections == nullptr) { + return false; + } + + return tcp_c->tcp_connections[tcp_connections_number].status != TCP_CONN_NONE; +} + +/** @brief Create a new empty connection. + * + * return -1 on failure. + * return connections_number on success. + */ +non_null() +static int create_connection(TCP_Connections *tcp_c) +{ + for (uint32_t i = 0; i < tcp_c->connections_length; ++i) { + if (tcp_c->connections[i].status == TCP_CONN_NONE) { + return i; + } + } + + int id = -1; + + if (realloc_TCP_Connection_to(&tcp_c->connections, tcp_c->connections_length + 1) == 0) { + id = tcp_c->connections_length; + ++tcp_c->connections_length; + tcp_c->connections[id] = empty_tcp_connection_to; + } + + return id; +} + +/** @brief Create a new empty tcp connection. + * + * return -1 on failure. + * return tcp_connections_number on success. + */ +non_null() +static int create_tcp_connection(TCP_Connections *tcp_c) +{ + for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { + if (tcp_c->tcp_connections[i].status == TCP_CONN_NONE) { + return i; + } + } + + int id = -1; + + if (realloc_TCP_con(&tcp_c->tcp_connections, tcp_c->tcp_connections_length + 1) == 0) { + id = tcp_c->tcp_connections_length; + ++tcp_c->tcp_connections_length; + tcp_c->tcp_connections[id] = empty_tcp_con; + } + + return id; +} + +/** @brief Wipe a connection. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +static int wipe_connection(TCP_Connections *tcp_c, int connections_number) +{ + if (!connections_number_is_valid(tcp_c, connections_number)) { + return -1; + } + + uint32_t i; + tcp_c->connections[connections_number] = empty_tcp_connection_to; + + for (i = tcp_c->connections_length; i != 0; --i) { + if (tcp_c->connections[i - 1].status != TCP_CONN_NONE) { + break; + } + } + + if (tcp_c->connections_length != i) { + tcp_c->connections_length = i; + realloc_TCP_Connection_to(&tcp_c->connections, tcp_c->connections_length); + } + + return 0; +} + +/** @brief Wipe a connection. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +static int wipe_tcp_connection(TCP_Connections *tcp_c, int tcp_connections_number) +{ + if (!tcp_connections_number_is_valid(tcp_c, tcp_connections_number)) { + return -1; + } + + tcp_c->tcp_connections[tcp_connections_number] = empty_tcp_con; + + uint32_t i; + + for (i = tcp_c->tcp_connections_length; i != 0; --i) { + if (tcp_c->tcp_connections[i - 1].status != TCP_CONN_NONE) { + break; + } + } + + if (tcp_c->tcp_connections_length != i) { + tcp_c->tcp_connections_length = i; + realloc_TCP_con(&tcp_c->tcp_connections, tcp_c->tcp_connections_length); + } + + return 0; +} + +non_null() +static TCP_Connection_to *get_connection(const TCP_Connections *tcp_c, int connections_number) +{ + if (!connections_number_is_valid(tcp_c, connections_number)) { + return nullptr; + } + + return &tcp_c->connections[connections_number]; +} + +non_null() +static TCP_con *get_tcp_connection(const TCP_Connections *tcp_c, int tcp_connections_number) +{ + if (!tcp_connections_number_is_valid(tcp_c, tcp_connections_number)) { + return nullptr; + } + + return &tcp_c->tcp_connections[tcp_connections_number]; +} + +/** Returns the number of connected TCP relays */ +uint32_t tcp_connected_relays_count(const TCP_Connections *tcp_c) +{ + uint32_t count = 0; + + for (uint32_t i = 0; i < tcp_connections_count(tcp_c); ++i) { + const TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con == nullptr) { + continue; + } + + if (tcp_con->status == TCP_CONN_CONNECTED) { + ++count; + } + } + + return count; +} + +/** @brief Send a packet to the TCP connection. + * + * return -1 on failure. + * return 0 on success. + */ +int send_packet_tcp_connection(const TCP_Connections *tcp_c, int connections_number, const uint8_t *packet, + uint16_t length) +{ + const TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (con_to == nullptr) { + return -1; + } + + // TODO(irungentoo): detect and kill bad relays. + // TODO(irungentoo): thread safety? + int ret = -1; + + bool limit_reached = false; + + for (uint32_t i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + uint32_t tcp_con_num = con_to->connections[i].tcp_connection; + const uint8_t status = con_to->connections[i].status; + const uint8_t connection_id = con_to->connections[i].connection_id; + + if (tcp_con_num > 0 && status == TCP_CONNECTIONS_STATUS_ONLINE) { + tcp_con_num -= 1; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_con_num); + + if (tcp_con == nullptr) { + continue; + } + + ret = send_data(tcp_c->logger, tcp_con->connection, connection_id, packet, length); + + if (ret == 0) { + limit_reached = true; + } + + if (ret == 1) { + break; + } + } + } + + if (ret == 1) { + return 0; + } + + if (limit_reached) { + return -1; + } + + bool sent_any = false; + + /* Send oob packets to all relays tied to the connection. */ + for (uint32_t i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + uint32_t tcp_con_num = con_to->connections[i].tcp_connection; + const uint8_t status = con_to->connections[i].status; + + if (tcp_con_num > 0 && status == TCP_CONNECTIONS_STATUS_REGISTERED) { + tcp_con_num -= 1; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_con_num); + + if (tcp_con == nullptr) { + continue; + } + + if (send_oob_packet(tcp_c->logger, tcp_con->connection, con_to->public_key, packet, length) == 1) { + sent_any = true; + } + } + } + + return sent_any ? 0 : -1; +} + +/** @brief Return a TCP connection number for use in send_tcp_onion_request. + * + * TODO(irungentoo): This number is just the index of an array that the elements + * can change without warning. + * + * return TCP connection number on success. + * return -1 on failure. + */ +int get_random_tcp_onion_conn_number(const TCP_Connections *tcp_c) +{ + const uint32_t r = random_u32(tcp_c->rng); + + for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { + const uint32_t index = (i + r) % tcp_c->tcp_connections_length; + + if (tcp_c->tcp_connections[index].onion && tcp_c->tcp_connections[index].status == TCP_CONN_CONNECTED) { + return index; + } + } + + return -1; +} + +/** @brief Return TCP connection number of active TCP connection with ip_port. + * + * return TCP connection number on success. + * return -1 on failure. + */ +non_null() +static int get_conn_number_by_ip_port(TCP_Connections *tcp_c, const IP_Port *ip_port) +{ + for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { + const IP_Port conn_ip_port = tcp_con_ip_port(tcp_c->tcp_connections[i].connection); + + if (ipport_equal(ip_port, &conn_ip_port) && + tcp_c->tcp_connections[i].status == TCP_CONN_CONNECTED) { + return i; + } + } + + return -1; +} + +/** @brief Put IP_Port of a random onion TCP connection in ip_port. + * + * return true on success. + * return false on failure. + */ +bool tcp_get_random_conn_ip_port(const TCP_Connections *tcp_c, IP_Port *ip_port) +{ + const int index = get_random_tcp_onion_conn_number(tcp_c); + + if (index == -1) { + return false; + } + + *ip_port = tcp_con_ip_port(tcp_c->tcp_connections[index].connection); + return true; +} + +/** @brief Send an onion packet via the TCP relay corresponding to tcp_connections_number. + * + * return 0 on success. + * return -1 on failure. + */ +int tcp_send_onion_request(TCP_Connections *tcp_c, uint32_t tcp_connections_number, const uint8_t *data, + uint16_t length) +{ + if (tcp_connections_number >= tcp_c->tcp_connections_length) { + return -1; + } + + if (tcp_c->tcp_connections[tcp_connections_number].status == TCP_CONN_CONNECTED) { + const int ret = send_onion_request(tcp_c->logger, tcp_c->tcp_connections[tcp_connections_number].connection, data, + length); + + if (ret == 1) { + return 0; + } + } + + return -1; +} + +/* Send a forward request to the TCP relay with IP_Port tcp_forwarder, + * requesting to forward data via a chain of dht nodes starting with dht_node. + * A chain_length of 0 means that dht_node is the final destination of data. + * + * return 0 on success. + * return -1 on failure. + */ +int tcp_send_forward_request(const Logger *logger, TCP_Connections *tcp_c, const IP_Port *tcp_forwarder, + const IP_Port *dht_node, + const uint8_t *chain_keys, uint16_t chain_length, + const uint8_t *data, uint16_t data_length) +{ + const int index = get_conn_number_by_ip_port(tcp_c, tcp_forwarder); + + if (index == -1) { + return -1; + } + + if (chain_length == 0) { + return send_forward_request_tcp(logger, tcp_c->tcp_connections[index].connection, dht_node, data, + data_length) == 1 ? 0 : -1; + } + + const uint16_t len = forward_chain_packet_size(chain_length, data_length); + VLA(uint8_t, packet, len); + + return create_forward_chain_packet(chain_keys, chain_length, data, data_length, packet) + && send_forward_request_tcp(logger, tcp_c->tcp_connections[index].connection, dht_node, packet, len) == 1 ? 0 : -1; +} + +/** @brief Send an oob packet via the TCP relay corresponding to tcp_connections_number. + * + * return 0 on success. + * return -1 on failure. + */ +int tcp_send_oob_packet(const TCP_Connections *tcp_c, unsigned int tcp_connections_number, + const uint8_t *public_key, const uint8_t *packet, uint16_t length) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + if (tcp_con->status != TCP_CONN_CONNECTED) { + return -1; + } + + const int ret = send_oob_packet(tcp_c->logger, tcp_con->connection, public_key, packet, length); + + if (ret == 1) { + return 0; + } + + return -1; +} + +non_null() +static int find_tcp_connection_relay(const TCP_Connections *tcp_c, const uint8_t *relay_pk); + +/** @brief Send an oob packet via the TCP relay corresponding to relay_pk. + * + * return 0 on success. + * return -1 on failure. + */ +int tcp_send_oob_packet_using_relay(const TCP_Connections *tcp_c, const uint8_t *relay_pk, const uint8_t *public_key, + const uint8_t *packet, uint16_t length) +{ + const int tcp_con_number = find_tcp_connection_relay(tcp_c, relay_pk); + + if (tcp_con_number < 0) { + return -1; + } + + return tcp_send_oob_packet(tcp_c, tcp_con_number, public_key, packet, length); +} + +/** @brief Set the callback for TCP data packets. */ +void set_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_data_cb *tcp_data_callback, void *object) +{ + tcp_c->tcp_data_callback = tcp_data_callback; + tcp_c->tcp_data_callback_object = object; +} + +/** @brief Set the callback for TCP oob data packets. */ +void set_oob_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_oob_cb *tcp_oob_callback, void *object) +{ + tcp_c->tcp_oob_callback = tcp_oob_callback; + tcp_c->tcp_oob_callback_object = object; +} + +/** @brief Set the callback for TCP onion packets. */ +void set_onion_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_onion_cb *tcp_onion_callback, void *object) +{ + tcp_c->tcp_onion_callback = tcp_onion_callback; + tcp_c->tcp_onion_callback_object = object; +} + +/** @brief Set the callback for TCP forwarding packets. */ +void set_forwarding_packet_tcp_connection_callback(TCP_Connections *tcp_c, + forwarded_response_cb *tcp_forwarded_response_callback, + void *object) +{ + tcp_c->tcp_forwarded_response_callback = tcp_forwarded_response_callback; + tcp_c->tcp_forwarded_response_callback_object = object; +} + +/** @brief Encode tcp_connections_number as a custom ip_port. + * + * return ip_port. + */ +IP_Port tcp_connections_number_to_ip_port(unsigned int tcp_connections_number) +{ + IP_Port ip_port = {{{0}}}; + ip_port.ip.family = net_family_tcp_server(); + ip_port.ip.ip.v6.uint32[0] = tcp_connections_number; + return ip_port; +} + +/** @brief Decode ip_port created by tcp_connections_number_to_ip_port to tcp_connections_number. + * + * return true on success. + * return false if ip_port is invalid. + */ +bool ip_port_to_tcp_connections_number(const IP_Port *ip_port, unsigned int *tcp_connections_number) +{ + *tcp_connections_number = ip_port->ip.ip.v6.uint32[0]; + return net_family_is_tcp_server(ip_port->ip.family); +} + +/** @brief Find the TCP connection with public_key. + * + * return connections_number on success. + * return -1 on failure. + */ +non_null() +static int find_tcp_connection_to(const TCP_Connections *tcp_c, const uint8_t *public_key) +{ + for (uint32_t i = 0; i < tcp_c->connections_length; ++i) { + const TCP_Connection_to *con_to = get_connection(tcp_c, i); + + if (con_to != nullptr) { + if (pk_equal(con_to->public_key, public_key)) { + return i; + } + } + } + + return -1; +} + +/** @brief Find the TCP connection to a relay with relay_pk. + * + * return connections_number on success. + * return -1 on failure. + */ +static int find_tcp_connection_relay(const TCP_Connections *tcp_c, const uint8_t *relay_pk) +{ + for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { + const TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con != nullptr) { + if (tcp_con->status == TCP_CONN_SLEEPING) { + if (pk_equal(tcp_con->relay_pk, relay_pk)) { + return i; + } + } else { + if (pk_equal(tcp_con_public_key(tcp_con->connection), relay_pk)) { + return i; + } + } + } + } + + return -1; +} + +/** @brief Create a new TCP connection to public_key. + * + * public_key must be the counterpart to the secret key that the other peer used with `new_tcp_connections()`. + * + * id is the id in the callbacks for that connection. + * + * return connections_number on success. + * return -1 on failure. + */ +int new_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key, int id) +{ + if (find_tcp_connection_to(tcp_c, public_key) != -1) { + return -1; + } + + const int connections_number = create_connection(tcp_c); + + if (connections_number == -1) { + return -1; + } + + TCP_Connection_to *con_to = &tcp_c->connections[connections_number]; + + con_to->status = TCP_CONN_VALID; + memcpy(con_to->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + con_to->id = id; + + return connections_number; +} + +/** + * @retval 0 on success. + * @retval -1 on failure. + */ +int kill_tcp_connection_to(TCP_Connections *tcp_c, int connections_number) +{ + const TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (con_to == nullptr) { + return -1; + } + + for (uint32_t i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection > 0) { + const unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + continue; + } + + if (tcp_con->status == TCP_CONN_CONNECTED) { + send_disconnect_request(tcp_c->logger, tcp_con->connection, con_to->connections[i].connection_id); + } + + if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) { + --tcp_con->lock_count; + + if (con_to->status == TCP_CONN_SLEEPING) { + --tcp_con->sleep_count; + } + } + } + } + + return wipe_connection(tcp_c, connections_number); +} + +/** @brief Set connection status. + * + * status of 1 means we are using the connection. + * status of 0 means we are not using it. + * + * Unused tcp connections will be disconnected from but kept in case they are needed. + * + * return 0 on success. + * return -1 on failure. + */ +int set_tcp_connection_to_status(const TCP_Connections *tcp_c, int connections_number, bool status) +{ + TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (con_to == nullptr) { + return -1; + } + + if (status) { + /* Connection is unsleeping. */ + if (con_to->status != TCP_CONN_SLEEPING) { + return -1; + } + + for (uint32_t i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection > 0) { + const unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + continue; + } + + if (tcp_con->status == TCP_CONN_SLEEPING) { + tcp_con->unsleep = 1; + } + } + } + + con_to->status = TCP_CONN_VALID; + return 0; + } + + /* Connection is going to sleep. */ + if (con_to->status != TCP_CONN_VALID) { + return -1; + } + + for (uint32_t i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection > 0) { + unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + continue; + } + + if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) { + ++tcp_con->sleep_count; + } + } + } + + con_to->status = TCP_CONN_SLEEPING; + return 0; +} + +non_null() +static bool tcp_connection_in_conn(const TCP_Connection_to *con_to, unsigned int tcp_connections_number) +{ + for (uint32_t i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) { + return true; + } + } + + return false; +} + +/** + * @return index on success. + * @retval -1 on failure. + */ +non_null() +static int add_tcp_connection_to_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number) +{ + if (tcp_connection_in_conn(con_to, tcp_connections_number)) { + return -1; + } + + for (uint32_t i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection == 0) { + con_to->connections[i].tcp_connection = tcp_connections_number + 1; + con_to->connections[i].status = TCP_CONNECTIONS_STATUS_NONE; + con_to->connections[i].connection_id = 0; + return i; + } + } + + return -1; +} + +/** + * @return index on success. + * @retval -1 on failure. + */ +non_null() +static int rm_tcp_connection_from_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number) +{ + for (uint32_t i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) { + con_to->connections[i].tcp_connection = 0; + con_to->connections[i].status = TCP_CONNECTIONS_STATUS_NONE; + con_to->connections[i].connection_id = 0; + return i; + } + } + + return -1; +} + +/** + * @return number of online connections on success. + * @retval -1 on failure. + */ +non_null() +static uint32_t online_tcp_connection_from_conn(const TCP_Connection_to *con_to) +{ + uint32_t count = 0; + + for (uint32_t i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection > 0) { + if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) { + ++count; + } + } + } + + return count; +} + +/** + * @return index on success. + * @retval -1 on failure. + */ +non_null() +static int set_tcp_connection_status(TCP_Connection_to *con_to, unsigned int tcp_connections_number, + uint8_t status, + uint8_t connection_id) +{ + for (uint32_t i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) { + + if (con_to->connections[i].status == status) { + return -1; + } + + con_to->connections[i].status = status; + con_to->connections[i].connection_id = connection_id; + return i; + } + } + + return -1; +} + +/** @brief Kill a TCP relay connection. + * + * return 0 on success. + * return -1 on failure. + */ +int kill_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + for (uint32_t i = 0; i < tcp_c->connections_length; ++i) { + TCP_Connection_to *con_to = get_connection(tcp_c, i); + + if (con_to != nullptr) { + rm_tcp_connection_from_conn(con_to, tcp_connections_number); + } + } + + if (tcp_con->onion) { + --tcp_c->onion_num_conns; + } + + kill_TCP_connection(tcp_con->connection); + + return wipe_tcp_connection(tcp_c, tcp_connections_number); +} + +non_null() +static int reconnect_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + if (tcp_con->status == TCP_CONN_SLEEPING) { + return -1; + } + + IP_Port ip_port = tcp_con_ip_port(tcp_con->connection); + uint8_t relay_pk[CRYPTO_PUBLIC_KEY_SIZE]; + memcpy(relay_pk, tcp_con_public_key(tcp_con->connection), CRYPTO_PUBLIC_KEY_SIZE); + kill_TCP_connection(tcp_con->connection); + tcp_con->connection = new_TCP_connection(tcp_c->logger, tcp_c->mono_time, tcp_c->rng, tcp_c->ns, &ip_port, relay_pk, tcp_c->self_public_key, tcp_c->self_secret_key, &tcp_c->proxy_info); + + if (tcp_con->connection == nullptr) { + kill_tcp_relay_connection(tcp_c, tcp_connections_number); + return -1; + } + + for (uint32_t i = 0; i < tcp_c->connections_length; ++i) { + TCP_Connection_to *con_to = get_connection(tcp_c, i); + + if (con_to != nullptr) { + set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_NONE, 0); + } + } + + if (tcp_con->onion) { + --tcp_c->onion_num_conns; + tcp_con->onion = 0; + } + + tcp_con->lock_count = 0; + tcp_con->sleep_count = 0; + tcp_con->connected_time = 0; + tcp_con->status = TCP_CONN_VALID; + tcp_con->unsleep = 0; + + return 0; +} + +non_null() +static int sleep_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + if (tcp_con->status != TCP_CONN_CONNECTED) { + return -1; + } + + if (tcp_con->lock_count != tcp_con->sleep_count) { + return -1; + } + + tcp_con->ip_port = tcp_con_ip_port(tcp_con->connection); + memcpy(tcp_con->relay_pk, tcp_con_public_key(tcp_con->connection), CRYPTO_PUBLIC_KEY_SIZE); + + kill_TCP_connection(tcp_con->connection); + tcp_con->connection = nullptr; + + for (uint32_t i = 0; i < tcp_c->connections_length; ++i) { + TCP_Connection_to *con_to = get_connection(tcp_c, i); + + if (con_to != nullptr) { + set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_NONE, 0); + } + } + + if (tcp_con->onion) { + --tcp_c->onion_num_conns; + tcp_con->onion = 0; + } + + tcp_con->lock_count = 0; + tcp_con->sleep_count = 0; + tcp_con->connected_time = 0; + tcp_con->status = TCP_CONN_SLEEPING; + tcp_con->unsleep = 0; + + return 0; +} + +non_null() +static int unsleep_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + if (tcp_con->status != TCP_CONN_SLEEPING) { + return -1; + } + + tcp_con->connection = new_TCP_connection( + tcp_c->logger, tcp_c->mono_time, tcp_c->rng, tcp_c->ns, &tcp_con->ip_port, + tcp_con->relay_pk, tcp_c->self_public_key, tcp_c->self_secret_key, &tcp_c->proxy_info); + + if (tcp_con->connection == nullptr) { + kill_tcp_relay_connection(tcp_c, tcp_connections_number); + return -1; + } + + tcp_con->lock_count = 0; + tcp_con->sleep_count = 0; + tcp_con->connected_time = 0; + tcp_con->status = TCP_CONN_VALID; + tcp_con->unsleep = 0; + + return 0; +} + +/** @brief Send a TCP routing request. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +static int send_tcp_relay_routing_request(const TCP_Connections *tcp_c, int tcp_connections_number, + const uint8_t *public_key) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + if (tcp_con->status == TCP_CONN_SLEEPING) { + return -1; + } + + if (send_routing_request(tcp_c->logger, tcp_con->connection, public_key) != 1) { + return -1; + } + + return 0; +} + +non_null() +static int tcp_response_callback(void *object, uint8_t connection_id, const uint8_t *public_key) +{ + TCP_Client_Connection *tcp_client_con = (TCP_Client_Connection *)object; + const TCP_Connections *tcp_c = (const TCP_Connections *)tcp_con_custom_object(tcp_client_con); + + const unsigned int tcp_connections_number = tcp_con_custom_uint(tcp_client_con); + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + const int connections_number = find_tcp_connection_to(tcp_c, public_key); + + if (connections_number == -1) { + return -1; + } + + TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (con_to == nullptr) { + return -1; + } + + if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_REGISTERED, connection_id) == -1) { + return -1; + } + + set_tcp_connection_number(tcp_con->connection, connection_id, connections_number); + + return 0; +} + +non_null() +static int tcp_status_callback(void *object, uint32_t number, uint8_t connection_id, uint8_t status) +{ + const TCP_Client_Connection *tcp_client_con = (const TCP_Client_Connection *)object; + const TCP_Connections *tcp_c = (const TCP_Connections *)tcp_con_custom_object(tcp_client_con); + + const unsigned int tcp_connections_number = tcp_con_custom_uint(tcp_client_con); + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + TCP_Connection_to *con_to = get_connection(tcp_c, number); + + if (con_to == nullptr || tcp_con == nullptr) { + return -1; + } + + if (status == 1) { + if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_REGISTERED, connection_id) == -1) { + return -1; + } + + --tcp_con->lock_count; + + if (con_to->status == TCP_CONN_SLEEPING) { + --tcp_con->sleep_count; + } + } else if (status == 2) { + if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_ONLINE, connection_id) == -1) { + return -1; + } + + ++tcp_con->lock_count; + + if (con_to->status == TCP_CONN_SLEEPING) { + ++tcp_con->sleep_count; + } + } + + return 0; +} + +non_null(1, 4) nullable(6) +static int tcp_conn_data_callback(void *object, uint32_t number, uint8_t connection_id, const uint8_t *data, + uint16_t length, void *userdata) +{ + if (length == 0) { + return -1; + } + + const TCP_Client_Connection *tcp_client_con = (TCP_Client_Connection *)object; + TCP_Connections *tcp_c = (TCP_Connections *)tcp_con_custom_object(tcp_client_con); + + const unsigned int tcp_connections_number = tcp_con_custom_uint(tcp_client_con); + const TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + const TCP_Connection_to *con_to = get_connection(tcp_c, number); + + if (con_to == nullptr) { + return -1; + } + + if (tcp_c->tcp_data_callback != nullptr) { + tcp_c->tcp_data_callback(tcp_c->tcp_data_callback_object, con_to->id, data, length, userdata); + } + + return 0; +} + +non_null() +static int tcp_conn_oob_callback(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length, + void *userdata) +{ + if (length == 0) { + return -1; + } + + const TCP_Client_Connection *tcp_client_con = (const TCP_Client_Connection *)object; + TCP_Connections *tcp_c = (TCP_Connections *)tcp_con_custom_object(tcp_client_con); + + const unsigned int tcp_connections_number = tcp_con_custom_uint(tcp_client_con); + const TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + /* TODO(irungentoo): optimize */ + const int connections_number = find_tcp_connection_to(tcp_c, public_key); + + const TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (con_to != nullptr && tcp_connection_in_conn(con_to, tcp_connections_number)) { + return tcp_conn_data_callback(object, connections_number, 0, data, length, userdata); + } + + if (tcp_c->tcp_oob_callback != nullptr) { + tcp_c->tcp_oob_callback(tcp_c->tcp_oob_callback_object, public_key, tcp_connections_number, data, length, userdata); + } + + return 0; +} + +non_null() +static int tcp_onion_callback(void *object, const uint8_t *data, uint16_t length, void *userdata) +{ + TCP_Connections *tcp_c = (TCP_Connections *)object; + + if (tcp_c->tcp_onion_callback != nullptr) { + tcp_c->tcp_onion_callback(tcp_c->tcp_onion_callback_object, data, length, userdata); + } + + return 0; +} + +non_null() +static void tcp_forwarding_callback(void *object, const uint8_t *data, uint16_t length, void *userdata) +{ + TCP_Connections *tcp_c = (TCP_Connections *)object; + + if (tcp_c->tcp_forwarded_response_callback != nullptr) { + tcp_c->tcp_forwarded_response_callback(tcp_c->tcp_forwarded_response_callback_object, data, length, userdata); + } +} + +/** @brief Set callbacks for the TCP relay connection. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +static int tcp_relay_set_callbacks(TCP_Connections *tcp_c, int tcp_connections_number) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + TCP_Client_Connection *con = tcp_con->connection; + + tcp_con_set_custom_object(con, tcp_c); + tcp_con_set_custom_uint(con, tcp_connections_number); + onion_response_handler(con, &tcp_onion_callback, tcp_c); + forwarding_handler(con, &tcp_forwarding_callback, tcp_c); + routing_response_handler(con, &tcp_response_callback, con); + routing_status_handler(con, &tcp_status_callback, con); + routing_data_handler(con, &tcp_conn_data_callback, con); + oob_data_handler(con, &tcp_conn_oob_callback, con); + + return 0; +} + +non_null() +static int tcp_relay_on_online(TCP_Connections *tcp_c, int tcp_connections_number) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + bool sent_any = false; + + for (uint32_t i = 0; i < tcp_c->connections_length; ++i) { + TCP_Connection_to *con_to = get_connection(tcp_c, i); + + if (con_to != nullptr) { + if (tcp_connection_in_conn(con_to, tcp_connections_number)) { + if (send_tcp_relay_routing_request(tcp_c, tcp_connections_number, con_to->public_key) == 0) { + sent_any = true; + } + } + } + } + + tcp_relay_set_callbacks(tcp_c, tcp_connections_number); + tcp_con->status = TCP_CONN_CONNECTED; + + /* If this connection isn't used by any connection, we don't need to wait for them to come online. */ + if (sent_any) { + tcp_con->connected_time = mono_time_get(tcp_c->mono_time); + } else { + tcp_con->connected_time = 0; + } + + if (tcp_c->onion_status && tcp_c->onion_num_conns < NUM_ONION_TCP_CONNECTIONS) { + tcp_con->onion = 1; + ++tcp_c->onion_num_conns; + } + + return 0; +} + +non_null() +static int add_tcp_relay_instance(TCP_Connections *tcp_c, const IP_Port *ip_port, const uint8_t *relay_pk) +{ + IP_Port ipp_copy = *ip_port; + + if (net_family_is_tcp_ipv4(ipp_copy.ip.family)) { + ipp_copy.ip.family = net_family_ipv4(); + } else if (net_family_is_tcp_ipv6(ipp_copy.ip.family)) { + ipp_copy.ip.family = net_family_ipv6(); + } + + if (!net_family_is_ipv4(ipp_copy.ip.family) && !net_family_is_ipv6(ipp_copy.ip.family)) { + return -1; + } + + const int tcp_connections_number = create_tcp_connection(tcp_c); + + if (tcp_connections_number == -1) { + return -1; + } + + TCP_con *tcp_con = &tcp_c->tcp_connections[tcp_connections_number]; + + tcp_con->connection = new_TCP_connection( + tcp_c->logger, tcp_c->mono_time, tcp_c->rng, tcp_c->ns, &ipp_copy, + relay_pk, tcp_c->self_public_key, tcp_c->self_secret_key, &tcp_c->proxy_info); + + if (tcp_con->connection == nullptr) { + return -1; + } + + tcp_con->status = TCP_CONN_VALID; + + return tcp_connections_number; +} + +/** @brief Add a TCP relay to the TCP_Connections instance. + * + * return 0 on success. + * return -1 on failure. + */ +int add_tcp_relay_global(TCP_Connections *tcp_c, const IP_Port *ip_port, const uint8_t *relay_pk) +{ + const int tcp_connections_number = find_tcp_connection_relay(tcp_c, relay_pk); + + if (tcp_connections_number != -1) { + return -1; + } + + if (add_tcp_relay_instance(tcp_c, ip_port, relay_pk) == -1) { + return -1; + } + + return 0; +} + +/** @brief Add a TCP relay tied to a connection. + * + * NOTE: This can only be used during the tcp_oob_callback. + * + * return 0 on success. + * return -1 on failure. + */ +int add_tcp_number_relay_connection(const TCP_Connections *tcp_c, int connections_number, + unsigned int tcp_connections_number) +{ + TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (con_to == nullptr) { + return -1; + } + + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + if (con_to->status != TCP_CONN_SLEEPING && tcp_con->status == TCP_CONN_SLEEPING) { + tcp_con->unsleep = 1; + } + + if (add_tcp_connection_to_conn(con_to, tcp_connections_number) == -1) { + return -1; + } + + if (tcp_con->status == TCP_CONN_CONNECTED) { + if (send_tcp_relay_routing_request(tcp_c, tcp_connections_number, con_to->public_key) == 0) { + tcp_con->connected_time = mono_time_get(tcp_c->mono_time); + } + } + + return 0; +} + +/** @brief Add a TCP relay tied to a connection. + * + * This should be called with the same relay by two peers who want to create a TCP connection with each other. + * + * return 0 on success. + * return -1 on failure. + */ +int add_tcp_relay_connection(TCP_Connections *tcp_c, int connections_number, const IP_Port *ip_port, + const uint8_t *relay_pk) +{ + TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (con_to == nullptr) { + return -1; + } + + int tcp_connections_number = find_tcp_connection_relay(tcp_c, relay_pk); + + if (tcp_connections_number != -1) { + return add_tcp_number_relay_connection(tcp_c, connections_number, tcp_connections_number); + } + + if (online_tcp_connection_from_conn(con_to) >= RECOMMENDED_FRIEND_TCP_CONNECTIONS) { + return -1; + } + + tcp_connections_number = add_tcp_relay_instance(tcp_c, ip_port, relay_pk); + + const TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (tcp_con == nullptr) { + return -1; + } + + if (add_tcp_connection_to_conn(con_to, tcp_connections_number) == -1) { + return -1; + } + + return 0; +} + +/** + * @return number of online tcp relays tied to the connection on success. + * @retval 0 on failure. + */ +uint32_t tcp_connection_to_online_tcp_relays(const TCP_Connections *tcp_c, int connections_number) +{ + const TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (con_to == nullptr) { + return 0; + } + + return online_tcp_connection_from_conn(con_to); +} + +/** @brief Copies the tcp relay from tcp connections designated by `idx` to `tcp_relay`. + * + * Returns true if the relay was successfully copied. + * Returns false if the connection index is invalid, or if the relay is not connected. + */ +non_null() +static bool copy_tcp_relay_conn(const TCP_Connections *tcp_c, Node_format *tcp_relay, uint16_t idx) +{ + const TCP_con *tcp_con = get_tcp_connection(tcp_c, idx); + + if (tcp_con == nullptr) { + return false; + } + + if (tcp_con->status != TCP_CONN_CONNECTED) { + return false; + } + + memcpy(tcp_relay->public_key, tcp_con_public_key(tcp_con->connection), CRYPTO_PUBLIC_KEY_SIZE); + tcp_relay->ip_port = tcp_con_ip_port(tcp_con->connection); + + Family *const family = &tcp_relay->ip_port.ip.family; + + if (net_family_is_ipv4(*family)) { + *family = net_family_tcp_ipv4(); + } else if (net_family_is_ipv6(*family)) { + *family = net_family_tcp_ipv6(); + } + + return true; +} + +/** @brief Copy a maximum of max_num TCP relays we are connected to to tcp_relays. + * + * NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6. + * + * return number of relays copied to tcp_relays on success. + * return 0 on failure. + */ +uint32_t tcp_copy_connected_relays(const TCP_Connections *tcp_c, Node_format *tcp_relays, uint16_t max_num) +{ + const uint32_t r = random_u32(tcp_c->rng); + uint32_t copied = 0; + + for (uint32_t i = 0; (i < tcp_c->tcp_connections_length) && (copied < max_num); ++i) { + const uint16_t idx = (i + r) % tcp_c->tcp_connections_length; + + if (copy_tcp_relay_conn(tcp_c, &tcp_relays[copied], idx)) { + ++copied; + } + } + + return copied; +} + +uint32_t tcp_copy_connected_relays_index(const TCP_Connections *tcp_c, Node_format *tcp_relays, uint16_t max_num, + uint32_t idx) +{ + if (tcp_c->tcp_connections_length == 0) { + return 0; + } + + uint32_t copied = 0; + const uint16_t num_to_copy = min_u16(max_num, tcp_c->tcp_connections_length); + const uint16_t start = idx % tcp_c->tcp_connections_length; + const uint16_t end = (start + num_to_copy) % tcp_c->tcp_connections_length; + + for (uint16_t i = start; i != end; i = (i + 1) % tcp_c->tcp_connections_length) { + if (copy_tcp_relay_conn(tcp_c, &tcp_relays[copied], i)) { + ++copied; + } + } + + return copied; +} + +/** @brief Set if we want TCP_connection to allocate some connection for onion use. + * + * If status is 1, allocate some connections. if status is 0, don't. + * + * return 0 on success. + * return -1 on failure. + */ +int set_tcp_onion_status(TCP_Connections *tcp_c, bool status) +{ + if (tcp_c->onion_status == status) { + return -1; + } + + if (status) { + for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { + TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con != nullptr) { + if (tcp_con->status == TCP_CONN_CONNECTED && !tcp_con->onion) { + ++tcp_c->onion_num_conns; + tcp_con->onion = 1; + } + } + + if (tcp_c->onion_num_conns >= NUM_ONION_TCP_CONNECTIONS) { + break; + } + } + + if (tcp_c->onion_num_conns < NUM_ONION_TCP_CONNECTIONS) { + const unsigned int wakeup = NUM_ONION_TCP_CONNECTIONS - tcp_c->onion_num_conns; + + for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { + TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con != nullptr) { + if (tcp_con->status == TCP_CONN_SLEEPING) { + tcp_con->unsleep = 1; + } + } + + if (wakeup == 0) { + break; + } + } + } + + tcp_c->onion_status = 1; + } else { + for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { + TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con != nullptr) { + if (tcp_con->onion) { + --tcp_c->onion_num_conns; + tcp_con->onion = 0; + } + } + } + + tcp_c->onion_status = 0; + } + + return 0; +} + +/** @brief Returns a new TCP_Connections object associated with the secret_key. + * + * In order for others to connect to this instance `new_tcp_connection_to()` must be called with the + * public_key associated with secret_key. + * + * Returns NULL on failure. + */ +TCP_Connections *new_tcp_connections( + const Logger *logger, const Random *rng, const Network *ns, Mono_Time *mono_time, const uint8_t *secret_key, + const TCP_Proxy_Info *proxy_info) +{ + if (secret_key == nullptr) { + return nullptr; + } + + TCP_Connections *temp = (TCP_Connections *)calloc(1, sizeof(TCP_Connections)); + + if (temp == nullptr) { + return nullptr; + } + + temp->logger = logger; + temp->rng = rng; + temp->mono_time = mono_time; + temp->ns = ns; + + memcpy(temp->self_secret_key, secret_key, CRYPTO_SECRET_KEY_SIZE); + crypto_derive_public_key(temp->self_public_key, temp->self_secret_key); + temp->proxy_info = *proxy_info; + + return temp; +} + +non_null(1, 2) nullable(3) +static void do_tcp_conns(const Logger *logger, TCP_Connections *tcp_c, void *userdata) +{ + for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { + TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con == nullptr) { + continue; + } + + if (tcp_con->status != TCP_CONN_SLEEPING) { + do_TCP_connection(logger, tcp_c->mono_time, tcp_con->connection, userdata); + + /* callbacks can change TCP connection address. */ + tcp_con = get_tcp_connection(tcp_c, i); + + // Make sure the TCP connection wasn't dropped in any of the callbacks. + assert(tcp_con != nullptr); + + if (tcp_con_status(tcp_con->connection) == TCP_CLIENT_DISCONNECTED) { + if (tcp_con->status == TCP_CONN_CONNECTED) { + reconnect_tcp_relay_connection(tcp_c, i); + } else { + kill_tcp_relay_connection(tcp_c, i); + } + + continue; + } + + if (tcp_con->status == TCP_CONN_VALID && tcp_con_status(tcp_con->connection) == TCP_CLIENT_CONFIRMED) { + tcp_relay_on_online(tcp_c, i); + } + + if (tcp_con->status == TCP_CONN_CONNECTED + && !tcp_con->onion && tcp_con->lock_count > 0 + && tcp_con->lock_count == tcp_con->sleep_count + && mono_time_is_timeout(tcp_c->mono_time, tcp_con->connected_time, TCP_CONNECTION_ANNOUNCE_TIMEOUT)) { + sleep_tcp_relay_connection(tcp_c, i); + } + } + + if (tcp_con->status == TCP_CONN_SLEEPING && tcp_con->unsleep) { + unsleep_tcp_relay_connection(tcp_c, i); + } + } +} + +non_null() +static void kill_nonused_tcp(TCP_Connections *tcp_c) +{ + if (tcp_c->tcp_connections_length <= RECOMMENDED_FRIEND_TCP_CONNECTIONS) { + return; + } + + const uint32_t num_online = tcp_connected_relays_count(tcp_c); + + if (num_online <= RECOMMENDED_FRIEND_TCP_CONNECTIONS) { + return; + } + + const uint32_t max_kill_count = num_online - RECOMMENDED_FRIEND_TCP_CONNECTIONS; + uint32_t kill_count = 0; + + for (uint32_t i = 0; i < tcp_c->tcp_connections_length && kill_count < max_kill_count; ++i) { + const TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con == nullptr) { + continue; + } + + if (tcp_con->status == TCP_CONN_CONNECTED) { + if (tcp_con->onion || tcp_con->lock_count > 0) { // connection is in use so we skip it + continue; + } + + if (mono_time_is_timeout(tcp_c->mono_time, tcp_con->connected_time, TCP_CONNECTION_ANNOUNCE_TIMEOUT)) { + kill_tcp_relay_connection(tcp_c, i); + ++kill_count; + } + } + } +} + +void do_tcp_connections(const Logger *logger, TCP_Connections *tcp_c, void *userdata) +{ + do_tcp_conns(logger, tcp_c, userdata); + kill_nonused_tcp(tcp_c); +} + +void kill_tcp_connections(TCP_Connections *tcp_c) +{ + if (tcp_c == nullptr) { + return; + } + + for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { + kill_TCP_connection(tcp_c->tcp_connections[i].connection); + } + + crypto_memzero(tcp_c->self_secret_key, sizeof(tcp_c->self_secret_key)); + + free(tcp_c->tcp_connections); + free(tcp_c->connections); + free(tcp_c); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/TCP_server.h b/local_pod_repo/toxcore/toxcore/toxcore/TCP_server.h new file mode 100644 index 0000000..2224938 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/TCP_server.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * Implementation of the TCP relay server part of Tox. + */ +#ifndef C_TOXCORE_TOXCORE_TCP_SERVER_H +#define C_TOXCORE_TOXCORE_TCP_SERVER_H + +#include "crypto_core.h" +#include "forwarding.h" +#include "onion.h" + +#define MAX_INCOMING_CONNECTIONS 256 + +#define TCP_MAX_BACKLOG MAX_INCOMING_CONNECTIONS + +#define ARRAY_ENTRY_SIZE 6 + +typedef enum TCP_Status { + TCP_STATUS_NO_STATUS, + TCP_STATUS_CONNECTED, + TCP_STATUS_UNCONFIRMED, + TCP_STATUS_CONFIRMED, +} TCP_Status; + +typedef struct TCP_Server TCP_Server; + +non_null() +const uint8_t *tcp_server_public_key(const TCP_Server *tcp_server); +non_null() +size_t tcp_server_listen_count(const TCP_Server *tcp_server); + +/** Create new TCP server instance. */ +non_null(1, 2, 3, 6, 7) nullable(8, 9) +TCP_Server *new_TCP_server(const Logger *logger, const Random *rng, const Network *ns, + bool ipv6_enabled, uint16_t num_sockets, const uint16_t *ports, + const uint8_t *secret_key, Onion *onion, Forwarding *forwarding); + +/** Run the TCP_server */ +non_null() +void do_TCP_server(TCP_Server *tcp_server, const Mono_Time *mono_time); + +/** Kill the TCP server */ +nullable(1) +void kill_TCP_server(TCP_Server *tcp_server); + + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/TCP_server.m b/local_pod_repo/toxcore/toxcore/toxcore/TCP_server.m new file mode 100644 index 0000000..f571a31 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/TCP_server.m @@ -0,0 +1,1408 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * Implementation of the TCP relay server part of Tox. + */ +#include "TCP_server.h" + +#include +#include +#if !defined(_WIN32) && !defined(__WIN32__) && !defined (WIN32) +#include +#endif + +#ifdef TCP_SERVER_USE_EPOLL +#include +#include +#endif + +#include "TCP_common.h" +#include "ccompat.h" +#include "list.h" +#include "mono_time.h" +#include "util.h" + +#ifdef TCP_SERVER_USE_EPOLL +#define TCP_SOCKET_LISTENING 0 +#define TCP_SOCKET_INCOMING 1 +#define TCP_SOCKET_UNCONFIRMED 2 +#define TCP_SOCKET_CONFIRMED 3 +#endif + +typedef struct TCP_Secure_Conn { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint32_t index; + // TODO(iphydf): Add an enum for this (same as in TCP_client.c, probably). + uint8_t status; /* 0 if not used, 1 if other is offline, 2 if other is online. */ + uint8_t other_id; +} TCP_Secure_Conn; + +typedef struct TCP_Secure_Connection { + TCP_Connection con; + + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ + uint16_t next_packet_length; + TCP_Secure_Conn connections[NUM_CLIENT_CONNECTIONS]; + uint8_t status; + + uint64_t identifier; + + uint64_t last_pinged; + uint64_t ping_id; +} TCP_Secure_Connection; + + +struct TCP_Server { + const Logger *logger; + const Random *rng; + const Network *ns; + Onion *onion; + Forwarding *forwarding; + +#ifdef TCP_SERVER_USE_EPOLL + int efd; + uint64_t last_run_pinged; +#endif + Socket *socks_listening; + unsigned int num_listening_socks; + + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t secret_key[CRYPTO_SECRET_KEY_SIZE]; + TCP_Secure_Connection incoming_connection_queue[MAX_INCOMING_CONNECTIONS]; + uint16_t incoming_connection_queue_index; + TCP_Secure_Connection unconfirmed_connection_queue[MAX_INCOMING_CONNECTIONS]; + uint16_t unconfirmed_connection_queue_index; + + TCP_Secure_Connection *accepted_connection_array; + uint32_t size_accepted_connections; + uint32_t num_accepted_connections; + + uint64_t counter; + + BS_List accepted_key_list; +}; + +const uint8_t *tcp_server_public_key(const TCP_Server *tcp_server) +{ + return tcp_server->public_key; +} + +size_t tcp_server_listen_count(const TCP_Server *tcp_server) +{ + return tcp_server->num_listening_socks; +} + +/** This is needed to compile on Android below API 21 */ +#ifdef TCP_SERVER_USE_EPOLL +#ifndef EPOLLRDHUP +#define EPOLLRDHUP 0x2000 +#endif +#endif + +/** @brief Increase the size of the connection list + * + * @retval -1 on failure + * @retval 0 on success. + */ +non_null() +static int alloc_new_connections(TCP_Server *tcp_server, uint32_t num) +{ + const uint32_t new_size = tcp_server->size_accepted_connections + num; + + if (new_size < tcp_server->size_accepted_connections) { + return -1; + } + + TCP_Secure_Connection *new_connections = (TCP_Secure_Connection *)realloc( + tcp_server->accepted_connection_array, + new_size * sizeof(TCP_Secure_Connection)); + + if (new_connections == nullptr) { + return -1; + } + + const uint32_t old_size = tcp_server->size_accepted_connections; + const uint32_t size_new_entries = num * sizeof(TCP_Secure_Connection); + memset(new_connections + old_size, 0, size_new_entries); + + tcp_server->accepted_connection_array = new_connections; + tcp_server->size_accepted_connections = new_size; + return 0; +} + +non_null() +static void wipe_secure_connection(TCP_Secure_Connection *con) +{ + if (con->status != 0) { + wipe_priority_list(con->con.priority_queue_start); + crypto_memzero(con, sizeof(TCP_Secure_Connection)); + } +} + +non_null() +static void move_secure_connection(TCP_Secure_Connection *con_new, TCP_Secure_Connection *con_old) +{ + *con_new = *con_old; + crypto_memzero(con_old, sizeof(TCP_Secure_Connection)); +} + +non_null() +static void free_accepted_connection_array(TCP_Server *tcp_server) +{ + if (tcp_server->accepted_connection_array == nullptr) { + return; + } + + for (uint32_t i = 0; i < tcp_server->size_accepted_connections; ++i) { + wipe_secure_connection(&tcp_server->accepted_connection_array[i]); + } + + free(tcp_server->accepted_connection_array); + tcp_server->accepted_connection_array = nullptr; + tcp_server->size_accepted_connections = 0; +} + +/** + * @return index corresponding to connection with peer on success + * @retval -1 on failure. + */ +non_null() +static int get_TCP_connection_index(const TCP_Server *tcp_server, const uint8_t *public_key) +{ + return bs_list_find(&tcp_server->accepted_key_list, public_key); +} + + +non_null() +static int kill_accepted(TCP_Server *tcp_server, int index); + +/** @brief Add accepted TCP connection to the list. + * + * @return index on success + * @retval -1 on failure + */ +non_null() +static int add_accepted(TCP_Server *tcp_server, const Mono_Time *mono_time, TCP_Secure_Connection *con) +{ + int index = get_TCP_connection_index(tcp_server, con->public_key); + + if (index != -1) { /* If an old connection to the same public key exists, kill it. */ + kill_accepted(tcp_server, index); + index = -1; + } + + if (tcp_server->size_accepted_connections == tcp_server->num_accepted_connections) { + if (alloc_new_connections(tcp_server, 4) == -1) { + return -1; + } + + index = tcp_server->num_accepted_connections; + } else { + for (uint32_t i = tcp_server->size_accepted_connections; i != 0; --i) { + if (tcp_server->accepted_connection_array[i - 1].status == TCP_STATUS_NO_STATUS) { + index = i - 1; + break; + } + } + } + + if (index == -1) { + LOGGER_ERROR(tcp_server->logger, "FAIL index is -1"); + return -1; + } + + if (!bs_list_add(&tcp_server->accepted_key_list, con->public_key, index)) { + return -1; + } + + move_secure_connection(&tcp_server->accepted_connection_array[index], con); + + tcp_server->accepted_connection_array[index].status = TCP_STATUS_CONFIRMED; + ++tcp_server->num_accepted_connections; + tcp_server->accepted_connection_array[index].identifier = ++tcp_server->counter; + tcp_server->accepted_connection_array[index].last_pinged = mono_time_get(mono_time); + tcp_server->accepted_connection_array[index].ping_id = 0; + + return index; +} + +/** @brief Delete accepted connection from list. + * + * @retval 0 on success + * @retval -1 on failure + */ +non_null() +static int del_accepted(TCP_Server *tcp_server, int index) +{ + if ((uint32_t)index >= tcp_server->size_accepted_connections) { + return -1; + } + + if (tcp_server->accepted_connection_array[index].status == TCP_STATUS_NO_STATUS) { + return -1; + } + + if (!bs_list_remove(&tcp_server->accepted_key_list, tcp_server->accepted_connection_array[index].public_key, index)) { + return -1; + } + + wipe_secure_connection(&tcp_server->accepted_connection_array[index]); + --tcp_server->num_accepted_connections; + + if (tcp_server->num_accepted_connections == 0) { + free_accepted_connection_array(tcp_server); + } + + return 0; +} + +/** Kill a TCP_Secure_Connection */ +non_null() +static void kill_TCP_secure_connection(TCP_Secure_Connection *con) +{ + kill_sock(con->con.ns, con->con.sock); + wipe_secure_connection(con); +} + +non_null() +static int rm_connection_index(TCP_Server *tcp_server, TCP_Secure_Connection *con, uint8_t con_number); + +/** @brief Kill an accepted TCP_Secure_Connection + * + * return -1 on failure. + * return 0 on success. + */ +static int kill_accepted(TCP_Server *tcp_server, int index) +{ + if ((uint32_t)index >= tcp_server->size_accepted_connections) { + return -1; + } + + for (uint32_t i = 0; i < NUM_CLIENT_CONNECTIONS; ++i) { + rm_connection_index(tcp_server, &tcp_server->accepted_connection_array[index], i); + } + + const Socket sock = tcp_server->accepted_connection_array[index].con.sock; + + if (del_accepted(tcp_server, index) != 0) { + return -1; + } + + kill_sock(tcp_server->ns, sock); + return 0; +} + +/** + * @retval 1 if everything went well. + * @retval -1 if the connection must be killed. + */ +non_null() +static int handle_TCP_handshake(const Logger *logger, TCP_Secure_Connection *con, const uint8_t *data, uint16_t length, + const uint8_t *self_secret_key) +{ + if (length != TCP_CLIENT_HANDSHAKE_SIZE) { + LOGGER_ERROR(logger, "invalid handshake length: %d != %d", length, TCP_CLIENT_HANDSHAKE_SIZE); + return -1; + } + + if (con->status != TCP_STATUS_CONNECTED) { + LOGGER_ERROR(logger, "TCP connection %u not connected", (unsigned int)con->identifier); + return -1; + } + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + encrypt_precompute(data, self_secret_key, shared_key); + uint8_t plain[TCP_HANDSHAKE_PLAIN_SIZE]; + int len = decrypt_data_symmetric(shared_key, data + CRYPTO_PUBLIC_KEY_SIZE, + data + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, TCP_HANDSHAKE_PLAIN_SIZE + CRYPTO_MAC_SIZE, plain); + + if (len != TCP_HANDSHAKE_PLAIN_SIZE) { + LOGGER_ERROR(logger, "invalid TCP handshake decrypted length: %d != %d", len, TCP_HANDSHAKE_PLAIN_SIZE); + crypto_memzero(shared_key, sizeof(shared_key)); + return -1; + } + + memcpy(con->public_key, data, CRYPTO_PUBLIC_KEY_SIZE); + uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE]; + uint8_t resp_plain[TCP_HANDSHAKE_PLAIN_SIZE]; + crypto_new_keypair(con->con.rng, resp_plain, temp_secret_key); + random_nonce(con->con.rng, con->con.sent_nonce); + memcpy(resp_plain + CRYPTO_PUBLIC_KEY_SIZE, con->con.sent_nonce, CRYPTO_NONCE_SIZE); + memcpy(con->recv_nonce, plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_NONCE_SIZE); + + uint8_t response[TCP_SERVER_HANDSHAKE_SIZE]; + random_nonce(con->con.rng, response); + + len = encrypt_data_symmetric(shared_key, response, resp_plain, TCP_HANDSHAKE_PLAIN_SIZE, + response + CRYPTO_NONCE_SIZE); + + if (len != TCP_HANDSHAKE_PLAIN_SIZE + CRYPTO_MAC_SIZE) { + crypto_memzero(shared_key, sizeof(shared_key)); + return -1; + } + + IP_Port ipp = {{{0}}}; + + if (TCP_SERVER_HANDSHAKE_SIZE != net_send(con->con.ns, logger, con->con.sock, response, TCP_SERVER_HANDSHAKE_SIZE, &ipp)) { + crypto_memzero(shared_key, sizeof(shared_key)); + return -1; + } + + encrypt_precompute(plain, temp_secret_key, con->con.shared_key); + con->status = TCP_STATUS_UNCONFIRMED; + + crypto_memzero(shared_key, sizeof(shared_key)); + + return 1; +} + +/** + * @retval 1 if connection handshake was handled correctly. + * @retval 0 if we didn't get it yet. + * @retval -1 if the connection must be killed. + */ +non_null() +static int read_connection_handshake(const Logger *logger, TCP_Secure_Connection *con, const uint8_t *self_secret_key) +{ + uint8_t data[TCP_CLIENT_HANDSHAKE_SIZE]; + const int len = read_TCP_packet(logger, con->con.ns, con->con.sock, data, TCP_CLIENT_HANDSHAKE_SIZE, &con->con.ip_port); + + if (len == -1) { + LOGGER_TRACE(logger, "connection handshake is not ready yet"); + return 0; + } + + return handle_TCP_handshake(logger, con, data, len, self_secret_key); +} + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +non_null() +static int send_routing_response(const Logger *logger, TCP_Secure_Connection *con, uint8_t rpid, + const uint8_t *public_key) +{ + uint8_t data[1 + 1 + CRYPTO_PUBLIC_KEY_SIZE]; + data[0] = TCP_PACKET_ROUTING_RESPONSE; + data[1] = rpid; + memcpy(data + 2, public_key, CRYPTO_PUBLIC_KEY_SIZE); + + return write_packet_TCP_secure_connection(logger, &con->con, data, sizeof(data), true); +} + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +non_null() +static int send_connect_notification(const Logger *logger, TCP_Secure_Connection *con, uint8_t id) +{ + uint8_t data[2] = {TCP_PACKET_CONNECTION_NOTIFICATION, (uint8_t)(id + NUM_RESERVED_PORTS)}; + return write_packet_TCP_secure_connection(logger, &con->con, data, sizeof(data), true); +} + +/** + * @retval 1 on success. + * @retval 0 if could not send packet. + * @retval -1 on failure (connection must be killed). + */ +non_null() +static int send_disconnect_notification(const Logger *logger, TCP_Secure_Connection *con, uint8_t id) +{ + uint8_t data[2] = {TCP_PACKET_DISCONNECT_NOTIFICATION, (uint8_t)(id + NUM_RESERVED_PORTS)}; + return write_packet_TCP_secure_connection(logger, &con->con, data, sizeof(data), true); +} + +/** + * @retval 0 on success. + * @retval -1 on failure (connection must be killed). + */ +non_null() +static int handle_TCP_routing_req(TCP_Server *tcp_server, uint32_t con_id, const uint8_t *public_key) +{ + uint32_t index = -1; + TCP_Secure_Connection *con = &tcp_server->accepted_connection_array[con_id]; + + /* If person tries to cennect to himself we deny the request*/ + if (pk_equal(con->public_key, public_key)) { + if (send_routing_response(tcp_server->logger, con, 0, public_key) == -1) { + return -1; + } + + return 0; + } + + for (uint32_t i = 0; i < NUM_CLIENT_CONNECTIONS; ++i) { + if (con->connections[i].status != 0) { + if (pk_equal(public_key, con->connections[i].public_key)) { + if (send_routing_response(tcp_server->logger, con, i + NUM_RESERVED_PORTS, public_key) == -1) { + return -1; + } + + return 0; + } + } else if (index == (uint32_t) -1) { + index = i; + } + } + + if (index == (uint32_t) -1) { + if (send_routing_response(tcp_server->logger, con, 0, public_key) == -1) { + return -1; + } + + return 0; + } + + const int ret = send_routing_response(tcp_server->logger, con, index + NUM_RESERVED_PORTS, public_key); + + if (ret == 0) { + return 0; + } + + if (ret == -1) { + return -1; + } + + con->connections[index].status = 1; + memcpy(con->connections[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + const int other_index = get_TCP_connection_index(tcp_server, public_key); + + if (other_index != -1) { + uint32_t other_id = -1; + TCP_Secure_Connection *other_conn = &tcp_server->accepted_connection_array[other_index]; + + for (uint32_t i = 0; i < NUM_CLIENT_CONNECTIONS; ++i) { + if (other_conn->connections[i].status == 1 + && pk_equal(other_conn->connections[i].public_key, con->public_key)) { + other_id = i; + break; + } + } + + if (other_id != (uint32_t) -1) { + con->connections[index].status = 2; + con->connections[index].index = other_index; + con->connections[index].other_id = other_id; + other_conn->connections[other_id].status = 2; + other_conn->connections[other_id].index = con_id; + other_conn->connections[other_id].other_id = index; + // TODO(irungentoo): return values? + send_connect_notification(tcp_server->logger, con, index); + send_connect_notification(tcp_server->logger, other_conn, other_id); + } + } + + return 0; +} + +/** + * @retval 0 on success. + * @retval -1 on failure (connection must be killed). + */ +non_null() +static int handle_TCP_oob_send(TCP_Server *tcp_server, uint32_t con_id, const uint8_t *public_key, const uint8_t *data, + uint16_t length) +{ + if (length == 0 || length > TCP_MAX_OOB_DATA_LENGTH) { + return -1; + } + + const TCP_Secure_Connection *con = &tcp_server->accepted_connection_array[con_id]; + + const int other_index = get_TCP_connection_index(tcp_server, public_key); + + if (other_index != -1) { + VLA(uint8_t, resp_packet, 1 + CRYPTO_PUBLIC_KEY_SIZE + length); + resp_packet[0] = TCP_PACKET_OOB_RECV; + memcpy(resp_packet + 1, con->public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(resp_packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, data, length); + write_packet_TCP_secure_connection(tcp_server->logger, &tcp_server->accepted_connection_array[other_index].con, + resp_packet, SIZEOF_VLA(resp_packet), false); + } + + return 0; +} + +/** @brief Remove connection with con_number from the connections array of con. + * + * return -1 on failure. + * return 0 on success. + */ +static int rm_connection_index(TCP_Server *tcp_server, TCP_Secure_Connection *con, uint8_t con_number) +{ + if (con_number >= NUM_CLIENT_CONNECTIONS) { + return -1; + } + + if (con->connections[con_number].status != 0) { + if (con->connections[con_number].status == 2) { + const uint32_t index = con->connections[con_number].index; + const uint8_t other_id = con->connections[con_number].other_id; + + if (index >= tcp_server->size_accepted_connections) { + return -1; + } + + tcp_server->accepted_connection_array[index].connections[other_id].other_id = 0; + tcp_server->accepted_connection_array[index].connections[other_id].index = 0; + tcp_server->accepted_connection_array[index].connections[other_id].status = 1; + // TODO(irungentoo): return values? + send_disconnect_notification(tcp_server->logger, &tcp_server->accepted_connection_array[index], other_id); + } + + con->connections[con_number].index = 0; + con->connections[con_number].other_id = 0; + con->connections[con_number].status = 0; + return 0; + } + + return -1; +} + +/** @brief Encode con_id and identifier as a custom IP_Port. + * + * @return ip_port. + */ +static IP_Port con_id_to_ip_port(uint32_t con_id, uint64_t identifier) +{ + IP_Port ip_port = {{{0}}}; + ip_port.ip.family = net_family_tcp_client(); + ip_port.ip.ip.v6.uint32[0] = con_id; + ip_port.ip.ip.v6.uint64[1] = identifier; + return ip_port; + +} + +/** @brief Decode ip_port created by con_id_to_ip_port to con_id. + * + * @retval true on success. + * @retval false if ip_port is invalid. + */ +non_null() +static bool ip_port_to_con_id(const TCP_Server *tcp_server, const IP_Port *ip_port, uint32_t *con_id) +{ + *con_id = ip_port->ip.ip.v6.uint32[0]; + + return net_family_is_tcp_client(ip_port->ip.family) && + *con_id < tcp_server->size_accepted_connections && + tcp_server->accepted_connection_array[*con_id].identifier == ip_port->ip.ip.v6.uint64[1]; +} + +non_null() +static int handle_onion_recv_1(void *object, const IP_Port *dest, const uint8_t *data, uint16_t length) +{ + TCP_Server *tcp_server = (TCP_Server *)object; + uint32_t index; + + if (!ip_port_to_con_id(tcp_server, dest, &index)) { + return 1; + } + + TCP_Secure_Connection *con = &tcp_server->accepted_connection_array[index]; + + VLA(uint8_t, packet, 1 + length); + memcpy(packet + 1, data, length); + packet[0] = TCP_PACKET_ONION_RESPONSE; + + if (write_packet_TCP_secure_connection(tcp_server->logger, &con->con, packet, SIZEOF_VLA(packet), false) != 1) { + return 1; + } + + return 0; +} + +non_null() +static bool handle_forward_reply_tcp(void *object, const uint8_t *sendback_data, uint16_t sendback_data_len, + const uint8_t *data, uint16_t length) +{ + TCP_Server *tcp_server = (TCP_Server *)object; + + if (sendback_data_len != 1 + sizeof(uint32_t) + sizeof(uint64_t)) { + return false; + } + + if (*sendback_data != SENDBACK_TCP) { + return false; + } + + uint32_t con_id; + uint64_t identifier; + net_unpack_u32(sendback_data + 1, &con_id); + net_unpack_u64(sendback_data + 1 + sizeof(uint32_t), &identifier); + + if (con_id >= tcp_server->size_accepted_connections) { + return false; + } + + TCP_Secure_Connection *con = &tcp_server->accepted_connection_array[con_id]; + + if (con->identifier != identifier) { + return false; + } + + VLA(uint8_t, packet, 1 + length); + memcpy(packet + 1, data, length); + packet[0] = TCP_PACKET_FORWARDING; + + return write_packet_TCP_secure_connection(tcp_server->logger, &con->con, packet, SIZEOF_VLA(packet), false) == 1; +} + +/** + * @retval 0 on success + * @retval -1 on failure + */ +non_null() +static int handle_TCP_packet(TCP_Server *tcp_server, uint32_t con_id, const uint8_t *data, uint16_t length) +{ + if (length == 0) { + return -1; + } + + TCP_Secure_Connection *const con = &tcp_server->accepted_connection_array[con_id]; + + switch (data[0]) { + case TCP_PACKET_ROUTING_REQUEST: { + if (length != 1 + CRYPTO_PUBLIC_KEY_SIZE) { + return -1; + } + + LOGGER_TRACE(tcp_server->logger, "handling routing request for %d", con_id); + return handle_TCP_routing_req(tcp_server, con_id, data + 1); + } + + case TCP_PACKET_CONNECTION_NOTIFICATION: { + if (length != 2) { + return -1; + } + + LOGGER_TRACE(tcp_server->logger, "handling connection notification for %d", con_id); + break; + } + + case TCP_PACKET_DISCONNECT_NOTIFICATION: { + if (length != 2) { + return -1; + } + + LOGGER_TRACE(tcp_server->logger, "handling disconnect notification for %d", con_id); + return rm_connection_index(tcp_server, con, data[1] - NUM_RESERVED_PORTS); + } + + case TCP_PACKET_PING: { + if (length != 1 + sizeof(uint64_t)) { + return -1; + } + + LOGGER_TRACE(tcp_server->logger, "handling ping for %d", con_id); + + uint8_t response[1 + sizeof(uint64_t)]; + response[0] = TCP_PACKET_PONG; + memcpy(response + 1, data + 1, sizeof(uint64_t)); + write_packet_TCP_secure_connection(tcp_server->logger, &con->con, response, sizeof(response), true); + return 0; + } + + case TCP_PACKET_PONG: { + if (length != 1 + sizeof(uint64_t)) { + return -1; + } + + LOGGER_TRACE(tcp_server->logger, "handling pong for %d", con_id); + + uint64_t ping_id; + memcpy(&ping_id, data + 1, sizeof(uint64_t)); + + if (ping_id != 0) { + if (ping_id == con->ping_id) { + con->ping_id = 0; + } + + return 0; + } + + return -1; + } + + case TCP_PACKET_OOB_SEND: { + if (length <= 1 + CRYPTO_PUBLIC_KEY_SIZE) { + return -1; + } + + LOGGER_TRACE(tcp_server->logger, "handling oob send for %d", con_id); + + return handle_TCP_oob_send(tcp_server, con_id, data + 1, data + 1 + CRYPTO_PUBLIC_KEY_SIZE, + length - (1 + CRYPTO_PUBLIC_KEY_SIZE)); + } + + case TCP_PACKET_ONION_REQUEST: { + LOGGER_TRACE(tcp_server->logger, "handling onion request for %d", con_id); + + if (tcp_server->onion != nullptr) { + if (length <= 1 + CRYPTO_NONCE_SIZE + ONION_SEND_BASE * 2) { + return -1; + } + + IP_Port source = con_id_to_ip_port(con_id, con->identifier); + onion_send_1(tcp_server->onion, data + 1 + CRYPTO_NONCE_SIZE, length - (1 + CRYPTO_NONCE_SIZE), &source, + data + 1); + } + + return 0; + } + + case TCP_PACKET_ONION_RESPONSE: { + LOGGER_TRACE(tcp_server->logger, "handling onion response for %d", con_id); + return -1; + } + + case TCP_PACKET_FORWARD_REQUEST: { + if (tcp_server->forwarding == nullptr) { + return -1; + } + + const uint16_t sendback_data_len = 1 + sizeof(uint32_t) + sizeof(uint64_t); + uint8_t sendback_data[1 + sizeof(uint32_t) + sizeof(uint64_t)]; + sendback_data[0] = SENDBACK_TCP; + net_pack_u32(sendback_data + 1, con_id); + net_pack_u64(sendback_data + 1 + sizeof(uint32_t), con->identifier); + + IP_Port dest; + const int ipport_length = unpack_ip_port(&dest, data + 1, length - 1, false); + + if (ipport_length == -1) { + return -1; + } + + const uint8_t *const forward_data = data + (1 + ipport_length); + const uint16_t forward_data_len = length - (1 + ipport_length); + + if (forward_data_len > MAX_FORWARD_DATA_SIZE) { + return -1; + } + + send_forwarding(tcp_server->forwarding, &dest, sendback_data, sendback_data_len, forward_data, forward_data_len); + return 0; + } + + case TCP_PACKET_FORWARDING: { + return -1; + } + + default: { + if (data[0] < NUM_RESERVED_PORTS) { + return -1; + } + + const uint8_t c_id = data[0] - NUM_RESERVED_PORTS; + LOGGER_TRACE(tcp_server->logger, "handling packet id %d for %d", c_id, con_id); + + if (c_id >= NUM_CLIENT_CONNECTIONS) { + return -1; + } + + if (con->connections[c_id].status == 0) { + return -1; + } + + if (con->connections[c_id].status != 2) { + return 0; + } + + const uint32_t index = con->connections[c_id].index; + const uint8_t other_c_id = con->connections[c_id].other_id + NUM_RESERVED_PORTS; + VLA(uint8_t, new_data, length); + memcpy(new_data, data, length); + new_data[0] = other_c_id; + const int ret = write_packet_TCP_secure_connection(tcp_server->logger, + &tcp_server->accepted_connection_array[index].con, new_data, length, false); + + if (ret == -1) { + return -1; + } + + return 0; + } + } + + return 0; +} + + +non_null() +static int confirm_TCP_connection(TCP_Server *tcp_server, const Mono_Time *mono_time, TCP_Secure_Connection *con, + const uint8_t *data, uint16_t length) +{ + const int index = add_accepted(tcp_server, mono_time, con); + + if (index == -1) { + LOGGER_DEBUG(tcp_server->logger, "dropping connection %u: not accepted", (unsigned int)con->identifier); + kill_TCP_secure_connection(con); + return -1; + } + + wipe_secure_connection(con); + + if (handle_TCP_packet(tcp_server, index, data, length) == -1) { + LOGGER_DEBUG(tcp_server->logger, "dropping connection %u: data packet (len=%d) not handled", + (unsigned int)con->identifier, length); + kill_accepted(tcp_server, index); + return -1; + } + + return index; +} + +/** + * @return index on success + * @retval -1 on failure + */ +non_null() +static int accept_connection(TCP_Server *tcp_server, Socket sock) +{ + if (!sock_valid(sock)) { + return -1; + } + + if (!set_socket_nonblock(tcp_server->ns, sock)) { + kill_sock(tcp_server->ns, sock); + return -1; + } + + if (!set_socket_nosigpipe(tcp_server->ns, sock)) { + kill_sock(tcp_server->ns, sock); + return -1; + } + + const uint16_t index = tcp_server->incoming_connection_queue_index % MAX_INCOMING_CONNECTIONS; + + TCP_Secure_Connection *conn = &tcp_server->incoming_connection_queue[index]; + + if (conn->status != TCP_STATUS_NO_STATUS) { + LOGGER_DEBUG(tcp_server->logger, "connection %d dropped before accepting", index); + kill_TCP_secure_connection(conn); + } + + conn->status = TCP_STATUS_CONNECTED; + conn->con.ns = tcp_server->ns; + conn->con.rng = tcp_server->rng; + conn->con.sock = sock; + conn->next_packet_length = 0; + + ++tcp_server->incoming_connection_queue_index; + return index; +} + +non_null() +static Socket new_listening_TCP_socket(const Logger *logger, const Network *ns, Family family, uint16_t port) +{ + const Socket sock = net_socket(ns, family, TOX_SOCK_STREAM, TOX_PROTO_TCP); + + if (!sock_valid(sock)) { + LOGGER_ERROR(logger, "TCP socket creation failed (family = %d)", family.value); + return net_invalid_socket; + } + + bool ok = set_socket_nonblock(ns, sock); + + if (ok && net_family_is_ipv6(family)) { + ok = set_socket_dualstack(ns, sock); + } + + if (ok) { + ok = set_socket_reuseaddr(ns, sock); + } + + ok = ok && bind_to_port(ns, sock, family, port) && (net_listen(ns, sock, TCP_MAX_BACKLOG) == 0); + + if (!ok) { + char *const error = net_new_strerror(net_error()); + LOGGER_WARNING(logger, "could not bind to TCP port %d (family = %d): %s", + port, family.value, error != nullptr ? error : "(null)"); + net_kill_strerror(error); + kill_sock(ns, sock); + return net_invalid_socket; + } + + LOGGER_DEBUG(logger, "successfully bound to TCP port %d", port); + return sock; +} + +TCP_Server *new_TCP_server(const Logger *logger, const Random *rng, const Network *ns, + bool ipv6_enabled, uint16_t num_sockets, + const uint16_t *ports, const uint8_t *secret_key, Onion *onion, Forwarding *forwarding) +{ + if (num_sockets == 0 || ports == nullptr) { + LOGGER_ERROR(logger, "no sockets"); + return nullptr; + } + + if (ns == nullptr) { + LOGGER_ERROR(logger, "NULL network"); + return nullptr; + } + + TCP_Server *temp = (TCP_Server *)calloc(1, sizeof(TCP_Server)); + + if (temp == nullptr) { + LOGGER_ERROR(logger, "TCP server allocation failed"); + return nullptr; + } + + temp->logger = logger; + temp->ns = ns; + temp->rng = rng; + + temp->socks_listening = (Socket *)calloc(num_sockets, sizeof(Socket)); + + if (temp->socks_listening == nullptr) { + LOGGER_ERROR(logger, "socket allocation failed"); + free(temp); + return nullptr; + } + +#ifdef TCP_SERVER_USE_EPOLL + temp->efd = epoll_create(8); + + if (temp->efd == -1) { + LOGGER_ERROR(logger, "epoll initialisation failed"); + free(temp->socks_listening); + free(temp); + return nullptr; + } + +#endif + + const Family family = ipv6_enabled ? net_family_ipv6() : net_family_ipv4(); + + for (uint32_t i = 0; i < num_sockets; ++i) { + const Socket sock = new_listening_TCP_socket(logger, ns, family, ports[i]); + + if (!sock_valid(sock)) { + continue; + } + +#ifdef TCP_SERVER_USE_EPOLL + struct epoll_event ev; + + ev.events = EPOLLIN | EPOLLET; + ev.data.u64 = sock.sock | ((uint64_t)TCP_SOCKET_LISTENING << 32); + + if (epoll_ctl(temp->efd, EPOLL_CTL_ADD, sock.sock, &ev) == -1) { + continue; + } + +#endif + + temp->socks_listening[temp->num_listening_socks] = sock; + ++temp->num_listening_socks; + } + + if (temp->num_listening_socks == 0) { + free(temp->socks_listening); + free(temp); + return nullptr; + } + + if (onion != nullptr) { + temp->onion = onion; + set_callback_handle_recv_1(onion, &handle_onion_recv_1, temp); + } + + if (forwarding != nullptr) { + temp->forwarding = forwarding; + set_callback_forward_reply(forwarding, &handle_forward_reply_tcp, temp); + } + + memcpy(temp->secret_key, secret_key, CRYPTO_SECRET_KEY_SIZE); + crypto_derive_public_key(temp->public_key, temp->secret_key); + + bs_list_init(&temp->accepted_key_list, CRYPTO_PUBLIC_KEY_SIZE, 8); + + return temp; +} + +#ifndef TCP_SERVER_USE_EPOLL +non_null() +static void do_TCP_accept_new(TCP_Server *tcp_server) +{ + for (uint32_t i = 0; i < tcp_server->num_listening_socks; ++i) { + Socket sock; + + do { + sock = net_accept(tcp_server->ns, tcp_server->socks_listening[i]); + } while (accept_connection(tcp_server, sock) != -1); + } +} +#endif + +non_null() +static int do_incoming(TCP_Server *tcp_server, uint32_t i) +{ + TCP_Secure_Connection *const conn = &tcp_server->incoming_connection_queue[i]; + + if (conn->status != TCP_STATUS_CONNECTED) { + return -1; + } + + LOGGER_TRACE(tcp_server->logger, "handling incoming TCP connection %d", i); + + const int ret = read_connection_handshake(tcp_server->logger, conn, tcp_server->secret_key); + + if (ret == -1) { + LOGGER_TRACE(tcp_server->logger, "incoming connection %d dropped due to failed handshake", i); + kill_TCP_secure_connection(conn); + return -1; + } + + if (ret != 1) { + return -1; + } + + const int index_new = tcp_server->unconfirmed_connection_queue_index % MAX_INCOMING_CONNECTIONS; + TCP_Secure_Connection *conn_old = conn; + TCP_Secure_Connection *conn_new = &tcp_server->unconfirmed_connection_queue[index_new]; + + if (conn_new->status != TCP_STATUS_NO_STATUS) { + LOGGER_ERROR(tcp_server->logger, "incoming connection %d would overwrite existing", i); + kill_TCP_secure_connection(conn_new); + } + + move_secure_connection(conn_new, conn_old); + ++tcp_server->unconfirmed_connection_queue_index; + + return index_new; +} + +non_null() +static int do_unconfirmed(TCP_Server *tcp_server, const Mono_Time *mono_time, uint32_t i) +{ + TCP_Secure_Connection *const conn = &tcp_server->unconfirmed_connection_queue[i]; + + if (conn->status != TCP_STATUS_UNCONFIRMED) { + return -1; + } + + LOGGER_TRACE(tcp_server->logger, "handling unconfirmed TCP connection %d", i); + + uint8_t packet[MAX_PACKET_SIZE]; + const int len = read_packet_TCP_secure_connection(tcp_server->logger, conn->con.ns, conn->con.sock, &conn->next_packet_length, conn->con.shared_key, conn->recv_nonce, packet, sizeof(packet), &conn->con.ip_port); + + if (len == 0) { + return -1; + } + + if (len == -1) { + kill_TCP_secure_connection(conn); + return -1; + } + + return confirm_TCP_connection(tcp_server, mono_time, conn, packet, len); +} + +non_null() +static bool tcp_process_secure_packet(TCP_Server *tcp_server, uint32_t i) +{ + TCP_Secure_Connection *const conn = &tcp_server->accepted_connection_array[i]; + + uint8_t packet[MAX_PACKET_SIZE]; + const int len = read_packet_TCP_secure_connection(tcp_server->logger, conn->con.ns, conn->con.sock, &conn->next_packet_length, conn->con.shared_key, conn->recv_nonce, packet, sizeof(packet), &conn->con.ip_port); + LOGGER_TRACE(tcp_server->logger, "processing packet for %d: %d", i, len); + + if (len == 0) { + return false; + } + + if (len == -1) { + kill_accepted(tcp_server, i); + return false; + } + + if (handle_TCP_packet(tcp_server, i, packet, len) == -1) { + LOGGER_TRACE(tcp_server->logger, "dropping connection %d: data packet (len=%d) not handled", i, len); + kill_accepted(tcp_server, i); + return false; + } + + return true; +} + +non_null() +static void do_confirmed_recv(TCP_Server *tcp_server, uint32_t i) +{ + while (tcp_process_secure_packet(tcp_server, i)) { + // Keep reading until an error occurs or there is no more data to read. + continue; + } +} + +#ifndef TCP_SERVER_USE_EPOLL +non_null() +static void do_TCP_incoming(TCP_Server *tcp_server) +{ + for (uint32_t i = 0; i < MAX_INCOMING_CONNECTIONS; ++i) { + do_incoming(tcp_server, i); + } +} + +non_null() +static void do_TCP_unconfirmed(TCP_Server *tcp_server, const Mono_Time *mono_time) +{ + for (uint32_t i = 0; i < MAX_INCOMING_CONNECTIONS; ++i) { + do_unconfirmed(tcp_server, mono_time, i); + } +} +#endif + +non_null() +static void do_TCP_confirmed(TCP_Server *tcp_server, const Mono_Time *mono_time) +{ +#ifdef TCP_SERVER_USE_EPOLL + + if (tcp_server->last_run_pinged == mono_time_get(mono_time)) { + return; + } + + tcp_server->last_run_pinged = mono_time_get(mono_time); +#endif + + for (uint32_t i = 0; i < tcp_server->size_accepted_connections; ++i) { + TCP_Secure_Connection *conn = &tcp_server->accepted_connection_array[i]; + + if (conn->status != TCP_STATUS_CONFIRMED) { + continue; + } + + if (mono_time_is_timeout(mono_time, conn->last_pinged, TCP_PING_FREQUENCY)) { + uint8_t ping[1 + sizeof(uint64_t)]; + ping[0] = TCP_PACKET_PING; + uint64_t ping_id = random_u64(conn->con.rng); + + if (ping_id == 0) { + ++ping_id; + } + + memcpy(ping + 1, &ping_id, sizeof(uint64_t)); + const int ret = write_packet_TCP_secure_connection(tcp_server->logger, &conn->con, ping, sizeof(ping), true); + + if (ret == 1) { + conn->last_pinged = mono_time_get(mono_time); + conn->ping_id = ping_id; + } else { + if (mono_time_is_timeout(mono_time, conn->last_pinged, TCP_PING_FREQUENCY + TCP_PING_TIMEOUT)) { + kill_accepted(tcp_server, i); + continue; + } + } + } + + if (conn->ping_id != 0 && mono_time_is_timeout(mono_time, conn->last_pinged, TCP_PING_TIMEOUT)) { + kill_accepted(tcp_server, i); + continue; + } + + send_pending_data(tcp_server->logger, &conn->con); + +#ifndef TCP_SERVER_USE_EPOLL + + do_confirmed_recv(tcp_server, i); + +#endif + } +} + +#ifdef TCP_SERVER_USE_EPOLL +non_null() +static bool tcp_epoll_process(TCP_Server *tcp_server, const Mono_Time *mono_time) +{ +#define MAX_EVENTS 16 + struct epoll_event events[MAX_EVENTS]; + const int nfds = epoll_wait(tcp_server->efd, events, MAX_EVENTS, 0); +#undef MAX_EVENTS + + for (int n = 0; n < nfds; ++n) { + const Socket sock = {(int)(events[n].data.u64 & 0xFFFFFFFF)}; + const int status = (events[n].data.u64 >> 32) & 0xFF; + const int index = events[n].data.u64 >> 40; + + if ((events[n].events & EPOLLERR) != 0 || (events[n].events & EPOLLHUP) != 0 || (events[n].events & EPOLLRDHUP) != 0) { + switch (status) { + case TCP_SOCKET_LISTENING: { + // should never happen + LOGGER_ERROR(tcp_server->logger, "connection %d was in listening state", index); + break; + } + + case TCP_SOCKET_INCOMING: { + LOGGER_TRACE(tcp_server->logger, "incoming connection %d dropped", index); + kill_TCP_secure_connection(&tcp_server->incoming_connection_queue[index]); + break; + } + + case TCP_SOCKET_UNCONFIRMED: { + LOGGER_TRACE(tcp_server->logger, "unconfirmed connection %d dropped", index); + kill_TCP_secure_connection(&tcp_server->unconfirmed_connection_queue[index]); + break; + } + + case TCP_SOCKET_CONFIRMED: { + LOGGER_TRACE(tcp_server->logger, "confirmed connection %d dropped", index); + kill_accepted(tcp_server, index); + break; + } + } + + continue; + } + + + if ((events[n].events & EPOLLIN) == 0) { + continue; + } + + switch (status) { + case TCP_SOCKET_LISTENING: { + // socket is from socks_listening, accept connection + while (true) { + const Socket sock_new = net_accept(tcp_server->ns, sock); + + if (!sock_valid(sock_new)) { + break; + } + + const int index_new = accept_connection(tcp_server, sock_new); + + if (index_new == -1) { + continue; + } + + struct epoll_event ev; + + ev.events = EPOLLIN | EPOLLET | EPOLLRDHUP; + + ev.data.u64 = sock_new.sock | ((uint64_t)TCP_SOCKET_INCOMING << 32) | ((uint64_t)index_new << 40); + + if (epoll_ctl(tcp_server->efd, EPOLL_CTL_ADD, sock_new.sock, &ev) == -1) { + LOGGER_DEBUG(tcp_server->logger, "new connection %d was dropped due to epoll error %d", index, net_error()); + kill_TCP_secure_connection(&tcp_server->incoming_connection_queue[index_new]); + continue; + } + } + + break; + } + + case TCP_SOCKET_INCOMING: { + const int index_new = do_incoming(tcp_server, index); + + if (index_new != -1) { + LOGGER_TRACE(tcp_server->logger, "incoming connection %d was accepted as %d", index, index_new); + events[n].events = EPOLLIN | EPOLLET | EPOLLRDHUP; + events[n].data.u64 = sock.sock | ((uint64_t)TCP_SOCKET_UNCONFIRMED << 32) | ((uint64_t)index_new << 40); + + if (epoll_ctl(tcp_server->efd, EPOLL_CTL_MOD, sock.sock, &events[n]) == -1) { + LOGGER_DEBUG(tcp_server->logger, "incoming connection %d was dropped due to epoll error %d", index, net_error()); + kill_TCP_secure_connection(&tcp_server->unconfirmed_connection_queue[index_new]); + break; + } + } + + break; + } + + case TCP_SOCKET_UNCONFIRMED: { + const int index_new = do_unconfirmed(tcp_server, mono_time, index); + + if (index_new != -1) { + LOGGER_TRACE(tcp_server->logger, "unconfirmed connection %d was confirmed as %d", index, index_new); + events[n].events = EPOLLIN | EPOLLET | EPOLLRDHUP; + events[n].data.u64 = sock.sock | ((uint64_t)TCP_SOCKET_CONFIRMED << 32) | ((uint64_t)index_new << 40); + + if (epoll_ctl(tcp_server->efd, EPOLL_CTL_MOD, sock.sock, &events[n]) == -1) { + // remove from confirmed connections + LOGGER_DEBUG(tcp_server->logger, "unconfirmed connection %d was dropped due to epoll error %d", index, net_error()); + kill_accepted(tcp_server, index_new); + break; + } + } + + break; + } + + case TCP_SOCKET_CONFIRMED: { + do_confirmed_recv(tcp_server, index); + break; + } + } + } + + return nfds > 0; +} + +non_null() +static void do_TCP_epoll(TCP_Server *tcp_server, const Mono_Time *mono_time) +{ + while (tcp_epoll_process(tcp_server, mono_time)) { + // Keep processing packets until there are no more FDs ready for reading. + continue; + } +} +#endif + +void do_TCP_server(TCP_Server *tcp_server, const Mono_Time *mono_time) +{ +#ifdef TCP_SERVER_USE_EPOLL + do_TCP_epoll(tcp_server, mono_time); + +#else + do_TCP_accept_new(tcp_server); + do_TCP_incoming(tcp_server); + do_TCP_unconfirmed(tcp_server, mono_time); +#endif + + do_TCP_confirmed(tcp_server, mono_time); +} + +void kill_TCP_server(TCP_Server *tcp_server) +{ + if (tcp_server == nullptr) { + return; + } + + for (uint32_t i = 0; i < tcp_server->num_listening_socks; ++i) { + kill_sock(tcp_server->ns, tcp_server->socks_listening[i]); + } + + if (tcp_server->onion != nullptr) { + set_callback_handle_recv_1(tcp_server->onion, nullptr, nullptr); + } + + if (tcp_server->forwarding != nullptr) { + set_callback_forward_reply(tcp_server->forwarding, nullptr, nullptr); + } + + bs_list_free(&tcp_server->accepted_key_list); + +#ifdef TCP_SERVER_USE_EPOLL + close(tcp_server->efd); +#endif + + for (uint32_t i = 0; i < MAX_INCOMING_CONNECTIONS; ++i) { + wipe_secure_connection(&tcp_server->incoming_connection_queue[i]); + wipe_secure_connection(&tcp_server->unconfirmed_connection_queue[i]); + } + + free_accepted_connection_array(tcp_server); + + crypto_memzero(tcp_server->secret_key, sizeof(tcp_server->secret_key)); + + free(tcp_server->socks_listening); + free(tcp_server); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/announce.h b/local_pod_repo/toxcore/toxcore/toxcore/announce.h new file mode 100644 index 0000000..e680da6 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/announce.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2020-2021 The TokTok team. + */ + +#ifndef C_TOXCORE_TOXCORE_ANNOUNCE_H +#define C_TOXCORE_TOXCORE_ANNOUNCE_H + +#include "forwarding.h" + +#define MAX_ANNOUNCEMENT_SIZE 512 + +typedef void announce_on_retrieve_cb(void *object, const uint8_t *data, uint16_t length); + +uint8_t announce_response_of_request_type(uint8_t request_type); + +typedef struct Announcements Announcements; + +non_null() +Announcements *new_announcements(const Logger *log, const Random *rng, const Mono_Time *mono_time, Forwarding *forwarding); + +/** + * @brief If data is stored, run `on_retrieve_callback` on it. + * + * @return true if data is stored, false otherwise. + */ +non_null(1, 2) nullable(3, 4) +bool announce_on_stored(const Announcements *announce, const uint8_t *data_public_key, + announce_on_retrieve_cb *on_retrieve_callback, void *object); + +non_null() +void announce_set_synch_offset(Announcements *announce, int32_t synch_offset); + +nullable(1) +void kill_announcements(Announcements *announce); + + +/* The declarations below are not public, they are exposed only for tests. */ + +/** @private + * Return xor of first ANNOUNCE_BUCKET_PREFIX_LENGTH bits from one bit after + * base and pk first differ + */ +non_null() +uint16_t announce_get_bucketnum(const uint8_t *base, const uint8_t *pk); + +/** @private */ +non_null(1, 2) nullable(3) +bool announce_store_data(Announcements *announce, const uint8_t *data_public_key, + const uint8_t *data, uint32_t length, uint32_t timeout); + +/** @private */ +#define MAX_MAX_ANNOUNCEMENT_TIMEOUT 900 +#define MIN_MAX_ANNOUNCEMENT_TIMEOUT 10 +#define MAX_ANNOUNCEMENT_TIMEOUT_UPTIME_RATIO 4 + +/** @private + * For efficient lookup and updating, entries are stored as a hash table keyed + * to the first ANNOUNCE_BUCKET_PREFIX_LENGTH bits starting from one bit after + * the first bit in which data public key first differs from the dht key, with + * (2-adically) closest keys preferentially stored within a given bucket. A + * given key appears at most once (even if timed out). + */ +#define ANNOUNCE_BUCKET_SIZE 8 +#define ANNOUNCE_BUCKET_PREFIX_LENGTH 5 +#define ANNOUNCE_BUCKETS 32 // ANNOUNCE_BUCKETS = 2 ** ANNOUNCE_BUCKET_PREFIX_LENGTH + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/announce.m b/local_pod_repo/toxcore/toxcore/toxcore/announce.m new file mode 100644 index 0000000..7914d19 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/announce.m @@ -0,0 +1,689 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2020-2021 The TokTok team. + */ + +/** + * "Server side" of the DHT announcements protocol. + */ + +#include "announce.h" + +#include +#include +#include + +#include "LAN_discovery.h" +#include "ccompat.h" +#include "timed_auth.h" +#include "util.h" + +uint8_t announce_response_of_request_type(uint8_t request_type) +{ + switch (request_type) { + case NET_PACKET_DATA_SEARCH_REQUEST: + return NET_PACKET_DATA_SEARCH_RESPONSE; + + case NET_PACKET_DATA_RETRIEVE_REQUEST: + return NET_PACKET_DATA_RETRIEVE_RESPONSE; + + case NET_PACKET_STORE_ANNOUNCE_REQUEST: + return NET_PACKET_STORE_ANNOUNCE_RESPONSE; + + default: { + assert(false); + return NET_PACKET_MAX; + } + } +} + +typedef struct Announce_Entry { + uint64_t store_until; + uint8_t data_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t *data; + uint32_t length; +} Announce_Entry; + +struct Announcements { + const Logger *log; + const Random *rng; + Forwarding *forwarding; + const Mono_Time *mono_time; + DHT *dht; + Networking_Core *net; + const uint8_t *public_key; + const uint8_t *secret_key; + + Shared_Keys shared_keys; + uint8_t hmac_key[CRYPTO_HMAC_KEY_SIZE]; + + int32_t synch_offset; + + uint64_t start_time; + + Announce_Entry entries[ANNOUNCE_BUCKETS * ANNOUNCE_BUCKET_SIZE]; +}; + +void announce_set_synch_offset(Announcements *announce, int32_t synch_offset) +{ + announce->synch_offset = synch_offset; +} + +/** + * An entry is considered to be "deleted" for the purposes of the protocol + * once it has timed out. + */ +non_null() +static bool entry_is_empty(const Announcements *announce, const Announce_Entry *entry) +{ + return mono_time_get(announce->mono_time) >= entry->store_until; +} + +non_null() +static void delete_entry(Announce_Entry *entry) +{ + entry->store_until = 0; +} + +/** Return bits (at most 8) from pk starting at index as uint8_t */ +non_null() +static uint8_t truncate_pk_at_index(const uint8_t *pk, uint16_t index, uint16_t bits) +{ + assert(bits < 8); + const uint8_t i = index / 8; + const uint8_t j = index % 8; + return ((uint8_t)((i < CRYPTO_PUBLIC_KEY_SIZE ? pk[i] : 0) << j) >> (8 - bits)) | + ((i + 1 < CRYPTO_PUBLIC_KEY_SIZE ? pk[i + 1] : 0) >> (16 - bits - j)); +} + +uint16_t announce_get_bucketnum(const uint8_t *base, const uint8_t *pk) +{ + const uint16_t index = bit_by_bit_cmp(base, pk); + + return truncate_pk_at_index(base, index + 1, ANNOUNCE_BUCKET_PREFIX_LENGTH) ^ + truncate_pk_at_index(pk, index + 1, ANNOUNCE_BUCKET_PREFIX_LENGTH); +} + +non_null() +static Announce_Entry *bucket_of_key(Announcements *announce, const uint8_t *pk) +{ + return &announce->entries[announce_get_bucketnum(announce->public_key, pk) * ANNOUNCE_BUCKET_SIZE]; +} + +non_null() +static Announce_Entry *get_stored(Announcements *announce, const uint8_t *data_public_key) +{ + Announce_Entry *const bucket = bucket_of_key(announce, data_public_key); + + for (uint32_t i = 0; i < ANNOUNCE_BUCKET_SIZE; ++i) { + if (pk_equal(bucket[i].data_public_key, data_public_key)) { + if (entry_is_empty(announce, &bucket[i])) { + break; + } + + return &bucket[i]; + } + } + + return nullptr; +} + +non_null() +static const Announce_Entry *bucket_of_key_const(const Announcements *announce, const uint8_t *pk) +{ + return &announce->entries[announce_get_bucketnum(announce->public_key, pk) * ANNOUNCE_BUCKET_SIZE]; +} + +non_null() +static const Announce_Entry *get_stored_const(const Announcements *announce, const uint8_t *data_public_key) +{ + const Announce_Entry *const bucket = bucket_of_key_const(announce, data_public_key); + + for (uint32_t i = 0; i < ANNOUNCE_BUCKET_SIZE; ++i) { + if (pk_equal(bucket[i].data_public_key, data_public_key)) { + if (entry_is_empty(announce, &bucket[i])) { + break; + } + + return &bucket[i]; + } + } + + return nullptr; +} + + +bool announce_on_stored(const Announcements *announce, const uint8_t *data_public_key, + announce_on_retrieve_cb *on_retrieve_callback, void *object) +{ + const Announce_Entry *const entry = get_stored_const(announce, data_public_key); + + if (entry == nullptr || entry->data == nullptr) { + return false; + } + + if (on_retrieve_callback != nullptr) { + on_retrieve_callback(object, entry->data, entry->length); + } + + return true; +} + +/** + * Return existing entry for this key if it exists, else an empty + * slot in the key's bucket if one exists, else an entry in the key's bucket + * of greatest 2-adic distance greater than that of the key bucket if one + * exists, else nullptr. + */ +non_null() +static Announce_Entry *find_entry_slot(Announcements *announce, const uint8_t *data_public_key) +{ + Announce_Entry *const bucket = bucket_of_key(announce, data_public_key); + + Announce_Entry *slot = nullptr; + uint16_t min_index = bit_by_bit_cmp(announce->public_key, data_public_key); + + for (uint32_t i = 0; i < ANNOUNCE_BUCKET_SIZE; ++i) { + if (pk_equal(bucket[i].data_public_key, data_public_key)) { + return &bucket[i]; + } + + if (entry_is_empty(announce, &bucket[i])) { + slot = &bucket[i]; + min_index = 0; + continue; + } + + const uint16_t index = bit_by_bit_cmp(announce->public_key, bucket[i].data_public_key); + + if (index < min_index) { + slot = &bucket[i]; + min_index = index; + } + } + + return slot; +} + +non_null() +static bool would_accept_store_request(Announcements *announce, const uint8_t *data_public_key) +{ + return find_entry_slot(announce, data_public_key) != nullptr; +} + +bool announce_store_data(Announcements *announce, const uint8_t *data_public_key, + const uint8_t *data, uint32_t length, uint32_t timeout) +{ + if (length > MAX_ANNOUNCEMENT_SIZE) { + return false; + } + + Announce_Entry *entry = find_entry_slot(announce, data_public_key); + + if (entry == nullptr) { + return false; + } + + if (length > 0) { + assert(data != nullptr); + + if (entry->data != nullptr) { + free(entry->data); + } + + entry->data = (uint8_t *)malloc(length); + + if (entry->data == nullptr) { + return false; + } + + memcpy(entry->data, data, length); + } + + entry->length = length; + memcpy(entry->data_public_key, data_public_key, CRYPTO_PUBLIC_KEY_SIZE); + entry->store_until = mono_time_get(announce->mono_time) + timeout; + + return true; +} + +non_null() +static uint32_t calculate_timeout(const Announcements *announce, uint32_t requested_timeout) +{ + const uint64_t uptime = mono_time_get(announce->mono_time) - announce->start_time; + const uint32_t max_announcement_timeout = max_u32( + (uint32_t)min_u64( + MAX_MAX_ANNOUNCEMENT_TIMEOUT, + uptime / MAX_ANNOUNCEMENT_TIMEOUT_UPTIME_RATIO), + MIN_MAX_ANNOUNCEMENT_TIMEOUT); + + return min_u32(max_announcement_timeout, requested_timeout); +} + +#define DATA_SEARCH_TO_AUTH_MAX_SIZE (CRYPTO_PUBLIC_KEY_SIZE * 2 + MAX_PACKED_IPPORT_SIZE + MAX_SENDBACK_SIZE) + +non_null(1, 2, 3, 4, 7) nullable(5) +static int create_data_search_to_auth(const Logger *logger, const uint8_t *data_public_key, + const uint8_t *requester_key, + const IP_Port *source, const uint8_t *sendback, uint16_t sendback_length, + uint8_t *dest, uint16_t max_length) +{ + if (max_length < DATA_SEARCH_TO_AUTH_MAX_SIZE + || sendback_length > MAX_SENDBACK_SIZE) { + return -1; + } + + memcpy(dest, data_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(dest + CRYPTO_PUBLIC_KEY_SIZE, requester_key, CRYPTO_PUBLIC_KEY_SIZE); + + const int ipport_length = pack_ip_port(logger, dest + CRYPTO_PUBLIC_KEY_SIZE * 2, MAX_PACKED_IPPORT_SIZE, source); + + if (ipport_length == -1) { + return -1; + } + + if (sendback_length > 0) { + assert(sendback != nullptr); + memcpy(dest + CRYPTO_PUBLIC_KEY_SIZE * 2 + ipport_length, sendback, sendback_length); + } + + return CRYPTO_PUBLIC_KEY_SIZE * 2 + ipport_length + sendback_length; +} + +#define DATA_SEARCH_TIMEOUT 60 + +non_null() +static int create_reply_plain_data_search_request(Announcements *announce, + const IP_Port *source, + const uint8_t *data, uint16_t length, + uint8_t *reply, uint16_t reply_max_length, + uint8_t *to_auth, uint16_t to_auth_length) +{ + if (length != CRYPTO_PUBLIC_KEY_SIZE && + length != CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA256_SIZE) { + return -1; + } + + const uint8_t *const data_public_key = data; + + const uint8_t *previous_hash = nullptr; + + if (length == CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA256_SIZE) { + previous_hash = data + CRYPTO_PUBLIC_KEY_SIZE; + } + + const int nodes_max_length = (int)reply_max_length - + (CRYPTO_PUBLIC_KEY_SIZE + 1 + CRYPTO_SHA256_SIZE + TIMED_AUTH_SIZE + 1 + 1); + + if (nodes_max_length < 0) { + return -1; + } + + uint8_t *p = reply; + + memcpy(p, data_public_key, CRYPTO_PUBLIC_KEY_SIZE); + p += CRYPTO_PUBLIC_KEY_SIZE; + + const Announce_Entry *const stored = get_stored_const(announce, data_public_key); + + if (stored == nullptr) { + *p = 0; + ++p; + } else { + *p = 1; + ++p; + crypto_sha256(p, stored->data, stored->length); + p += CRYPTO_SHA256_SIZE; + } + + generate_timed_auth(announce->mono_time, DATA_SEARCH_TIMEOUT, announce->hmac_key, + to_auth, to_auth_length, p); + p += TIMED_AUTH_SIZE; + + *p = would_accept_store_request(announce, data_public_key); + ++p; + + Node_format nodes_list[MAX_SENT_NODES]; + const int num_nodes = get_close_nodes(announce->dht, data_public_key, nodes_list, + net_family_unspec(), ip_is_lan(&source->ip), true); + + if (num_nodes < 0 || num_nodes > MAX_SENT_NODES) { + return -1; + } + + *p = num_nodes; + ++p; + + p += pack_nodes(announce->log, p, nodes_max_length, nodes_list, num_nodes); + + const uint32_t reply_len = p - reply; + + if (previous_hash != nullptr) { + uint8_t hash[CRYPTO_SHA256_SIZE]; + + crypto_sha256(hash, reply, reply_len); + + if (crypto_sha256_eq(hash, previous_hash)) { + return CRYPTO_PUBLIC_KEY_SIZE; + } + } + + return reply_len; +} + +non_null() +static int create_reply_plain_data_retrieve_request(Announcements *announce, + const IP_Port *source, + const uint8_t *data, uint16_t length, + uint8_t *reply, uint16_t reply_max_length, + uint8_t *to_auth, uint16_t to_auth_length) +{ + if (length != CRYPTO_PUBLIC_KEY_SIZE + 1 + TIMED_AUTH_SIZE) { + return -1; + } + + if (data[CRYPTO_PUBLIC_KEY_SIZE] != 0) { + return -1; + } + + const uint8_t *const data_public_key = data; + const uint8_t *const auth = data + CRYPTO_PUBLIC_KEY_SIZE + 1; + + if (!check_timed_auth(announce->mono_time, DATA_SEARCH_TIMEOUT, announce->hmac_key, + to_auth, to_auth_length, auth)) { + return -1; + } + + const Announce_Entry *const entry = get_stored_const(announce, data_public_key); + + if (entry == nullptr) { + return -1; + } + + const uint16_t reply_len = CRYPTO_PUBLIC_KEY_SIZE + 1 + entry->length; + + if (reply_max_length < reply_len) { + return -1; + } + + memcpy(reply, data_public_key, CRYPTO_PUBLIC_KEY_SIZE); + reply[CRYPTO_PUBLIC_KEY_SIZE] = 1; + memcpy(reply + CRYPTO_PUBLIC_KEY_SIZE + 1, entry->data, entry->length); + + return reply_len; +} + +non_null() +static int create_reply_plain_store_announce_request(Announcements *announce, + const IP_Port *source, + const uint8_t *data, uint16_t length, + uint8_t *reply, uint16_t reply_max_length, + uint8_t *to_auth, uint16_t to_auth_length) +{ + const int plain_len = (int)length - (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_MAC_SIZE); + const int announcement_len = (int)plain_len - (TIMED_AUTH_SIZE + sizeof(uint32_t) + 1); + + const uint8_t *const data_public_key = data; + + if (announcement_len < 0) { + return -1; + } + + VLA(uint8_t, plain, plain_len); + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + + get_shared_key(announce->mono_time, &announce->shared_keys, shared_key, + announce->secret_key, data_public_key); + + if (decrypt_data_symmetric(shared_key, + data + CRYPTO_PUBLIC_KEY_SIZE, + data + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + plain_len + CRYPTO_MAC_SIZE, + plain) != plain_len) { + return -1; + } + + const uint8_t *const auth = plain; + uint32_t requested_timeout; + net_unpack_u32(plain + TIMED_AUTH_SIZE, &requested_timeout); + const uint32_t timeout = calculate_timeout(announce, requested_timeout); + const uint8_t announcement_type = plain[TIMED_AUTH_SIZE + sizeof(uint32_t)]; + const uint8_t *announcement = plain + TIMED_AUTH_SIZE + sizeof(uint32_t) + 1; + + if (!check_timed_auth(announce->mono_time, DATA_SEARCH_TIMEOUT, announce->hmac_key, + to_auth, to_auth_length, auth)) { + return -1; + } + + if (announcement_type > 1) { + return -1; + } + + if (announcement_type == 1) { + if (announcement_len != CRYPTO_SHA256_SIZE) { + return -1; + } + + Announce_Entry *stored = get_stored(announce, data_public_key); + + if (stored == nullptr) { + return -1; + } + + uint8_t stored_hash[CRYPTO_SHA256_SIZE]; + crypto_sha256(stored_hash, stored->data, stored->length); + + if (!crypto_sha256_eq(announcement, stored_hash)) { + delete_entry(stored); + return -1; + } else { + stored->store_until = mono_time_get(announce->mono_time) + timeout; + } + } else { + if (!announce_store_data(announce, data_public_key, announcement, announcement_len, timeout)) { + return -1; + } + } + + const uint16_t reply_len = CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t) + sizeof(uint64_t); + + if (reply_max_length < reply_len) { + return -1; + } + + memcpy(reply, data_public_key, CRYPTO_PUBLIC_KEY_SIZE); + net_pack_u32(reply + CRYPTO_PUBLIC_KEY_SIZE, timeout); + net_pack_u64(reply + CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t), + mono_time_get(announce->mono_time) + announce->synch_offset); + return reply_len; +} + +non_null(1, 2, 3, 7, 9) nullable(5) +static int create_reply_plain(Announcements *announce, + const uint8_t *requester_key, const IP_Port *source, uint8_t type, + const uint8_t *sendback, uint16_t sendback_length, + const uint8_t *data, uint16_t length, + uint8_t *reply, uint16_t reply_max_length) +{ + if (length < CRYPTO_PUBLIC_KEY_SIZE) { + return -1; + } + + const uint8_t *const data_public_key = data; + + uint8_t to_auth[DATA_SEARCH_TO_AUTH_MAX_SIZE]; + const int to_auth_length = create_data_search_to_auth(announce->log, data_public_key, requester_key, source, + sendback, sendback_length, to_auth, DATA_SEARCH_TO_AUTH_MAX_SIZE); + + if (to_auth_length == -1) { + return -1; + } + + switch (type) { + case NET_PACKET_DATA_SEARCH_REQUEST: + return create_reply_plain_data_search_request(announce, source, data, length, reply, reply_max_length, to_auth, + (uint16_t)to_auth_length); + + case NET_PACKET_DATA_RETRIEVE_REQUEST: + return create_reply_plain_data_retrieve_request(announce, source, data, length, reply, reply_max_length, to_auth, + (uint16_t)to_auth_length); + + case NET_PACKET_STORE_ANNOUNCE_REQUEST: + return create_reply_plain_store_announce_request(announce, source, data, length, reply, reply_max_length, to_auth, + (uint16_t)to_auth_length); + + default: + return -1; + } +} + +non_null(1, 2, 5, 7) nullable(3) +static int create_reply(Announcements *announce, const IP_Port *source, + const uint8_t *sendback, uint16_t sendback_length, + const uint8_t *data, uint16_t length, + uint8_t *reply, uint16_t reply_max_length) +{ + const int plain_len = (int)length - (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_MAC_SIZE); + + if (plain_len < (int)sizeof(uint64_t)) { + return -1; + } + + VLA(uint8_t, plain, plain_len); + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + + dht_get_shared_key_recv(announce->dht, shared_key, data + 1); + + if (decrypt_data_symmetric(shared_key, + data + 1 + CRYPTO_PUBLIC_KEY_SIZE, + data + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + plain_len + CRYPTO_MAC_SIZE, + plain) != plain_len) { + return -1; + } + + const int plain_reply_max_len = (int)reply_max_length - + (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_MAC_SIZE); + + if (plain_reply_max_len < sizeof(uint64_t)) { + return -1; + } + + VLA(uint8_t, plain_reply, plain_reply_max_len); + + const int plain_reply_noping_len = create_reply_plain(announce, + data + 1, source, data[0], + sendback, sendback_length, + plain, plain_len - sizeof(uint64_t), + plain_reply, plain_reply_max_len - sizeof(uint64_t)); + + if (plain_reply_noping_len == -1) { + return -1; + } + + memcpy(plain_reply + plain_reply_noping_len, + plain + (plain_len - sizeof(uint64_t)), sizeof(uint64_t)); + + const uint16_t plain_reply_len = plain_reply_noping_len + sizeof(uint64_t); + + const uint8_t response_type = announce_response_of_request_type(data[0]); + + return dht_create_packet(announce->rng, announce->public_key, shared_key, response_type, + plain_reply, plain_reply_len, reply, reply_max_length); +} + +non_null(1, 2, 3, 5) nullable(7) +static void forwarded_request_callback(void *object, const IP_Port *forwarder, + const uint8_t *sendback, uint16_t sendback_length, + const uint8_t *data, uint16_t length, void *userdata) +{ + Announcements *announce = (Announcements *) object; + + uint8_t reply[MAX_FORWARD_DATA_SIZE]; + + const int len = create_reply(announce, forwarder, + sendback, sendback_length, + data, length, reply, sizeof(reply)); + + if (len == -1) { + return; + } + + forward_reply(announce->net, forwarder, sendback, sendback_length, reply, len); +} + +non_null(1, 2, 3) nullable(5) +static int handle_dht_announce_request(void *object, const IP_Port *source, + const uint8_t *data, uint16_t length, void *userdata) +{ + Announcements *announce = (Announcements *) object; + + uint8_t reply[MAX_FORWARD_DATA_SIZE]; + + const int len = create_reply(announce, source, + nullptr, 0, + data, length, reply, sizeof(reply)); + + if (len == -1) { + return -1; + } + + return sendpacket(announce->net, source, reply, len) == len ? 0 : -1; +} + +Announcements *new_announcements(const Logger *log, const Random *rng, const Mono_Time *mono_time, + Forwarding *forwarding) +{ + if (log == nullptr || mono_time == nullptr || forwarding == nullptr) { + return nullptr; + } + + Announcements *announce = (Announcements *)calloc(1, sizeof(Announcements)); + + if (announce == nullptr) { + return nullptr; + } + + announce->log = log; + announce->rng = rng; + announce->forwarding = forwarding; + announce->mono_time = mono_time; + announce->dht = forwarding_get_dht(forwarding); + announce->net = dht_get_net(announce->dht); + announce->public_key = dht_get_self_public_key(announce->dht); + announce->secret_key = dht_get_self_secret_key(announce->dht); + new_hmac_key(announce->rng, announce->hmac_key); + + announce->start_time = mono_time_get(announce->mono_time); + + set_callback_forwarded_request(forwarding, forwarded_request_callback, announce); + + networking_registerhandler(announce->net, NET_PACKET_DATA_SEARCH_REQUEST, handle_dht_announce_request, announce); + networking_registerhandler(announce->net, NET_PACKET_DATA_RETRIEVE_REQUEST, handle_dht_announce_request, announce); + networking_registerhandler(announce->net, NET_PACKET_STORE_ANNOUNCE_REQUEST, handle_dht_announce_request, announce); + + return announce; +} + +void kill_announcements(Announcements *announce) +{ + if (announce == nullptr) { + return; + } + + set_callback_forwarded_request(announce->forwarding, nullptr, nullptr); + + networking_registerhandler(announce->net, NET_PACKET_DATA_SEARCH_REQUEST, nullptr, nullptr); + networking_registerhandler(announce->net, NET_PACKET_DATA_RETRIEVE_REQUEST, nullptr, nullptr); + networking_registerhandler(announce->net, NET_PACKET_STORE_ANNOUNCE_REQUEST, nullptr, nullptr); + + crypto_memzero(announce->hmac_key, CRYPTO_HMAC_KEY_SIZE); + crypto_memzero(&announce->shared_keys, sizeof(Shared_Keys)); + + for (uint32_t i = 0; i < ANNOUNCE_BUCKETS * ANNOUNCE_BUCKET_SIZE; ++i) { + if (announce->entries[i].data != nullptr) { + free(announce->entries[i].data); + } + } + + free(announce); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/attributes.h b/local_pod_repo/toxcore/toxcore/toxcore/attributes.h new file mode 100644 index 0000000..3da768f --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/attributes.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +/** + * printf and nonnull attributes for GCC/Clang and Cimple. + */ +#ifndef C_TOXCORE_TOXCORE_ATTRIBUTES_H +#define C_TOXCORE_TOXCORE_ATTRIBUTES_H + +/* No declarations here. */ + +//!TOKSTYLE- + +#ifdef __GNUC__ +#define GNU_PRINTF(f, a) __attribute__((__format__(__printf__, f, a))) +#else +#define GNU_PRINTF(f, a) +#endif + +#if defined(__GNUC__) && defined(_DEBUG) && !defined(__OPTIMIZE__) +#define non_null(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else +#define non_null(...) +#endif + +#define nullable(...) + +//!TOKSTYLE+ + +#endif // C_TOXCORE_TOXCORE_ATTRIBUTES_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/bin_pack.h b/local_pod_repo/toxcore/toxcore/toxcore/bin_pack.h new file mode 100644 index 0000000..542f533 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/bin_pack.h @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ +#ifndef C_TOXCORE_TOXCORE_BIN_PACK_H +#define C_TOXCORE_TOXCORE_BIN_PACK_H + +#include +#include + +#include "attributes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Binary serialisation object. + */ +typedef struct Bin_Pack Bin_Pack; + +/** @brief Function used to pack an object. + * + * This function would typically cast the `void *` to the actual object pointer type and then call + * more appropriately typed packing functions. + */ +typedef bool bin_pack_cb(Bin_Pack *bp, const void *obj); + +/** @brief Determine the serialised size of an object. + * + * @param callback The function called on the created packer and packed object. + * @param obj The object to be packed, passed as `obj` to the callback. + * + * @return The packed size of the passed object according to the callback. UINT32_MAX in case of + * errors such as buffer overflow. + */ +non_null(1) nullable(2) +uint32_t bin_pack_obj_size(bin_pack_cb *callback, const void *obj); + +/** @brief Pack an object into a buffer of a given size. + * + * This function creates and initialises a `Bin_Pack` packer object, calls the callback with the + * packer object and the to-be-packed object, and then cleans up the packer object. + * + * You can use `bin_pack_obj_size` to determine the minimum required size of `buf`. If packing + * overflows `uint32_t`, this function returns `false`. + * + * @param callback The function called on the created packer and packed object. + * @param obj The object to be packed, passed as `obj` to the callback. + * @param buf A byte array large enough to hold the serialised representation of `obj`. + * @param buf_size The size of the byte array. Can be `UINT32_MAX` to disable bounds checking. + * + * @retval false if an error occurred (e.g. buffer overflow). + */ +non_null(1, 3) nullable(2) +bool bin_pack_obj(bin_pack_cb *callback, const void *obj, uint8_t *buf, uint32_t buf_size); + +/** @brief Allocate a new packer object. + * + * This is the only function that allocates memory in this module. + * + * @param buf A byte array large enough to hold the serialised representation of `obj`. + * @param buf_size The size of the byte array. Can be `UINT32_MAX` to disable bounds checking. + * + * @retval nullptr on allocation failure. + */ +non_null() +Bin_Pack *bin_pack_new(uint8_t *buf, uint32_t buf_size); + +/** @brief Deallocates a packer object. + * + * Does not deallocate the buffer inside. + */ +nullable(1) +void bin_pack_free(Bin_Pack *bp); + +/** @brief Start packing a MessagePack array. + * + * A call to this function must be followed by exactly `size` calls to other functions below. + */ +non_null() +bool bin_pack_array(Bin_Pack *bp, uint32_t size); + +/** @brief Pack a MessagePack bool. */ +non_null() bool bin_pack_bool(Bin_Pack *bp, bool val); +/** @brief Pack a `uint8_t` as MessagePack positive integer. */ +non_null() bool bin_pack_u08(Bin_Pack *bp, uint8_t val); +/** @brief Pack a `uint16_t` as MessagePack positive integer. */ +non_null() bool bin_pack_u16(Bin_Pack *bp, uint16_t val); +/** @brief Pack a `uint32_t` as MessagePack positive integer. */ +non_null() bool bin_pack_u32(Bin_Pack *bp, uint32_t val); +/** @brief Pack a `uint64_t` as MessagePack positive integer. */ +non_null() bool bin_pack_u64(Bin_Pack *bp, uint64_t val); +/** @brief Pack a byte array as MessagePack bin. */ +non_null() bool bin_pack_bin(Bin_Pack *bp, const uint8_t *data, uint32_t length); + +/** @brief Start packing a custom binary representation. + * + * A call to this function must be followed by exactly `size` bytes packed by functions below. + */ +non_null() bool bin_pack_bin_marker(Bin_Pack *bp, uint32_t size); + +/** @brief Write a `uint8_t` directly to the packer in 1 byte. */ +non_null() bool bin_pack_u08_b(Bin_Pack *bp, uint8_t val); +/** @brief Write a `uint16_t` as big endian 16 bit int in 2 bytes. */ +non_null() bool bin_pack_u16_b(Bin_Pack *bp, uint16_t val); +/** @brief Write a `uint32_t` as big endian 32 bit int in 4 bytes. */ +non_null() bool bin_pack_u32_b(Bin_Pack *bp, uint32_t val); +/** @brief Write a `uint64_t` as big endian 64 bit int in 8 bytes. */ +non_null() bool bin_pack_u64_b(Bin_Pack *bp, uint64_t val); + +/** @brief Write a byte array directly to the packer in `length` bytes. + * + * Note that unless you prepend the array length manually, there is no record of it in the resulting + * serialised representation. + */ +non_null() bool bin_pack_bin_b(Bin_Pack *bp, const uint8_t *data, uint32_t length); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // C_TOXCORE_TOXCORE_BIN_PACK_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/bin_pack.m b/local_pod_repo/toxcore/toxcore/toxcore/bin_pack.m new file mode 100644 index 0000000..a730748 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/bin_pack.m @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "bin_pack.h" + +#include +#include +#include + +#include "cmp.h" +#include "ccompat.h" + +struct Bin_Pack { + uint8_t *bytes; + uint32_t bytes_size; + uint32_t bytes_pos; + cmp_ctx_t ctx; +}; + +non_null() +static bool null_reader(cmp_ctx_t *ctx, void *data, size_t limit) +{ + assert(limit == 0); + return false; +} + +non_null() +static bool null_skipper(cmp_ctx_t *ctx, size_t limit) +{ + assert(limit == 0); + return false; +} + +non_null() +static size_t buf_writer(cmp_ctx_t *ctx, const void *data, size_t count) +{ + Bin_Pack *bp = (Bin_Pack *)ctx->buf; + assert(bp != nullptr); + const uint32_t new_pos = bp->bytes_pos + count; + if (new_pos < bp->bytes_pos) { + // 32 bit overflow. + return 0; + } + if (bp->bytes != nullptr) { + if (new_pos > bp->bytes_size) { + // Buffer too small. + return 0; + } + memcpy(bp->bytes + bp->bytes_pos, data, count); + } + bp->bytes_pos += count; + return count; +} + +non_null(1) nullable(2) +static void bin_pack_init(Bin_Pack *bp, uint8_t *buf, uint32_t buf_size) +{ + bp->bytes = buf; + bp->bytes_size = buf_size; + bp->bytes_pos = 0; + cmp_init(&bp->ctx, bp, null_reader, null_skipper, buf_writer); +} + +bool bin_pack_obj(bin_pack_cb *callback, const void *obj, uint8_t *buf, uint32_t buf_size) +{ + Bin_Pack bp; + bin_pack_init(&bp, buf, buf_size); + return callback(&bp, obj); +} + +uint32_t bin_pack_obj_size(bin_pack_cb *callback, const void *obj) +{ + Bin_Pack bp; + bin_pack_init(&bp, nullptr, 0); + callback(&bp, obj); + return bp.bytes_pos; +} + +Bin_Pack *bin_pack_new(uint8_t *buf, uint32_t buf_size) +{ + Bin_Pack *bp = (Bin_Pack *)calloc(1, sizeof(Bin_Pack)); + if (bp == nullptr) { + return nullptr; + } + bin_pack_init(bp, buf, buf_size); + return bp; +} + +void bin_pack_free(Bin_Pack *bp) +{ + free(bp); +} + +bool bin_pack_array(Bin_Pack *bp, uint32_t size) +{ + return cmp_write_array(&bp->ctx, size); +} + +bool bin_pack_bool(Bin_Pack *bp, bool val) +{ + return cmp_write_bool(&bp->ctx, val); +} + +bool bin_pack_u08(Bin_Pack *bp, uint8_t val) +{ + return cmp_write_uinteger(&bp->ctx, val); +} + +bool bin_pack_u16(Bin_Pack *bp, uint16_t val) +{ + return cmp_write_uinteger(&bp->ctx, val); +} + +bool bin_pack_u32(Bin_Pack *bp, uint32_t val) +{ + return cmp_write_uinteger(&bp->ctx, val); +} + +bool bin_pack_u64(Bin_Pack *bp, uint64_t val) +{ + return cmp_write_uinteger(&bp->ctx, val); +} + +bool bin_pack_bin(Bin_Pack *bp, const uint8_t *data, uint32_t length) +{ + return cmp_write_bin(&bp->ctx, data, length); +} + +bool bin_pack_bin_marker(Bin_Pack *bp, uint32_t size) +{ + return cmp_write_bin_marker(&bp->ctx, size); +} + +bool bin_pack_u08_b(Bin_Pack *bp, uint8_t val) +{ + return bp->ctx.write(&bp->ctx, &val, 1) == 1; +} + +bool bin_pack_u16_b(Bin_Pack *bp, uint16_t val) +{ + return bin_pack_u08_b(bp, (val >> 8) & 0xff) + && bin_pack_u08_b(bp, val & 0xff); +} + +bool bin_pack_u32_b(Bin_Pack *bp, uint32_t val) +{ + return bin_pack_u16_b(bp, (val >> 16) & 0xffff) + && bin_pack_u16_b(bp, val & 0xffff); +} + +bool bin_pack_u64_b(Bin_Pack *bp, uint64_t val) +{ + return bin_pack_u32_b(bp, (val >> 32) & 0xffffffff) + && bin_pack_u32_b(bp, val & 0xffffffff); +} + +bool bin_pack_bin_b(Bin_Pack *bp, const uint8_t *data, uint32_t length) +{ + return bp->ctx.write(&bp->ctx, data, length) == length; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/bin_unpack.h b/local_pod_repo/toxcore/toxcore/toxcore/bin_unpack.h new file mode 100644 index 0000000..c6ead96 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/bin_unpack.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#ifndef C_TOXCORE_TOXCORE_BIN_UNPACK_H +#define C_TOXCORE_TOXCORE_BIN_UNPACK_H + +#include +#include + +#include "attributes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Binary deserialisation object. + */ +typedef struct Bin_Unpack Bin_Unpack; + +/** @brief Allocate a new unpacker object. + * + * @param buf The byte array to unpack values from. + * @param buf_size The size of the byte array. + * + * @retval nullptr on allocation failure. + */ +non_null() +Bin_Unpack *bin_unpack_new(const uint8_t *buf, uint32_t buf_size); + +/** @brief Deallocates an unpacker object. + * + * Does not deallocate the buffer inside. + */ +nullable(1) +void bin_unpack_free(Bin_Unpack *bu); + +/** @brief Start unpacking a MessagePack array. + * + * A call to this function must be followed by exactly `size` calls to other functions below. + * + * @param size Will contain the number of array elements following the array marker. + */ +non_null() bool bin_unpack_array(Bin_Unpack *bu, uint32_t *size); + +/** @brief Start unpacking a fixed size MessagePack array. + * + * @retval false if the packed array size is not exactly the required size. + */ +non_null() bool bin_unpack_array_fixed(Bin_Unpack *bu, uint32_t required_size); + +/** @brief Unpack a MessagePack bool. */ +non_null() bool bin_unpack_bool(Bin_Unpack *bu, bool *val); +/** @brief Unpack a MessagePack positive int into a `uint8_t`. */ +non_null() bool bin_unpack_u08(Bin_Unpack *bu, uint8_t *val); +/** @brief Unpack a MessagePack positive int into a `uint16_t`. */ +non_null() bool bin_unpack_u16(Bin_Unpack *bu, uint16_t *val); +/** @brief Unpack a MessagePack positive int into a `uint32_t`. */ +non_null() bool bin_unpack_u32(Bin_Unpack *bu, uint32_t *val); +/** @brief Unpack a MessagePack positive int into a `uint64_t`. */ +non_null() bool bin_unpack_u64(Bin_Unpack *bu, uint64_t *val); +/** @brief Unpack a MessagePack bin into a newly allocated byte array. + * + * Allocates a new byte array and stores it into `data_ptr` with its length stored in + * `data_length_ptr`. This function requires that the unpacking buffer has at least as many bytes + * remaining to be unpacked as the bin claims to need, so it's not possible to cause an arbitrarily + * large allocation unless the input array was already that large. + */ +non_null() bool bin_unpack_bin(Bin_Unpack *bu, uint8_t **data_ptr, uint32_t *data_length_ptr); +/** @brief Unpack a MessagePack bin of a fixed length into a pre-allocated byte array. + * + * Unlike the function above, this function does not allocate any memory, but requires the size to + * be known up front. + */ +non_null() bool bin_unpack_bin_fixed(Bin_Unpack *bu, uint8_t *data, uint32_t data_length); + +/** @brief Start unpacking a custom binary representation. + * + * A call to this function must be followed by exactly `size` bytes packed by functions below. + */ +non_null() bool bin_unpack_bin_size(Bin_Unpack *bu, uint32_t *size); + +/** @brief Read a `uint8_t` directly from the unpacker, consuming 1 byte. */ +non_null() bool bin_unpack_u08_b(Bin_Unpack *bu, uint8_t *val); +/** @brief Read a `uint16_t` as big endian 16 bit int, consuming 2 bytes. */ +non_null() bool bin_unpack_u16_b(Bin_Unpack *bu, uint16_t *val); +/** @brief Read a `uint32_t` as big endian 32 bit int, consuming 4 bytes. */ +non_null() bool bin_unpack_u32_b(Bin_Unpack *bu, uint32_t *val); +/** @brief Read a `uint64_t` as big endian 64 bit int, consuming 8 bytes. */ +non_null() bool bin_unpack_u64_b(Bin_Unpack *bu, uint64_t *val); + +/** @brief Read a byte array directly from the packer, consuming `length` bytes. */ +non_null() bool bin_unpack_bin_b(Bin_Unpack *bu, uint8_t *data, uint32_t length); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // C_TOXCORE_TOXCORE_BIN_UNPACK_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/bin_unpack.m b/local_pod_repo/toxcore/toxcore/toxcore/bin_unpack.m new file mode 100644 index 0000000..2035a12 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/bin_unpack.m @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "bin_unpack.h" + +#include +#include +#include + +#include "cmp.h" +#include "ccompat.h" + +struct Bin_Unpack { + const uint8_t *bytes; + uint32_t bytes_size; + cmp_ctx_t ctx; +}; + +non_null() +static bool buf_reader(cmp_ctx_t *ctx, void *data, size_t limit) +{ + Bin_Unpack *reader = (Bin_Unpack *)ctx->buf; + assert(reader != nullptr && reader->bytes != nullptr); + if (limit > reader->bytes_size) { + return false; + } + memcpy(data, reader->bytes, limit); + reader->bytes += limit; + reader->bytes_size -= limit; + return true; +} + +non_null() +static bool buf_skipper(cmp_ctx_t *ctx, size_t limit) +{ + Bin_Unpack *reader = (Bin_Unpack *)ctx->buf; + assert(reader != nullptr && reader->bytes != nullptr); + if (limit > reader->bytes_size) { + return false; + } + reader->bytes += limit; + reader->bytes_size -= limit; + return true; +} + +non_null() +static size_t null_writer(cmp_ctx_t *ctx, const void *data, size_t count) +{ + assert(count == 0); + return 0; +} + +Bin_Unpack *bin_unpack_new(const uint8_t *buf, uint32_t buf_size) +{ + Bin_Unpack *bu = (Bin_Unpack *)calloc(1, sizeof(Bin_Unpack)); + if (bu == nullptr) { + return nullptr; + } + bu->bytes = buf; + bu->bytes_size = buf_size; + cmp_init(&bu->ctx, bu, buf_reader, buf_skipper, null_writer); + return bu; +} + +void bin_unpack_free(Bin_Unpack *bu) +{ + free(bu); +} + +bool bin_unpack_array(Bin_Unpack *bu, uint32_t *size) +{ + return cmp_read_array(&bu->ctx, size) && *size <= bu->bytes_size; +} + +bool bin_unpack_array_fixed(Bin_Unpack *bu, uint32_t required_size) +{ + uint32_t size; + return cmp_read_array(&bu->ctx, &size) && size == required_size; +} + +bool bin_unpack_bool(Bin_Unpack *bu, bool *val) +{ + return cmp_read_bool(&bu->ctx, val); +} + +bool bin_unpack_u08(Bin_Unpack *bu, uint8_t *val) +{ + return cmp_read_uchar(&bu->ctx, val); +} + +bool bin_unpack_u16(Bin_Unpack *bu, uint16_t *val) +{ + return cmp_read_ushort(&bu->ctx, val); +} + +bool bin_unpack_u32(Bin_Unpack *bu, uint32_t *val) +{ + return cmp_read_uint(&bu->ctx, val); +} + +bool bin_unpack_u64(Bin_Unpack *bu, uint64_t *val) +{ + return cmp_read_ulong(&bu->ctx, val); +} + +bool bin_unpack_bin(Bin_Unpack *bu, uint8_t **data_ptr, uint32_t *data_length_ptr) +{ + uint32_t bin_size; + if (!bin_unpack_bin_size(bu, &bin_size) || bin_size > bu->bytes_size) { + // There aren't as many bytes as this bin claims to want to allocate. + return false; + } + uint8_t *const data = (uint8_t *)malloc(bin_size); + + if (!bin_unpack_bin_b(bu, data, bin_size)) { + free(data); + return false; + } + + *data_ptr = data; + *data_length_ptr = bin_size; + return true; +} + +bool bin_unpack_bin_fixed(Bin_Unpack *bu, uint8_t *data, uint32_t data_length) +{ + uint32_t bin_size; + if (!bin_unpack_bin_size(bu, &bin_size) || bin_size != data_length) { + return false; + } + + return bin_unpack_bin_b(bu, data, bin_size); +} + +bool bin_unpack_bin_size(Bin_Unpack *bu, uint32_t *size) +{ + return cmp_read_bin_size(&bu->ctx, size); +} + +bool bin_unpack_u08_b(Bin_Unpack *bu, uint8_t *val) +{ + return bin_unpack_bin_b(bu, val, 1); +} + +bool bin_unpack_u16_b(Bin_Unpack *bu, uint16_t *val) +{ + uint8_t hi = 0; + uint8_t lo = 0; + if (!(bin_unpack_u08_b(bu, &hi) + && bin_unpack_u08_b(bu, &lo))) { + return false; + } + *val = ((uint16_t)hi << 8) | lo; + return true; +} + +bool bin_unpack_u32_b(Bin_Unpack *bu, uint32_t *val) +{ + uint16_t hi = 0; + uint16_t lo = 0; + if (!(bin_unpack_u16_b(bu, &hi) + && bin_unpack_u16_b(bu, &lo))) { + return false; + } + *val = ((uint32_t)hi << 16) | lo; + return true; +} + +bool bin_unpack_u64_b(Bin_Unpack *bu, uint64_t *val) +{ + uint32_t hi = 0; + uint32_t lo = 0; + if (!(bin_unpack_u32_b(bu, &hi) + && bin_unpack_u32_b(bu, &lo))) { + return false; + } + *val = ((uint64_t)hi << 32) | lo; + return true; +} + +bool bin_unpack_bin_b(Bin_Unpack *bu, uint8_t *data, uint32_t length) +{ + return bu->ctx.read(&bu->ctx, data, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/ccompat.h b/local_pod_repo/toxcore/toxcore/toxcore/ccompat.h new file mode 100644 index 0000000..9ea6739 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/ccompat.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2021 The TokTok team. + */ + +/** + * C language compatibility macros for varying compiler support. + */ +#ifndef C_TOXCORE_TOXCORE_CCOMPAT_H +#define C_TOXCORE_TOXCORE_CCOMPAT_H + +#include // NULL, size_t + +#include "attributes.h" + +//!TOKSTYLE- + +// Variable length arrays. +// VLA(type, name, size) allocates a variable length array with automatic +// storage duration. VLA_SIZE(name) evaluates to the runtime size of that array +// in bytes. +// +// If C99 VLAs are not available, an emulation using alloca (stack allocation +// "function") is used. Note the semantic difference: alloca'd memory does not +// get freed at the end of the declaration's scope. Do not use VLA() in loops or +// you may run out of stack space. +#if !defined(DISABLE_VLA) && !defined(_MSC_VER) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +// C99 VLAs. +#define ALLOC_VLA(type, name, size) type name[size] +#define SIZEOF_VLA sizeof +#else + +// Emulation using alloca. +#ifdef _WIN32 +#include +#elif defined(__COMPCERT__) +// TODO(iphydf): This leaks memory like crazy, so compcert is useless for now. +// Once we're rid of VLAs, we can remove this and compcert becomes useful. +#define alloca malloc +#include +#elif defined(__linux__) +#include +#else +#include +#if !defined(alloca) && defined(__GNUC__) +#define alloca __builtin_alloca +#endif +#endif + +#define ALLOC_VLA(type, name, size) \ + const size_t name##_vla_size = (size) * sizeof(type); \ + type *const name = (type *)alloca(name##_vla_size) +#define SIZEOF_VLA(name) name##_vla_size + +#endif + +#ifdef MAX_VLA_SIZE +#include +#define VLA(type, name, size) \ + ALLOC_VLA(type, name, size); \ + assert((size_t)(size) * sizeof(type) <= MAX_VLA_SIZE) +#else +#define VLA ALLOC_VLA +#endif + +#if !defined(__cplusplus) || __cplusplus < 201103L +#define nullptr NULL +#ifndef static_assert +#ifdef __GNUC__ +// We'll just assume gcc and clang support C11 _Static_assert. +#define static_assert _Static_assert +#else // !__GNUC__ +#define STATIC_ASSERT_(cond, msg, line) typedef int static_assert_##line[(cond) ? 1 : -1] +#define STATIC_ASSERT(cond, msg, line) STATIC_ASSERT_(cond, msg, line) +#define static_assert(cond, msg) STATIC_ASSERT(cond, msg, __LINE__) +#endif // !__GNUC__ +#endif // !static_assert +#endif // !__cplusplus + +#ifdef __GNUC__ +#define GNU_PRINTF(f, a) __attribute__((__format__(__printf__, f, a))) +#else +#define GNU_PRINTF(f, a) +#endif + +//!TOKSTYLE+ + +#endif // C_TOXCORE_TOXCORE_CCOMPAT_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/ccompat.m b/local_pod_repo/toxcore/toxcore/toxcore/ccompat.m new file mode 100644 index 0000000..30e689d --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/ccompat.m @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ +#include "ccompat.h" diff --git a/local_pod_repo/toxcore/toxcore/toxcore/crypto_core.h b/local_pod_repo/toxcore/toxcore/toxcore/crypto_core.h new file mode 100644 index 0000000..5f525c7 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/crypto_core.h @@ -0,0 +1,461 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** @file + * @brief Functions for the core crypto. + */ +#ifndef C_TOXCORE_TOXCORE_CRYPTO_CORE_H +#define C_TOXCORE_TOXCORE_CRYPTO_CORE_H + +#include +#include +#include + +#include "attributes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The number of bytes in a signature. + */ +#define CRYPTO_SIGNATURE_SIZE 64 + +/** + * The number of bytes in a Tox public key used for signatures. + */ +#define CRYPTO_SIGN_PUBLIC_KEY_SIZE 32 + +/** + * The number of bytes in a Tox secret key used for signatures. + */ +#define CRYPTO_SIGN_SECRET_KEY_SIZE 64 + +/** + * @brief The number of bytes in a Tox public key used for encryption. + */ +#define CRYPTO_PUBLIC_KEY_SIZE 32 + +/** + * @brief The number of bytes in a Tox secret key used for encryption. + */ +#define CRYPTO_SECRET_KEY_SIZE 32 + +/** + * @brief The number of bytes in a shared key computed from public and secret keys. + */ +#define CRYPTO_SHARED_KEY_SIZE 32 + +/** + * @brief The number of bytes in a symmetric key. + */ +#define CRYPTO_SYMMETRIC_KEY_SIZE CRYPTO_SHARED_KEY_SIZE + +/** + * @brief The number of bytes needed for the MAC (message authentication code) in an + * encrypted message. + */ +#define CRYPTO_MAC_SIZE 16 + +/** + * @brief The number of bytes in a nonce used for encryption/decryption. + */ +#define CRYPTO_NONCE_SIZE 24 + +/** + * @brief The number of bytes in a SHA256 hash. + */ +#define CRYPTO_SHA256_SIZE 32 + +/** + * @brief The number of bytes in a SHA512 hash. + */ +#define CRYPTO_SHA512_SIZE 64 + +typedef void crypto_random_bytes_cb(void *obj, uint8_t *bytes, size_t length); +typedef uint32_t crypto_random_uniform_cb(void *obj, uint32_t upper_bound); + +typedef struct Random_Funcs { + crypto_random_bytes_cb *random_bytes; + crypto_random_uniform_cb *random_uniform; +} Random_Funcs; + +typedef struct Random { + const Random_Funcs *funcs; + void *obj; +} Random; + +const Random *system_random(void); + +/** + * @brief The number of bytes in an encryption public key used by DHT group chats. + */ +#define ENC_PUBLIC_KEY_SIZE CRYPTO_PUBLIC_KEY_SIZE + +/** + * @brief The number of bytes in an encryption secret key used by DHT group chats. + */ +#define ENC_SECRET_KEY_SIZE CRYPTO_SECRET_KEY_SIZE + +/** + * @brief The number of bytes in a signature public key. + */ +#define SIG_PUBLIC_KEY_SIZE CRYPTO_SIGN_PUBLIC_KEY_SIZE + +/** + * @brief The number of bytes in a signature secret key. + */ +#define SIG_SECRET_KEY_SIZE CRYPTO_SIGN_SECRET_KEY_SIZE + +/** + * @brief The number of bytes in a DHT group chat public key identifier. + */ +#define CHAT_ID_SIZE SIG_PUBLIC_KEY_SIZE + +/** + * @brief The number of bytes in an extended public key used by DHT group chats. + */ +#define EXT_PUBLIC_KEY_SIZE (ENC_PUBLIC_KEY_SIZE + SIG_PUBLIC_KEY_SIZE) + +/** + * @brief The number of bytes in an extended secret key used by DHT group chats. + */ +#define EXT_SECRET_KEY_SIZE (ENC_SECRET_KEY_SIZE + SIG_SECRET_KEY_SIZE) + +/** + * @brief The number of bytes in an HMAC authenticator. + */ +#define CRYPTO_HMAC_SIZE 32 + +/** + * @brief The number of bytes in an HMAC secret key. + */ +#define CRYPTO_HMAC_KEY_SIZE 32 + +/** + * @brief A `bzero`-like function which won't be optimised away by the compiler. + * + * Some compilers will inline `bzero` or `memset` if they can prove that there + * will be no reads to the written data. Use this function if you want to be + * sure the memory is indeed zeroed. + */ +non_null() +void crypto_memzero(void *data, size_t length); + +/** + * @brief Compute a SHA256 hash (32 bytes). + */ +non_null() +void crypto_sha256(uint8_t *hash, const uint8_t *data, size_t length); + +/** + * @brief Compute a SHA512 hash (64 bytes). + */ +non_null() +void crypto_sha512(uint8_t *hash, const uint8_t *data, size_t length); + +/** + * @brief Compute an HMAC authenticator (32 bytes). + * + * @param auth Resulting authenticator. + * @param key Secret key, as generated by `new_hmac_key()`. + */ +non_null() +void crypto_hmac(uint8_t auth[CRYPTO_HMAC_SIZE], const uint8_t key[CRYPTO_HMAC_KEY_SIZE], const uint8_t *data, + size_t length); + +/** + * @brief Verify an HMAC authenticator. + */ +non_null() +bool crypto_hmac_verify(const uint8_t auth[CRYPTO_HMAC_SIZE], const uint8_t key[CRYPTO_HMAC_KEY_SIZE], + const uint8_t *data, size_t length); + +/** + * @brief Compare 2 public keys of length @ref CRYPTO_PUBLIC_KEY_SIZE, not vulnerable to + * timing attacks. + * + * @retval true if both mem locations of length are equal + * @retval false if they are not + */ +non_null() +bool pk_equal(const uint8_t pk1[CRYPTO_PUBLIC_KEY_SIZE], const uint8_t pk2[CRYPTO_PUBLIC_KEY_SIZE]); + +/** + * @brief Copy a public key from `src` to `dest`. + */ +non_null() +void pk_copy(uint8_t dest[CRYPTO_PUBLIC_KEY_SIZE], const uint8_t src[CRYPTO_PUBLIC_KEY_SIZE]); + +/** + * @brief Compare 2 SHA512 checksums of length CRYPTO_SHA512_SIZE, not vulnerable to + * timing attacks. + * + * @return true if both mem locations of length are equal, false if they are not. + */ +non_null() +bool crypto_sha512_eq(const uint8_t *cksum1, const uint8_t *cksum2); + +/** + * @brief Compare 2 SHA256 checksums of length CRYPTO_SHA256_SIZE, not vulnerable to + * timing attacks. + * + * @return true if both mem locations of length are equal, false if they are not. + */ +non_null() +bool crypto_sha256_eq(const uint8_t *cksum1, const uint8_t *cksum2); + +/** + * @brief Return a random 8 bit integer. + */ +non_null() +uint8_t random_u08(const Random *rng); + +/** + * @brief Return a random 16 bit integer. + */ +non_null() +uint16_t random_u16(const Random *rng); + +/** + * @brief Return a random 32 bit integer. + */ +non_null() +uint32_t random_u32(const Random *rng); + +/** + * @brief Return a random 64 bit integer. + */ +non_null() +uint64_t random_u64(const Random *rng); + +/** + * @brief Return a random 32 bit integer between 0 and upper_bound (excluded). + * + * On libsodium builds this function guarantees a uniform distribution of possible outputs. + * On vanilla NACL builds this function is equivalent to `random() % upper_bound`. + */ +non_null() +uint32_t random_range_u32(const Random *rng, uint32_t upper_bound); + +/** @brief Cryptographically signs a message using the supplied secret key and puts the resulting signature + * in the supplied buffer. + * + * @param signature The buffer for the resulting signature, which must have room for at + * least CRYPTO_SIGNATURE_SIZE bytes. + * @param message The message being signed. + * @param message_length The length in bytes of the message being signed. + * @param secret_key The secret key used to create the signature. The key should be + * produced by either `create_extended_keypair` or the libsodium function `crypto_sign_keypair`. + * + * @retval true on success. + */ +non_null() +bool crypto_signature_create(uint8_t *signature, const uint8_t *message, uint64_t message_length, + const uint8_t *secret_key); + +/** @brief Verifies that the given signature was produced by a given message and public key. + * + * @param signature The signature we wish to verify. + * @param message The message we wish to verify. + * @param message_length The length of the message. + * @param public_key The public key counterpart of the secret key that was used to + * create the signature. + * + * @retval true on success. + */ +non_null() +bool crypto_signature_verify(const uint8_t *signature, const uint8_t *message, uint64_t message_length, + const uint8_t *public_key); + +/** + * @brief Fill the given nonce with random bytes. + */ +non_null() +void random_nonce(const Random *rng, uint8_t *nonce); + +/** + * @brief Fill an array of bytes with random values. + */ +non_null() +void random_bytes(const Random *rng, uint8_t *bytes, size_t length); + +/** + * @brief Check if a Tox public key CRYPTO_PUBLIC_KEY_SIZE is valid or not. + * + * This should only be used for input validation. + * + * @return false if it isn't, true if it is. + */ +non_null() +bool public_key_valid(const uint8_t *public_key); + +/** @brief Creates an extended keypair: curve25519 and ed25519 for encryption and signing + * respectively. The Encryption keys are derived from the signature keys. + * + * @param pk The buffer where the public key will be stored. Must have room for EXT_PUBLIC_KEY_SIZE bytes. + * @param sk The buffer where the secret key will be stored. Must have room for EXT_SECRET_KEY_SIZE bytes. + * + * @retval true on success. + */ +non_null() +bool create_extended_keypair(uint8_t *pk, uint8_t *sk); + +/** Functions for groupchat extended keys */ +non_null() const uint8_t *get_enc_key(const uint8_t *key); +non_null() const uint8_t *get_sig_pk(const uint8_t *key); +non_null() void set_sig_pk(uint8_t *key, const uint8_t *sig_pk); +non_null() const uint8_t *get_sig_sk(const uint8_t *key); +non_null() const uint8_t *get_chat_id(const uint8_t *key); + +/** + * @brief Generate a new random keypair. + * + * Every call to this function is likely to generate a different keypair. + */ +non_null() +int32_t crypto_new_keypair(const Random *rng, uint8_t *public_key, uint8_t *secret_key); + +/** + * @brief Derive the public key from a given secret key. + */ +non_null() +void crypto_derive_public_key(uint8_t *public_key, const uint8_t *secret_key); + +/** + * @brief Encrypt message to send from secret key to public key. + * + * Encrypt plain text of the given length to encrypted of + * `length + CRYPTO_MAC_SIZE` using the public key (@ref CRYPTO_PUBLIC_KEY_SIZE + * bytes) of the receiver and the secret key of the sender and a + * @ref CRYPTO_NONCE_SIZE byte nonce. + * + * @retval -1 if there was a problem. + * @return length of encrypted data if everything was fine. + */ +non_null() +int32_t encrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *plain, + size_t length, uint8_t *encrypted); + +/** + * @brief Decrypt message from public key to secret key. + * + * Decrypt encrypted text of the given @p length to plain text of the given + * `length - CRYPTO_MAC_SIZE` using the public key (@ref CRYPTO_PUBLIC_KEY_SIZE + * bytes) of the sender, the secret key of the receiver and a + * @ref CRYPTO_NONCE_SIZE byte nonce. + * + * @retval -1 if there was a problem (decryption failed). + * @return length of plain text data if everything was fine. + */ +non_null() +int32_t decrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, + const uint8_t *encrypted, size_t length, uint8_t *plain); + +/** + * @brief Fast encrypt/decrypt operations. + * + * Use if this is not a one-time communication. @ref encrypt_precompute does the + * shared-key generation once so it does not have to be performed on every + * encrypt/decrypt. + */ +non_null() +int32_t encrypt_precompute(const uint8_t *public_key, const uint8_t *secret_key, uint8_t *shared_key); + +/** + * @brief Encrypt message with precomputed shared key. + * + * Encrypts plain of length length to encrypted of length + @ref CRYPTO_MAC_SIZE + * using a shared key @ref CRYPTO_SYMMETRIC_KEY_SIZE big and a @ref CRYPTO_NONCE_SIZE + * byte nonce. + * + * @retval -1 if there was a problem. + * @return length of encrypted data if everything was fine. + */ +non_null() +int32_t encrypt_data_symmetric(const uint8_t *shared_key, const uint8_t *nonce, const uint8_t *plain, size_t length, + uint8_t *encrypted); + +/** + * @brief Decrypt message with precomputed shared key. + * + * Decrypts encrypted of length length to plain of length + * `length - CRYPTO_MAC_SIZE` using a shared key @ref CRYPTO_SHARED_KEY_SIZE + * big and a @ref CRYPTO_NONCE_SIZE byte nonce. + * + * @retval -1 if there was a problem (decryption failed). + * @return length of plain data if everything was fine. + */ +non_null() +int32_t decrypt_data_symmetric(const uint8_t *shared_key, const uint8_t *nonce, const uint8_t *encrypted, size_t length, + uint8_t *plain); + +/** + * @brief Increment the given nonce by 1 in big endian (rightmost byte incremented + * first). + */ +non_null() +void increment_nonce(uint8_t *nonce); + +/** + * @brief Increment the given nonce by a given number. + * + * The number should be in host byte order. + */ +non_null() +void increment_nonce_number(uint8_t *nonce, uint32_t increment); + +/** + * @brief Fill a key @ref CRYPTO_SYMMETRIC_KEY_SIZE big with random bytes. + * + * This does the same as `new_symmetric_key` but without giving the Random object implicitly. + * It is as safe as `new_symmetric_key`. + */ +non_null() +void new_symmetric_key_implicit_random(uint8_t *key); + +/** + * @brief Fill a key @ref CRYPTO_SYMMETRIC_KEY_SIZE big with random bytes. + */ +non_null() +void new_symmetric_key(const Random *rng, uint8_t *key); + +/** + * @brief Locks `length` bytes of memory pointed to by `data`. + * + * This will attempt to prevent the specified memory region from being swapped + * to disk. + * + * @return true on success. + */ +non_null() +bool crypto_memlock(void *data, size_t length); + +/** + * @brief Unlocks `length` bytes of memory pointed to by `data`. + * + * This allows the specified memory region to be swapped to disk. + * + * This function call has the side effect of zeroing the specified memory region + * whether or not it succeeds. Therefore it should only be used once the memory + * is no longer in use. + * + * @return true on success. + */ +non_null() +bool crypto_memunlock(void *data, size_t length); + +/** + * @brief Generate a random secret HMAC key. + */ +non_null() +void new_hmac_key(const Random *rng, uint8_t key[CRYPTO_HMAC_KEY_SIZE]); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // C_TOXCORE_TOXCORE_CRYPTO_CORE_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/crypto_core.m b/local_pod_repo/toxcore/toxcore/toxcore/crypto_core.m new file mode 100644 index 0000000..748790f --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/crypto_core.m @@ -0,0 +1,578 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Functions for the core crypto. + * + * NOTE: This code has to be perfect. We don't mess around with encryption. + */ +#include "crypto_core.h" + +#include +#include +#include + +#ifndef VANILLA_NACL +// We use libsodium by default. +#include "sodium.h" +#else +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "ccompat.h" + +#ifndef crypto_box_MACBYTES +#define crypto_box_MACBYTES (crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES) +#endif + +#ifndef VANILLA_NACL +// Need dht because of ENC_SECRET_KEY_SIZE and ENC_PUBLIC_KEY_SIZE +#define ENC_PUBLIC_KEY_SIZE CRYPTO_PUBLIC_KEY_SIZE +#define ENC_SECRET_KEY_SIZE CRYPTO_SECRET_KEY_SIZE +#endif + +static_assert(CRYPTO_PUBLIC_KEY_SIZE == crypto_box_PUBLICKEYBYTES, + "CRYPTO_PUBLIC_KEY_SIZE should be equal to crypto_box_PUBLICKEYBYTES"); +static_assert(CRYPTO_SECRET_KEY_SIZE == crypto_box_SECRETKEYBYTES, + "CRYPTO_SECRET_KEY_SIZE should be equal to crypto_box_SECRETKEYBYTES"); +static_assert(CRYPTO_SHARED_KEY_SIZE == crypto_box_BEFORENMBYTES, + "CRYPTO_SHARED_KEY_SIZE should be equal to crypto_box_BEFORENMBYTES"); +static_assert(CRYPTO_SYMMETRIC_KEY_SIZE == crypto_box_BEFORENMBYTES, + "CRYPTO_SYMMETRIC_KEY_SIZE should be equal to crypto_box_BEFORENMBYTES"); +static_assert(CRYPTO_MAC_SIZE == crypto_box_MACBYTES, + "CRYPTO_MAC_SIZE should be equal to crypto_box_MACBYTES"); +static_assert(CRYPTO_NONCE_SIZE == crypto_box_NONCEBYTES, + "CRYPTO_NONCE_SIZE should be equal to crypto_box_NONCEBYTES"); +static_assert(CRYPTO_HMAC_SIZE == crypto_auth_BYTES, + "CRYPTO_HMAC_SIZE should be equal to crypto_auth_BYTES"); +static_assert(CRYPTO_HMAC_KEY_SIZE == crypto_auth_KEYBYTES, + "CRYPTO_HMAC_KEY_SIZE should be equal to crypto_auth_KEYBYTES"); +static_assert(CRYPTO_SHA256_SIZE == crypto_hash_sha256_BYTES, + "CRYPTO_SHA256_SIZE should be equal to crypto_hash_sha256_BYTES"); +static_assert(CRYPTO_SHA512_SIZE == crypto_hash_sha512_BYTES, + "CRYPTO_SHA512_SIZE should be equal to crypto_hash_sha512_BYTES"); +static_assert(CRYPTO_PUBLIC_KEY_SIZE == 32, + "CRYPTO_PUBLIC_KEY_SIZE is required to be 32 bytes for pk_equal to work"); + +#ifndef VANILLA_NACL +static_assert(CRYPTO_SIGNATURE_SIZE == crypto_sign_BYTES, + "CRYPTO_SIGNATURE_SIZE should be equal to crypto_sign_BYTES"); +static_assert(CRYPTO_SIGN_PUBLIC_KEY_SIZE == crypto_sign_PUBLICKEYBYTES, + "CRYPTO_SIGN_PUBLIC_KEY_SIZE should be equal to crypto_sign_PUBLICKEYBYTES"); +static_assert(CRYPTO_SIGN_SECRET_KEY_SIZE == crypto_sign_SECRETKEYBYTES, + "CRYPTO_SIGN_SECRET_KEY_SIZE should be equal to crypto_sign_SECRETKEYBYTES"); +#endif /* VANILLA_NACL */ + +bool create_extended_keypair(uint8_t *pk, uint8_t *sk) +{ +#ifdef VANILLA_NACL + return false; +#else + /* create signature key pair */ + crypto_sign_keypair(pk + ENC_PUBLIC_KEY_SIZE, sk + ENC_SECRET_KEY_SIZE); + + /* convert public signature key to public encryption key */ + const int res1 = crypto_sign_ed25519_pk_to_curve25519(pk, pk + ENC_PUBLIC_KEY_SIZE); + + /* convert secret signature key to secret encryption key */ + const int res2 = crypto_sign_ed25519_sk_to_curve25519(sk, sk + ENC_SECRET_KEY_SIZE); + + return res1 == 0 && res2 == 0; +#endif +} + +const uint8_t *get_enc_key(const uint8_t *key) +{ + return key; +} + +const uint8_t *get_sig_pk(const uint8_t *key) +{ + return key + ENC_PUBLIC_KEY_SIZE; +} + +void set_sig_pk(uint8_t *key, const uint8_t *sig_pk) +{ + memcpy(key + ENC_PUBLIC_KEY_SIZE, sig_pk, SIG_PUBLIC_KEY_SIZE); +} + +const uint8_t *get_sig_sk(const uint8_t *key) +{ + return key + ENC_SECRET_KEY_SIZE; +} + +const uint8_t *get_chat_id(const uint8_t *key) +{ + return key + ENC_PUBLIC_KEY_SIZE; +} + +#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +static uint8_t *crypto_malloc(size_t bytes) +{ + uint8_t *ptr = (uint8_t *)malloc(bytes); + + if (ptr != nullptr) { + crypto_memlock(ptr, bytes); + } + + return ptr; +} + +nullable(1) +static void crypto_free(uint8_t *ptr, size_t bytes) +{ + if (ptr != nullptr) { + crypto_memzero(ptr, bytes); + crypto_memunlock(ptr, bytes); + } + + free(ptr); +} +#endif // !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + +void crypto_memzero(void *data, size_t length) +{ +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) || defined(VANILLA_NACL) + memset(data, 0, length); +#else + sodium_memzero(data, length); +#endif +} + +bool crypto_memlock(void *data, size_t length) +{ +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) || defined(VANILLA_NACL) + return false; +#else + + if (sodium_mlock(data, length) != 0) { + return false; + } + + return true; +#endif +} + +bool crypto_memunlock(void *data, size_t length) +{ +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) || defined(VANILLA_NACL) + return false; +#else + + if (sodium_munlock(data, length) != 0) { + return false; + } + + return true; +#endif +} + +bool pk_equal(const uint8_t *pk1, const uint8_t *pk2) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + // Hope that this is better for the fuzzer + return memcmp(pk1, pk2, CRYPTO_PUBLIC_KEY_SIZE) == 0; +#else + return crypto_verify_32(pk1, pk2) == 0; +#endif +} + +void pk_copy(uint8_t *dest, const uint8_t *src) +{ + memcpy(dest, src, CRYPTO_PUBLIC_KEY_SIZE); +} + +bool crypto_sha512_eq(const uint8_t *cksum1, const uint8_t *cksum2) +{ +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + // Hope that this is better for the fuzzer + return memcmp(cksum1, cksum2, CRYPTO_SHA512_SIZE) == 0; +#elif defined(VANILLA_NACL) + const int lo = crypto_verify_32(cksum1, cksum2) == 0 ? 1 : 0; + const int hi = crypto_verify_32(cksum1 + 8, cksum2 + 8) == 0 ? 1 : 0; + return (lo & hi) == 1; +#else + return crypto_verify_64(cksum1, cksum2) == 0; +#endif +} + +bool crypto_sha256_eq(const uint8_t *cksum1, const uint8_t *cksum2) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + // Hope that this is better for the fuzzer + return memcmp(cksum1, cksum2, CRYPTO_SHA256_SIZE) == 0; +#else + return crypto_verify_32(cksum1, cksum2) == 0; +#endif +} + +uint8_t random_u08(const Random *rng) +{ + uint8_t randnum; + random_bytes(rng, &randnum, 1); + return randnum; +} + +uint16_t random_u16(const Random *rng) +{ + uint16_t randnum; + random_bytes(rng, (uint8_t *)&randnum, sizeof(randnum)); + return randnum; +} + +uint32_t random_u32(const Random *rng) +{ + uint32_t randnum; + random_bytes(rng, (uint8_t *)&randnum, sizeof(randnum)); + return randnum; +} + +uint64_t random_u64(const Random *rng) +{ + uint64_t randnum; + random_bytes(rng, (uint8_t *)&randnum, sizeof(randnum)); + return randnum; +} + +uint32_t random_range_u32(const Random *rng, uint32_t upper_bound) +{ + return rng->funcs->random_uniform(rng->obj, upper_bound); +} + +bool crypto_signature_create(uint8_t *signature, const uint8_t *message, uint64_t message_length, + const uint8_t *secret_key) +{ +#ifdef VANILLA_NACL + return false; +#else + return crypto_sign_detached(signature, nullptr, message, message_length, secret_key) == 0; +#endif // VANILLA_NACL +} + +bool crypto_signature_verify(const uint8_t *signature, const uint8_t *message, uint64_t message_length, + const uint8_t *public_key) +{ +#ifdef VANILLA_NACL + return false; +#else + return crypto_sign_verify_detached(signature, message, message_length, public_key) == 0; +#endif +} + +bool public_key_valid(const uint8_t *public_key) +{ + /* Last bit of key is always zero. */ + return public_key[31] < 128; +} + +int32_t encrypt_precompute(const uint8_t *public_key, const uint8_t *secret_key, + uint8_t *shared_key) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + memcpy(shared_key, public_key, CRYPTO_SHARED_KEY_SIZE); + return 0; +#else + return crypto_box_beforenm(shared_key, public_key, secret_key); +#endif +} + +int32_t encrypt_data_symmetric(const uint8_t *shared_key, const uint8_t *nonce, + const uint8_t *plain, size_t length, uint8_t *encrypted) +{ + if (length == 0 || shared_key == nullptr || nonce == nullptr || plain == nullptr || encrypted == nullptr) { + return -1; + } + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + // Don't encrypt anything. + memcpy(encrypted, plain, length); + // Zero MAC to avoid uninitialized memory reads. + memset(encrypted + length, 0, crypto_box_MACBYTES); +#else + + const size_t size_temp_plain = length + crypto_box_ZEROBYTES; + const size_t size_temp_encrypted = length + crypto_box_MACBYTES + crypto_box_BOXZEROBYTES; + + uint8_t *temp_plain = crypto_malloc(size_temp_plain); + uint8_t *temp_encrypted = crypto_malloc(size_temp_encrypted); + + if (temp_plain == nullptr || temp_encrypted == nullptr) { + crypto_free(temp_plain, size_temp_plain); + crypto_free(temp_encrypted, size_temp_encrypted); + return -1; + } + + // crypto_box_afternm requires the entire range of the output array be + // initialised with something. It doesn't matter what it's initialised with, + // so we'll pick 0x00. + memset(temp_encrypted, 0, size_temp_encrypted); + + memset(temp_plain, 0, crypto_box_ZEROBYTES); + // Pad the message with 32 0 bytes. + memcpy(temp_plain + crypto_box_ZEROBYTES, plain, length); + + if (crypto_box_afternm(temp_encrypted, temp_plain, length + crypto_box_ZEROBYTES, nonce, + shared_key) != 0) { + crypto_free(temp_plain, size_temp_plain); + crypto_free(temp_encrypted, size_temp_encrypted); + return -1; + } + + // Unpad the encrypted message. + memcpy(encrypted, temp_encrypted + crypto_box_BOXZEROBYTES, length + crypto_box_MACBYTES); + + crypto_free(temp_plain, size_temp_plain); + crypto_free(temp_encrypted, size_temp_encrypted); +#endif + assert(length < INT32_MAX - crypto_box_MACBYTES); + return (int32_t)(length + crypto_box_MACBYTES); +} + +int32_t decrypt_data_symmetric(const uint8_t *shared_key, const uint8_t *nonce, + const uint8_t *encrypted, size_t length, uint8_t *plain) +{ + if (length <= crypto_box_BOXZEROBYTES || shared_key == nullptr || nonce == nullptr || encrypted == nullptr + || plain == nullptr) { + return -1; + } + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + assert(length >= crypto_box_MACBYTES); + memcpy(plain, encrypted, length - crypto_box_MACBYTES); // Don't encrypt anything +#else + + const size_t size_temp_plain = length + crypto_box_ZEROBYTES; + const size_t size_temp_encrypted = length + crypto_box_BOXZEROBYTES; + + uint8_t *temp_plain = crypto_malloc(size_temp_plain); + uint8_t *temp_encrypted = crypto_malloc(size_temp_encrypted); + + if (temp_plain == nullptr || temp_encrypted == nullptr) { + crypto_free(temp_plain, size_temp_plain); + crypto_free(temp_encrypted, size_temp_encrypted); + return -1; + } + + // crypto_box_open_afternm requires the entire range of the output array be + // initialised with something. It doesn't matter what it's initialised with, + // so we'll pick 0x00. + memset(temp_plain, 0, size_temp_plain); + + memset(temp_encrypted, 0, crypto_box_BOXZEROBYTES); + // Pad the message with 16 0 bytes. + memcpy(temp_encrypted + crypto_box_BOXZEROBYTES, encrypted, length); + + if (crypto_box_open_afternm(temp_plain, temp_encrypted, length + crypto_box_BOXZEROBYTES, nonce, + shared_key) != 0) { + crypto_free(temp_plain, size_temp_plain); + crypto_free(temp_encrypted, size_temp_encrypted); + return -1; + } + + memcpy(plain, temp_plain + crypto_box_ZEROBYTES, length - crypto_box_MACBYTES); + + crypto_free(temp_plain, size_temp_plain); + crypto_free(temp_encrypted, size_temp_encrypted); +#endif + assert(length > crypto_box_MACBYTES); + assert(length < INT32_MAX); + return (int32_t)(length - crypto_box_MACBYTES); +} + +int32_t encrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, + const uint8_t *plain, size_t length, uint8_t *encrypted) +{ + if (public_key == nullptr || secret_key == nullptr) { + return -1; + } + + uint8_t k[crypto_box_BEFORENMBYTES]; + encrypt_precompute(public_key, secret_key, k); + const int ret = encrypt_data_symmetric(k, nonce, plain, length, encrypted); + crypto_memzero(k, sizeof(k)); + return ret; +} + +int32_t decrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, + const uint8_t *encrypted, size_t length, uint8_t *plain) +{ + if (public_key == nullptr || secret_key == nullptr) { + return -1; + } + + uint8_t k[crypto_box_BEFORENMBYTES]; + encrypt_precompute(public_key, secret_key, k); + const int ret = decrypt_data_symmetric(k, nonce, encrypted, length, plain); + crypto_memzero(k, sizeof(k)); + return ret; +} + +void increment_nonce(uint8_t *nonce) +{ + /* TODO(irungentoo): use `increment_nonce_number(nonce, 1)` or + * sodium_increment (change to little endian). + * + * NOTE don't use breaks inside this loop. + * In particular, make sure, as far as possible, + * that loop bounds and their potential underflow or overflow + * are independent of user-controlled input (you may have heard of the Heartbleed bug). + */ + uint_fast16_t carry = 1U; + + for (uint32_t i = crypto_box_NONCEBYTES; i != 0; --i) { + carry += (uint_fast16_t)nonce[i - 1]; + nonce[i - 1] = (uint8_t)carry; + carry >>= 8; + } +} + +void increment_nonce_number(uint8_t *nonce, uint32_t increment) +{ + /* NOTE don't use breaks inside this loop + * In particular, make sure, as far as possible, + * that loop bounds and their potential underflow or overflow + * are independent of user-controlled input (you may have heard of the Heartbleed bug). + */ + uint8_t num_as_nonce[crypto_box_NONCEBYTES] = {0}; + num_as_nonce[crypto_box_NONCEBYTES - 4] = increment >> 24; + num_as_nonce[crypto_box_NONCEBYTES - 3] = increment >> 16; + num_as_nonce[crypto_box_NONCEBYTES - 2] = increment >> 8; + num_as_nonce[crypto_box_NONCEBYTES - 1] = increment; + + uint_fast16_t carry = 0U; + + for (uint32_t i = crypto_box_NONCEBYTES; i != 0; --i) { + carry += (uint_fast16_t)nonce[i - 1] + (uint_fast16_t)num_as_nonce[i - 1]; + nonce[i - 1] = (uint8_t)carry; + carry >>= 8; + } +} + +void random_nonce(const Random *rng, uint8_t *nonce) +{ + random_bytes(rng, nonce, crypto_box_NONCEBYTES); +} + +void new_symmetric_key_implicit_random(uint8_t *key) +{ + randombytes(key, CRYPTO_SYMMETRIC_KEY_SIZE); +} + +void new_symmetric_key(const Random *rng, uint8_t *key) +{ + random_bytes(rng, key, CRYPTO_SYMMETRIC_KEY_SIZE); +} + +int32_t crypto_new_keypair(const Random *rng, uint8_t *public_key, uint8_t *secret_key) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + random_bytes(rng, secret_key, CRYPTO_SECRET_KEY_SIZE); + memset(public_key, 0, CRYPTO_PUBLIC_KEY_SIZE); // Make MSAN happy + crypto_scalarmult_curve25519_base(public_key, secret_key); + return 0; +#else + return crypto_box_keypair(public_key, secret_key); +#endif +} + +void crypto_derive_public_key(uint8_t *public_key, const uint8_t *secret_key) +{ + crypto_scalarmult_curve25519_base(public_key, secret_key); +} + +void new_hmac_key(const Random *rng, uint8_t *key) +{ + random_bytes(rng, key, CRYPTO_HMAC_KEY_SIZE); +} + +void crypto_hmac(uint8_t auth[CRYPTO_HMAC_SIZE], const uint8_t key[CRYPTO_HMAC_KEY_SIZE], const uint8_t *data, + size_t length) +{ + crypto_auth(auth, data, length, key); +} + +bool crypto_hmac_verify(const uint8_t auth[CRYPTO_HMAC_SIZE], const uint8_t key[CRYPTO_HMAC_KEY_SIZE], + const uint8_t *data, size_t length) +{ + return crypto_auth_verify(auth, data, length, key) == 0; +} + +void crypto_sha256(uint8_t *hash, const uint8_t *data, size_t length) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + memset(hash, 0, CRYPTO_SHA256_SIZE); + memcpy(hash, data, length < CRYPTO_SHA256_SIZE ? length : CRYPTO_SHA256_SIZE); +#else + crypto_hash_sha256(hash, data, length); +#endif +} + +void crypto_sha512(uint8_t *hash, const uint8_t *data, size_t length) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + memset(hash, 0, CRYPTO_SHA512_SIZE); + memcpy(hash, data, length < CRYPTO_SHA512_SIZE ? length : CRYPTO_SHA512_SIZE); +#else + crypto_hash_sha512(hash, data, length); +#endif +} + +non_null() +static void sys_random_bytes(void *obj, uint8_t *bytes, size_t length) +{ + randombytes(bytes, length); +} + +non_null() +static uint32_t sys_random_uniform(void *obj, uint32_t upper_bound) +{ +#ifdef VANILLA_NACL + if (upper_bound == 0) { + return 0; + } + + uint32_t randnum; + sys_random_bytes(obj, (uint8_t *)&randnum, sizeof(randnum)); + return randnum % upper_bound; +#else + return randombytes_uniform(upper_bound); +#endif +} + +static const Random_Funcs system_random_funcs = { + sys_random_bytes, + sys_random_uniform, +}; + +static const Random system_random_obj = {&system_random_funcs}; + +const Random *system_random(void) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if ((true)) { + return nullptr; + } +#endif +#ifndef VANILLA_NACL + // It is safe to call this function more than once and from different + // threads -- subsequent calls won't have any effects. + if (sodium_init() == -1) { + return nullptr; + } +#endif + return &system_random_obj; +} + +void random_bytes(const Random *rng, uint8_t *bytes, size_t length) +{ + rng->funcs->random_bytes(rng->obj, bytes, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/conference_connected.m b/local_pod_repo/toxcore/toxcore/toxcore/events/conference_connected.m new file mode 100644 index 0000000..ce27875 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/conference_connected.m @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Conference_Connected { + uint32_t conference_number; +}; + +non_null() +static void tox_event_conference_connected_construct(Tox_Event_Conference_Connected *conference_connected) +{ + *conference_connected = (Tox_Event_Conference_Connected) { + 0 + }; +} +non_null() +static void tox_event_conference_connected_destruct(Tox_Event_Conference_Connected *conference_connected) +{ + return; +} + +non_null() +static void tox_event_conference_connected_set_conference_number( + Tox_Event_Conference_Connected *conference_connected, uint32_t conference_number) +{ + assert(conference_connected != nullptr); + conference_connected->conference_number = conference_number; +} +uint32_t tox_event_conference_connected_get_conference_number( + const Tox_Event_Conference_Connected *conference_connected) +{ + assert(conference_connected != nullptr); + return conference_connected->conference_number; +} + +non_null() +static bool tox_event_conference_connected_pack( + const Tox_Event_Conference_Connected *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_CONFERENCE_CONNECTED) + && bin_pack_u32(bp, event->conference_number); +} + +non_null() +static bool tox_event_conference_connected_unpack( + Tox_Event_Conference_Connected *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + return bin_unpack_u32(bu, &event->conference_number); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Conference_Connected *tox_events_add_conference_connected(Tox_Events *events) +{ + if (events->conference_connected_size == UINT32_MAX) { + return nullptr; + } + + if (events->conference_connected_size == events->conference_connected_capacity) { + const uint32_t new_conference_connected_capacity = events->conference_connected_capacity * 2 + 1; + Tox_Event_Conference_Connected *new_conference_connected = (Tox_Event_Conference_Connected *)realloc( + events->conference_connected, new_conference_connected_capacity * sizeof(Tox_Event_Conference_Connected)); + + if (new_conference_connected == nullptr) { + return nullptr; + } + + events->conference_connected = new_conference_connected; + events->conference_connected_capacity = new_conference_connected_capacity; + } + + Tox_Event_Conference_Connected *const conference_connected = + &events->conference_connected[events->conference_connected_size]; + tox_event_conference_connected_construct(conference_connected); + ++events->conference_connected_size; + return conference_connected; +} + +void tox_events_clear_conference_connected(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->conference_connected_size; ++i) { + tox_event_conference_connected_destruct(&events->conference_connected[i]); + } + + free(events->conference_connected); + events->conference_connected = nullptr; + events->conference_connected_size = 0; + events->conference_connected_capacity = 0; +} + +uint32_t tox_events_get_conference_connected_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->conference_connected_size; +} + +const Tox_Event_Conference_Connected *tox_events_get_conference_connected(const Tox_Events *events, uint32_t index) +{ + assert(index < events->conference_connected_size); + assert(events->conference_connected != nullptr); + return &events->conference_connected[index]; +} + +bool tox_events_pack_conference_connected(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_conference_connected_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_conference_connected_pack(tox_events_get_conference_connected(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_conference_connected(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Conference_Connected *event = tox_events_add_conference_connected(events); + + if (event == nullptr) { + return false; + } + + return tox_event_conference_connected_unpack(event, bu); +} + + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_conference_connected(Tox *tox, uint32_t conference_number, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Conference_Connected *conference_connected = tox_events_add_conference_connected(state->events); + + if (conference_connected == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_conference_connected_set_conference_number(conference_connected, conference_number); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/conference_invite.m b/local_pod_repo/toxcore/toxcore/toxcore/events/conference_invite.m new file mode 100644 index 0000000..1d88f41 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/conference_invite.m @@ -0,0 +1,249 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" +#include "../tox_unpack.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Conference_Invite { + uint32_t friend_number; + Tox_Conference_Type type; + uint8_t *cookie; + uint32_t cookie_length; +}; + +non_null() +static void tox_event_conference_invite_construct(Tox_Event_Conference_Invite *conference_invite) +{ + *conference_invite = (Tox_Event_Conference_Invite) { + 0 + }; +} +non_null() +static void tox_event_conference_invite_destruct(Tox_Event_Conference_Invite *conference_invite) +{ + free(conference_invite->cookie); +} + +non_null() +static void tox_event_conference_invite_set_friend_number(Tox_Event_Conference_Invite *conference_invite, + uint32_t friend_number) +{ + assert(conference_invite != nullptr); + conference_invite->friend_number = friend_number; +} +uint32_t tox_event_conference_invite_get_friend_number(const Tox_Event_Conference_Invite *conference_invite) +{ + assert(conference_invite != nullptr); + return conference_invite->friend_number; +} + +non_null() +static void tox_event_conference_invite_set_type(Tox_Event_Conference_Invite *conference_invite, + Tox_Conference_Type type) +{ + assert(conference_invite != nullptr); + conference_invite->type = type; +} +Tox_Conference_Type tox_event_conference_invite_get_type(const Tox_Event_Conference_Invite *conference_invite) +{ + assert(conference_invite != nullptr); + return conference_invite->type; +} + +non_null() +static bool tox_event_conference_invite_set_cookie(Tox_Event_Conference_Invite *conference_invite, + const uint8_t *cookie, uint32_t cookie_length) +{ + assert(conference_invite != nullptr); + + if (conference_invite->cookie != nullptr) { + free(conference_invite->cookie); + conference_invite->cookie = nullptr; + conference_invite->cookie_length = 0; + } + + conference_invite->cookie = (uint8_t *)malloc(cookie_length); + + if (conference_invite->cookie == nullptr) { + return false; + } + + memcpy(conference_invite->cookie, cookie, cookie_length); + conference_invite->cookie_length = cookie_length; + return true; +} +uint32_t tox_event_conference_invite_get_cookie_length(const Tox_Event_Conference_Invite *conference_invite) +{ + assert(conference_invite != nullptr); + return conference_invite->cookie_length; +} +const uint8_t *tox_event_conference_invite_get_cookie(const Tox_Event_Conference_Invite *conference_invite) +{ + assert(conference_invite != nullptr); + return conference_invite->cookie; +} + +non_null() +static bool tox_event_conference_invite_pack( + const Tox_Event_Conference_Invite *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_CONFERENCE_INVITE) + && bin_pack_array(bp, 3) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_u32(bp, event->type) + && bin_pack_bin(bp, event->cookie, event->cookie_length); +} + +non_null() +static bool tox_event_conference_invite_unpack( + Tox_Event_Conference_Invite *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 3)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && tox_unpack_conference_type(bu, &event->type) + && bin_unpack_bin(bu, &event->cookie, &event->cookie_length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Conference_Invite *tox_events_add_conference_invite(Tox_Events *events) +{ + if (events->conference_invite_size == UINT32_MAX) { + return nullptr; + } + + if (events->conference_invite_size == events->conference_invite_capacity) { + const uint32_t new_conference_invite_capacity = events->conference_invite_capacity * 2 + 1; + Tox_Event_Conference_Invite *new_conference_invite = (Tox_Event_Conference_Invite *)realloc( + events->conference_invite, new_conference_invite_capacity * sizeof(Tox_Event_Conference_Invite)); + + if (new_conference_invite == nullptr) { + return nullptr; + } + + events->conference_invite = new_conference_invite; + events->conference_invite_capacity = new_conference_invite_capacity; + } + + Tox_Event_Conference_Invite *const conference_invite = &events->conference_invite[events->conference_invite_size]; + tox_event_conference_invite_construct(conference_invite); + ++events->conference_invite_size; + return conference_invite; +} + +void tox_events_clear_conference_invite(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->conference_invite_size; ++i) { + tox_event_conference_invite_destruct(&events->conference_invite[i]); + } + + free(events->conference_invite); + events->conference_invite = nullptr; + events->conference_invite_size = 0; + events->conference_invite_capacity = 0; +} + +uint32_t tox_events_get_conference_invite_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->conference_invite_size; +} + +const Tox_Event_Conference_Invite *tox_events_get_conference_invite(const Tox_Events *events, uint32_t index) +{ + assert(index < events->conference_invite_size); + assert(events->conference_invite != nullptr); + return &events->conference_invite[index]; +} + +bool tox_events_pack_conference_invite(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_conference_invite_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_conference_invite_pack(tox_events_get_conference_invite(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_conference_invite(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Conference_Invite *event = tox_events_add_conference_invite(events); + + if (event == nullptr) { + return false; + } + + return tox_event_conference_invite_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_conference_invite(Tox *tox, uint32_t friend_number, Tox_Conference_Type type, + const uint8_t *cookie, size_t length, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Conference_Invite *conference_invite = tox_events_add_conference_invite(state->events); + + if (conference_invite == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_conference_invite_set_friend_number(conference_invite, friend_number); + tox_event_conference_invite_set_type(conference_invite, type); + tox_event_conference_invite_set_cookie(conference_invite, cookie, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/conference_message.m b/local_pod_repo/toxcore/toxcore/toxcore/events/conference_message.m new file mode 100644 index 0000000..bc9a19b --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/conference_message.m @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" +#include "../tox_unpack.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Conference_Message { + uint32_t conference_number; + uint32_t peer_number; + Tox_Message_Type type; + uint8_t *message; + uint32_t message_length; +}; + +non_null() +static void tox_event_conference_message_construct(Tox_Event_Conference_Message *conference_message) +{ + *conference_message = (Tox_Event_Conference_Message) { + 0 + }; +} +non_null() +static void tox_event_conference_message_destruct(Tox_Event_Conference_Message *conference_message) +{ + free(conference_message->message); +} + +non_null() +static void tox_event_conference_message_set_conference_number(Tox_Event_Conference_Message *conference_message, + uint32_t conference_number) +{ + assert(conference_message != nullptr); + conference_message->conference_number = conference_number; +} +uint32_t tox_event_conference_message_get_conference_number(const Tox_Event_Conference_Message *conference_message) +{ + assert(conference_message != nullptr); + return conference_message->conference_number; +} + +non_null() +static void tox_event_conference_message_set_peer_number(Tox_Event_Conference_Message *conference_message, + uint32_t peer_number) +{ + assert(conference_message != nullptr); + conference_message->peer_number = peer_number; +} +uint32_t tox_event_conference_message_get_peer_number(const Tox_Event_Conference_Message *conference_message) +{ + assert(conference_message != nullptr); + return conference_message->peer_number; +} + +non_null() +static void tox_event_conference_message_set_type(Tox_Event_Conference_Message *conference_message, + Tox_Message_Type type) +{ + assert(conference_message != nullptr); + conference_message->type = type; +} +Tox_Message_Type tox_event_conference_message_get_type(const Tox_Event_Conference_Message *conference_message) +{ + assert(conference_message != nullptr); + return conference_message->type; +} + +non_null() +static bool tox_event_conference_message_set_message(Tox_Event_Conference_Message *conference_message, + const uint8_t *message, uint32_t message_length) +{ + assert(conference_message != nullptr); + + if (conference_message->message != nullptr) { + free(conference_message->message); + conference_message->message = nullptr; + conference_message->message_length = 0; + } + + conference_message->message = (uint8_t *)malloc(message_length); + + if (conference_message->message == nullptr) { + return false; + } + + memcpy(conference_message->message, message, message_length); + conference_message->message_length = message_length; + return true; +} +uint32_t tox_event_conference_message_get_message_length(const Tox_Event_Conference_Message *conference_message) +{ + assert(conference_message != nullptr); + return conference_message->message_length; +} +const uint8_t *tox_event_conference_message_get_message(const Tox_Event_Conference_Message *conference_message) +{ + assert(conference_message != nullptr); + return conference_message->message; +} + +non_null() +static bool tox_event_conference_message_pack( + const Tox_Event_Conference_Message *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_CONFERENCE_MESSAGE) + && bin_pack_array(bp, 4) + && bin_pack_u32(bp, event->conference_number) + && bin_pack_u32(bp, event->peer_number) + && bin_pack_u32(bp, event->type) + && bin_pack_bin(bp, event->message, event->message_length); +} + +non_null() +static bool tox_event_conference_message_unpack( + Tox_Event_Conference_Message *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 4)) { + return false; + } + + return bin_unpack_u32(bu, &event->conference_number) + && bin_unpack_u32(bu, &event->peer_number) + && tox_unpack_message_type(bu, &event->type) + && bin_unpack_bin(bu, &event->message, &event->message_length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Conference_Message *tox_events_add_conference_message(Tox_Events *events) +{ + if (events->conference_message_size == UINT32_MAX) { + return nullptr; + } + + if (events->conference_message_size == events->conference_message_capacity) { + const uint32_t new_conference_message_capacity = events->conference_message_capacity * 2 + 1; + Tox_Event_Conference_Message *new_conference_message = (Tox_Event_Conference_Message *)realloc( + events->conference_message, new_conference_message_capacity * sizeof(Tox_Event_Conference_Message)); + + if (new_conference_message == nullptr) { + return nullptr; + } + + events->conference_message = new_conference_message; + events->conference_message_capacity = new_conference_message_capacity; + } + + Tox_Event_Conference_Message *const conference_message = &events->conference_message[events->conference_message_size]; + tox_event_conference_message_construct(conference_message); + ++events->conference_message_size; + return conference_message; +} + +void tox_events_clear_conference_message(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->conference_message_size; ++i) { + tox_event_conference_message_destruct(&events->conference_message[i]); + } + + free(events->conference_message); + events->conference_message = nullptr; + events->conference_message_size = 0; + events->conference_message_capacity = 0; +} + +uint32_t tox_events_get_conference_message_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->conference_message_size; +} + +const Tox_Event_Conference_Message *tox_events_get_conference_message(const Tox_Events *events, uint32_t index) +{ + assert(index < events->conference_message_size); + assert(events->conference_message != nullptr); + return &events->conference_message[index]; +} + +bool tox_events_pack_conference_message(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_conference_message_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_conference_message_pack(tox_events_get_conference_message(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_conference_message(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Conference_Message *event = tox_events_add_conference_message(events); + + if (event == nullptr) { + return false; + } + + return tox_event_conference_message_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_conference_message(Tox *tox, uint32_t conference_number, uint32_t peer_number, + Tox_Message_Type type, const uint8_t *message, size_t length, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Conference_Message *conference_message = tox_events_add_conference_message(state->events); + + if (conference_message == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_conference_message_set_conference_number(conference_message, conference_number); + tox_event_conference_message_set_peer_number(conference_message, peer_number); + tox_event_conference_message_set_type(conference_message, type); + tox_event_conference_message_set_message(conference_message, message, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/conference_peer_list_changed.m b/local_pod_repo/toxcore/toxcore/toxcore/events/conference_peer_list_changed.m new file mode 100644 index 0000000..b6aaa3b --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/conference_peer_list_changed.m @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Conference_Peer_List_Changed { + uint32_t conference_number; +}; + +non_null() +static void tox_event_conference_peer_list_changed_construct(Tox_Event_Conference_Peer_List_Changed + *conference_peer_list_changed) +{ + *conference_peer_list_changed = (Tox_Event_Conference_Peer_List_Changed) { + 0 + }; +} +non_null() +static void tox_event_conference_peer_list_changed_destruct(Tox_Event_Conference_Peer_List_Changed + *conference_peer_list_changed) +{ + return; +} + +non_null() +static void tox_event_conference_peer_list_changed_set_conference_number(Tox_Event_Conference_Peer_List_Changed + *conference_peer_list_changed, uint32_t conference_number) +{ + assert(conference_peer_list_changed != nullptr); + conference_peer_list_changed->conference_number = conference_number; +} +uint32_t tox_event_conference_peer_list_changed_get_conference_number(const Tox_Event_Conference_Peer_List_Changed + *conference_peer_list_changed) +{ + assert(conference_peer_list_changed != nullptr); + return conference_peer_list_changed->conference_number; +} + +non_null() +static bool tox_event_conference_peer_list_changed_pack( + const Tox_Event_Conference_Peer_List_Changed *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_CONFERENCE_PEER_LIST_CHANGED) + && bin_pack_u32(bp, event->conference_number); +} + +non_null() +static bool tox_event_conference_peer_list_changed_unpack( + Tox_Event_Conference_Peer_List_Changed *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + return bin_unpack_u32(bu, &event->conference_number); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Conference_Peer_List_Changed *tox_events_add_conference_peer_list_changed(Tox_Events *events) +{ + if (events->conference_peer_list_changed_size == UINT32_MAX) { + return nullptr; + } + + if (events->conference_peer_list_changed_size == events->conference_peer_list_changed_capacity) { + const uint32_t new_conference_peer_list_changed_capacity = events->conference_peer_list_changed_capacity * 2 + 1; + Tox_Event_Conference_Peer_List_Changed *new_conference_peer_list_changed = (Tox_Event_Conference_Peer_List_Changed *) + realloc( + events->conference_peer_list_changed, + new_conference_peer_list_changed_capacity * sizeof(Tox_Event_Conference_Peer_List_Changed)); + + if (new_conference_peer_list_changed == nullptr) { + return nullptr; + } + + events->conference_peer_list_changed = new_conference_peer_list_changed; + events->conference_peer_list_changed_capacity = new_conference_peer_list_changed_capacity; + } + + Tox_Event_Conference_Peer_List_Changed *const conference_peer_list_changed = + &events->conference_peer_list_changed[events->conference_peer_list_changed_size]; + tox_event_conference_peer_list_changed_construct(conference_peer_list_changed); + ++events->conference_peer_list_changed_size; + return conference_peer_list_changed; +} + +void tox_events_clear_conference_peer_list_changed(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->conference_peer_list_changed_size; ++i) { + tox_event_conference_peer_list_changed_destruct(&events->conference_peer_list_changed[i]); + } + + free(events->conference_peer_list_changed); + events->conference_peer_list_changed = nullptr; + events->conference_peer_list_changed_size = 0; + events->conference_peer_list_changed_capacity = 0; +} + +uint32_t tox_events_get_conference_peer_list_changed_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->conference_peer_list_changed_size; +} + +const Tox_Event_Conference_Peer_List_Changed *tox_events_get_conference_peer_list_changed(const Tox_Events *events, + uint32_t index) +{ + assert(index < events->conference_peer_list_changed_size); + assert(events->conference_peer_list_changed != nullptr); + return &events->conference_peer_list_changed[index]; +} + +bool tox_events_pack_conference_peer_list_changed(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_conference_peer_list_changed_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_conference_peer_list_changed_pack(tox_events_get_conference_peer_list_changed(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_conference_peer_list_changed(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Conference_Peer_List_Changed *event = tox_events_add_conference_peer_list_changed(events); + + if (event == nullptr) { + return false; + } + + return tox_event_conference_peer_list_changed_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_conference_peer_list_changed(Tox *tox, uint32_t conference_number, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Conference_Peer_List_Changed *conference_peer_list_changed = tox_events_add_conference_peer_list_changed( + state->events); + + if (conference_peer_list_changed == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_conference_peer_list_changed_set_conference_number(conference_peer_list_changed, conference_number); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/conference_peer_name.m b/local_pod_repo/toxcore/toxcore/toxcore/events/conference_peer_name.m new file mode 100644 index 0000000..4d3f285 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/conference_peer_name.m @@ -0,0 +1,250 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Conference_Peer_Name { + uint32_t conference_number; + uint32_t peer_number; + uint8_t *name; + uint32_t name_length; +}; + +non_null() +static void tox_event_conference_peer_name_construct(Tox_Event_Conference_Peer_Name *conference_peer_name) +{ + *conference_peer_name = (Tox_Event_Conference_Peer_Name) { + 0 + }; +} +non_null() +static void tox_event_conference_peer_name_destruct(Tox_Event_Conference_Peer_Name *conference_peer_name) +{ + free(conference_peer_name->name); +} + +non_null() +static void tox_event_conference_peer_name_set_conference_number(Tox_Event_Conference_Peer_Name *conference_peer_name, + uint32_t conference_number) +{ + assert(conference_peer_name != nullptr); + conference_peer_name->conference_number = conference_number; +} +uint32_t tox_event_conference_peer_name_get_conference_number(const Tox_Event_Conference_Peer_Name + *conference_peer_name) +{ + assert(conference_peer_name != nullptr); + return conference_peer_name->conference_number; +} + +non_null() +static void tox_event_conference_peer_name_set_peer_number(Tox_Event_Conference_Peer_Name *conference_peer_name, + uint32_t peer_number) +{ + assert(conference_peer_name != nullptr); + conference_peer_name->peer_number = peer_number; +} +uint32_t tox_event_conference_peer_name_get_peer_number(const Tox_Event_Conference_Peer_Name *conference_peer_name) +{ + assert(conference_peer_name != nullptr); + return conference_peer_name->peer_number; +} + +non_null() +static bool tox_event_conference_peer_name_set_name(Tox_Event_Conference_Peer_Name *conference_peer_name, + const uint8_t *name, uint32_t name_length) +{ + assert(conference_peer_name != nullptr); + + if (conference_peer_name->name != nullptr) { + free(conference_peer_name->name); + conference_peer_name->name = nullptr; + conference_peer_name->name_length = 0; + } + + conference_peer_name->name = (uint8_t *)malloc(name_length); + + if (conference_peer_name->name == nullptr) { + return false; + } + + memcpy(conference_peer_name->name, name, name_length); + conference_peer_name->name_length = name_length; + return true; +} +uint32_t tox_event_conference_peer_name_get_name_length(const Tox_Event_Conference_Peer_Name *conference_peer_name) +{ + assert(conference_peer_name != nullptr); + return conference_peer_name->name_length; +} +const uint8_t *tox_event_conference_peer_name_get_name(const Tox_Event_Conference_Peer_Name *conference_peer_name) +{ + assert(conference_peer_name != nullptr); + return conference_peer_name->name; +} + +non_null() +static bool tox_event_conference_peer_name_pack( + const Tox_Event_Conference_Peer_Name *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_CONFERENCE_PEER_NAME) + && bin_pack_array(bp, 3) + && bin_pack_u32(bp, event->conference_number) + && bin_pack_u32(bp, event->peer_number) + && bin_pack_bin(bp, event->name, event->name_length); +} + +non_null() +static bool tox_event_conference_peer_name_unpack( + Tox_Event_Conference_Peer_Name *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 3)) { + return false; + } + + return bin_unpack_u32(bu, &event->conference_number) + && bin_unpack_u32(bu, &event->peer_number) + && bin_unpack_bin(bu, &event->name, &event->name_length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Conference_Peer_Name *tox_events_add_conference_peer_name(Tox_Events *events) +{ + if (events->conference_peer_name_size == UINT32_MAX) { + return nullptr; + } + + if (events->conference_peer_name_size == events->conference_peer_name_capacity) { + const uint32_t new_conference_peer_name_capacity = events->conference_peer_name_capacity * 2 + 1; + Tox_Event_Conference_Peer_Name *new_conference_peer_name = (Tox_Event_Conference_Peer_Name *)realloc( + events->conference_peer_name, new_conference_peer_name_capacity * sizeof(Tox_Event_Conference_Peer_Name)); + + if (new_conference_peer_name == nullptr) { + return nullptr; + } + + events->conference_peer_name = new_conference_peer_name; + events->conference_peer_name_capacity = new_conference_peer_name_capacity; + } + + Tox_Event_Conference_Peer_Name *const conference_peer_name = + &events->conference_peer_name[events->conference_peer_name_size]; + tox_event_conference_peer_name_construct(conference_peer_name); + ++events->conference_peer_name_size; + return conference_peer_name; +} + +void tox_events_clear_conference_peer_name(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->conference_peer_name_size; ++i) { + tox_event_conference_peer_name_destruct(&events->conference_peer_name[i]); + } + + free(events->conference_peer_name); + events->conference_peer_name = nullptr; + events->conference_peer_name_size = 0; + events->conference_peer_name_capacity = 0; +} + +uint32_t tox_events_get_conference_peer_name_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->conference_peer_name_size; +} + +const Tox_Event_Conference_Peer_Name *tox_events_get_conference_peer_name(const Tox_Events *events, uint32_t index) +{ + assert(index < events->conference_peer_name_size); + assert(events->conference_peer_name != nullptr); + return &events->conference_peer_name[index]; +} + +bool tox_events_pack_conference_peer_name(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_conference_peer_name_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_conference_peer_name_pack(tox_events_get_conference_peer_name(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_conference_peer_name(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Conference_Peer_Name *event = tox_events_add_conference_peer_name(events); + + if (event == nullptr) { + return false; + } + + return tox_event_conference_peer_name_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_conference_peer_name(Tox *tox, uint32_t conference_number, uint32_t peer_number, + const uint8_t *name, size_t length, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Conference_Peer_Name *conference_peer_name = tox_events_add_conference_peer_name(state->events); + + if (conference_peer_name == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_conference_peer_name_set_conference_number(conference_peer_name, conference_number); + tox_event_conference_peer_name_set_peer_number(conference_peer_name, peer_number); + tox_event_conference_peer_name_set_name(conference_peer_name, name, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/conference_title.m b/local_pod_repo/toxcore/toxcore/toxcore/events/conference_title.m new file mode 100644 index 0000000..1866966 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/conference_title.m @@ -0,0 +1,248 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Conference_Title { + uint32_t conference_number; + uint32_t peer_number; + uint8_t *title; + uint32_t title_length; +}; + +non_null() +static void tox_event_conference_title_construct(Tox_Event_Conference_Title *conference_title) +{ + *conference_title = (Tox_Event_Conference_Title) { + 0 + }; +} +non_null() +static void tox_event_conference_title_destruct(Tox_Event_Conference_Title *conference_title) +{ + free(conference_title->title); +} + +non_null() +static void tox_event_conference_title_set_conference_number(Tox_Event_Conference_Title *conference_title, + uint32_t conference_number) +{ + assert(conference_title != nullptr); + conference_title->conference_number = conference_number; +} +uint32_t tox_event_conference_title_get_conference_number(const Tox_Event_Conference_Title *conference_title) +{ + assert(conference_title != nullptr); + return conference_title->conference_number; +} + +non_null() +static void tox_event_conference_title_set_peer_number(Tox_Event_Conference_Title *conference_title, + uint32_t peer_number) +{ + assert(conference_title != nullptr); + conference_title->peer_number = peer_number; +} +uint32_t tox_event_conference_title_get_peer_number(const Tox_Event_Conference_Title *conference_title) +{ + assert(conference_title != nullptr); + return conference_title->peer_number; +} + +non_null() +static bool tox_event_conference_title_set_title(Tox_Event_Conference_Title *conference_title, const uint8_t *title, + uint32_t title_length) +{ + assert(conference_title != nullptr); + + if (conference_title->title != nullptr) { + free(conference_title->title); + conference_title->title = nullptr; + conference_title->title_length = 0; + } + + conference_title->title = (uint8_t *)malloc(title_length); + + if (conference_title->title == nullptr) { + return false; + } + + memcpy(conference_title->title, title, title_length); + conference_title->title_length = title_length; + return true; +} +uint32_t tox_event_conference_title_get_title_length(const Tox_Event_Conference_Title *conference_title) +{ + assert(conference_title != nullptr); + return conference_title->title_length; +} +const uint8_t *tox_event_conference_title_get_title(const Tox_Event_Conference_Title *conference_title) +{ + assert(conference_title != nullptr); + return conference_title->title; +} + +non_null() +static bool tox_event_conference_title_pack( + const Tox_Event_Conference_Title *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_CONFERENCE_TITLE) + && bin_pack_array(bp, 3) + && bin_pack_u32(bp, event->conference_number) + && bin_pack_u32(bp, event->peer_number) + && bin_pack_bin(bp, event->title, event->title_length); +} + +non_null() +static bool tox_event_conference_title_unpack( + Tox_Event_Conference_Title *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 3)) { + return false; + } + + return bin_unpack_u32(bu, &event->conference_number) + && bin_unpack_u32(bu, &event->peer_number) + && bin_unpack_bin(bu, &event->title, &event->title_length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Conference_Title *tox_events_add_conference_title(Tox_Events *events) +{ + if (events->conference_title_size == UINT32_MAX) { + return nullptr; + } + + if (events->conference_title_size == events->conference_title_capacity) { + const uint32_t new_conference_title_capacity = events->conference_title_capacity * 2 + 1; + Tox_Event_Conference_Title *new_conference_title = (Tox_Event_Conference_Title *)realloc( + events->conference_title, new_conference_title_capacity * sizeof(Tox_Event_Conference_Title)); + + if (new_conference_title == nullptr) { + return nullptr; + } + + events->conference_title = new_conference_title; + events->conference_title_capacity = new_conference_title_capacity; + } + + Tox_Event_Conference_Title *const conference_title = &events->conference_title[events->conference_title_size]; + tox_event_conference_title_construct(conference_title); + ++events->conference_title_size; + return conference_title; +} + +void tox_events_clear_conference_title(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->conference_title_size; ++i) { + tox_event_conference_title_destruct(&events->conference_title[i]); + } + + free(events->conference_title); + events->conference_title = nullptr; + events->conference_title_size = 0; + events->conference_title_capacity = 0; +} + +uint32_t tox_events_get_conference_title_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->conference_title_size; +} + +const Tox_Event_Conference_Title *tox_events_get_conference_title(const Tox_Events *events, uint32_t index) +{ + assert(index < events->conference_title_size); + assert(events->conference_title != nullptr); + return &events->conference_title[index]; +} + +bool tox_events_pack_conference_title(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_conference_title_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_conference_title_pack(tox_events_get_conference_title(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_conference_title(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Conference_Title *event = tox_events_add_conference_title(events); + + if (event == nullptr) { + return false; + } + + return tox_event_conference_title_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_conference_title(Tox *tox, uint32_t conference_number, uint32_t peer_number, + const uint8_t *title, size_t length, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Conference_Title *conference_title = tox_events_add_conference_title(state->events); + + if (conference_title == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_conference_title_set_conference_number(conference_title, conference_number); + tox_event_conference_title_set_peer_number(conference_title, peer_number); + tox_event_conference_title_set_title(conference_title, title, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/events_alloc.h b/local_pod_repo/toxcore/toxcore/toxcore/events/events_alloc.h new file mode 100644 index 0000000..6c5a7ab --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/events_alloc.h @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#ifndef C_TOXCORE_TOXCORE_TOX_EVENTS_INTERNAL_H +#define C_TOXCORE_TOXCORE_TOX_EVENTS_INTERNAL_H + +#include "../attributes.h" +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../tox_events.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct Tox_Events { + Tox_Event_Conference_Connected *conference_connected; + uint32_t conference_connected_size; + uint32_t conference_connected_capacity; + + Tox_Event_Conference_Invite *conference_invite; + uint32_t conference_invite_size; + uint32_t conference_invite_capacity; + + Tox_Event_Conference_Message *conference_message; + uint32_t conference_message_size; + uint32_t conference_message_capacity; + + Tox_Event_Conference_Peer_List_Changed *conference_peer_list_changed; + uint32_t conference_peer_list_changed_size; + uint32_t conference_peer_list_changed_capacity; + + Tox_Event_Conference_Peer_Name *conference_peer_name; + uint32_t conference_peer_name_size; + uint32_t conference_peer_name_capacity; + + Tox_Event_Conference_Title *conference_title; + uint32_t conference_title_size; + uint32_t conference_title_capacity; + + Tox_Event_File_Chunk_Request *file_chunk_request; + uint32_t file_chunk_request_size; + uint32_t file_chunk_request_capacity; + + Tox_Event_File_Recv *file_recv; + uint32_t file_recv_size; + uint32_t file_recv_capacity; + + Tox_Event_File_Recv_Chunk *file_recv_chunk; + uint32_t file_recv_chunk_size; + uint32_t file_recv_chunk_capacity; + + Tox_Event_File_Recv_Control *file_recv_control; + uint32_t file_recv_control_size; + uint32_t file_recv_control_capacity; + + Tox_Event_Friend_Connection_Status *friend_connection_status; + uint32_t friend_connection_status_size; + uint32_t friend_connection_status_capacity; + + Tox_Event_Friend_Lossless_Packet *friend_lossless_packet; + uint32_t friend_lossless_packet_size; + uint32_t friend_lossless_packet_capacity; + + Tox_Event_Friend_Lossy_Packet *friend_lossy_packet; + uint32_t friend_lossy_packet_size; + uint32_t friend_lossy_packet_capacity; + + Tox_Event_Friend_Message *friend_message; + uint32_t friend_message_size; + uint32_t friend_message_capacity; + + Tox_Event_Friend_Name *friend_name; + uint32_t friend_name_size; + uint32_t friend_name_capacity; + + Tox_Event_Friend_Read_Receipt *friend_read_receipt; + uint32_t friend_read_receipt_size; + uint32_t friend_read_receipt_capacity; + + Tox_Event_Friend_Request *friend_request; + uint32_t friend_request_size; + uint32_t friend_request_capacity; + + Tox_Event_Friend_Status *friend_status; + uint32_t friend_status_size; + uint32_t friend_status_capacity; + + Tox_Event_Friend_Status_Message *friend_status_message; + uint32_t friend_status_message_size; + uint32_t friend_status_message_capacity; + + Tox_Event_Friend_Typing *friend_typing; + uint32_t friend_typing_size; + uint32_t friend_typing_capacity; + + Tox_Event_Self_Connection_Status *self_connection_status; + uint32_t self_connection_status_size; + uint32_t self_connection_status_capacity; +}; + +typedef struct Tox_Events_State { + Tox_Err_Events_Iterate error; + Tox_Events *events; +} Tox_Events_State; + +tox_conference_connected_cb tox_events_handle_conference_connected; +tox_conference_invite_cb tox_events_handle_conference_invite; +tox_conference_message_cb tox_events_handle_conference_message; +tox_conference_peer_list_changed_cb tox_events_handle_conference_peer_list_changed; +tox_conference_peer_name_cb tox_events_handle_conference_peer_name; +tox_conference_title_cb tox_events_handle_conference_title; +tox_file_chunk_request_cb tox_events_handle_file_chunk_request; +tox_file_recv_cb tox_events_handle_file_recv; +tox_file_recv_chunk_cb tox_events_handle_file_recv_chunk; +tox_file_recv_control_cb tox_events_handle_file_recv_control; +tox_friend_connection_status_cb tox_events_handle_friend_connection_status; +tox_friend_lossless_packet_cb tox_events_handle_friend_lossless_packet; +tox_friend_lossy_packet_cb tox_events_handle_friend_lossy_packet; +tox_friend_message_cb tox_events_handle_friend_message; +tox_friend_name_cb tox_events_handle_friend_name; +tox_friend_read_receipt_cb tox_events_handle_friend_read_receipt; +tox_friend_request_cb tox_events_handle_friend_request; +tox_friend_status_cb tox_events_handle_friend_status; +tox_friend_status_message_cb tox_events_handle_friend_status_message; +tox_friend_typing_cb tox_events_handle_friend_typing; +tox_self_connection_status_cb tox_events_handle_self_connection_status; + +// non_null() +typedef void tox_events_clear_cb(Tox_Events *events); + +tox_events_clear_cb tox_events_clear_conference_connected; +tox_events_clear_cb tox_events_clear_conference_invite; +tox_events_clear_cb tox_events_clear_conference_message; +tox_events_clear_cb tox_events_clear_conference_peer_list_changed; +tox_events_clear_cb tox_events_clear_conference_peer_name; +tox_events_clear_cb tox_events_clear_conference_title; +tox_events_clear_cb tox_events_clear_file_chunk_request; +tox_events_clear_cb tox_events_clear_file_recv_chunk; +tox_events_clear_cb tox_events_clear_file_recv_control; +tox_events_clear_cb tox_events_clear_file_recv; +tox_events_clear_cb tox_events_clear_friend_connection_status; +tox_events_clear_cb tox_events_clear_friend_lossless_packet; +tox_events_clear_cb tox_events_clear_friend_lossy_packet; +tox_events_clear_cb tox_events_clear_friend_message; +tox_events_clear_cb tox_events_clear_friend_name; +tox_events_clear_cb tox_events_clear_friend_read_receipt; +tox_events_clear_cb tox_events_clear_friend_request; +tox_events_clear_cb tox_events_clear_friend_status_message; +tox_events_clear_cb tox_events_clear_friend_status; +tox_events_clear_cb tox_events_clear_friend_typing; +tox_events_clear_cb tox_events_clear_self_connection_status; + +// non_null() +typedef bool tox_events_pack_cb(const Tox_Events *events, Bin_Pack *bp); + +tox_events_pack_cb tox_events_pack_conference_connected; +tox_events_pack_cb tox_events_pack_conference_invite; +tox_events_pack_cb tox_events_pack_conference_message; +tox_events_pack_cb tox_events_pack_conference_peer_list_changed; +tox_events_pack_cb tox_events_pack_conference_peer_name; +tox_events_pack_cb tox_events_pack_conference_title; +tox_events_pack_cb tox_events_pack_file_chunk_request; +tox_events_pack_cb tox_events_pack_file_recv_chunk; +tox_events_pack_cb tox_events_pack_file_recv_control; +tox_events_pack_cb tox_events_pack_file_recv; +tox_events_pack_cb tox_events_pack_friend_connection_status; +tox_events_pack_cb tox_events_pack_friend_lossless_packet; +tox_events_pack_cb tox_events_pack_friend_lossy_packet; +tox_events_pack_cb tox_events_pack_friend_message; +tox_events_pack_cb tox_events_pack_friend_name; +tox_events_pack_cb tox_events_pack_friend_read_receipt; +tox_events_pack_cb tox_events_pack_friend_request; +tox_events_pack_cb tox_events_pack_friend_status_message; +tox_events_pack_cb tox_events_pack_friend_status; +tox_events_pack_cb tox_events_pack_friend_typing; +tox_events_pack_cb tox_events_pack_self_connection_status; + +tox_events_pack_cb tox_events_pack; + +// non_null() +typedef bool tox_events_unpack_cb(Tox_Events *events, Bin_Unpack *bu); + +tox_events_unpack_cb tox_events_unpack_conference_connected; +tox_events_unpack_cb tox_events_unpack_conference_invite; +tox_events_unpack_cb tox_events_unpack_conference_message; +tox_events_unpack_cb tox_events_unpack_conference_peer_list_changed; +tox_events_unpack_cb tox_events_unpack_conference_peer_name; +tox_events_unpack_cb tox_events_unpack_conference_title; +tox_events_unpack_cb tox_events_unpack_file_chunk_request; +tox_events_unpack_cb tox_events_unpack_file_recv_chunk; +tox_events_unpack_cb tox_events_unpack_file_recv_control; +tox_events_unpack_cb tox_events_unpack_file_recv; +tox_events_unpack_cb tox_events_unpack_friend_connection_status; +tox_events_unpack_cb tox_events_unpack_friend_lossless_packet; +tox_events_unpack_cb tox_events_unpack_friend_lossy_packet; +tox_events_unpack_cb tox_events_unpack_friend_message; +tox_events_unpack_cb tox_events_unpack_friend_name; +tox_events_unpack_cb tox_events_unpack_friend_read_receipt; +tox_events_unpack_cb tox_events_unpack_friend_request; +tox_events_unpack_cb tox_events_unpack_friend_status_message; +tox_events_unpack_cb tox_events_unpack_friend_status; +tox_events_unpack_cb tox_events_unpack_friend_typing; +tox_events_unpack_cb tox_events_unpack_self_connection_status; + +tox_events_unpack_cb tox_events_unpack; + +non_null() +Tox_Events_State *tox_events_alloc(void *user_data); + +#ifdef __cplusplus +} +#endif + +#endif // C_TOXCORE_TOXCORE_TOX_EVENTS_INTERNAL_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/events_alloc.m b/local_pod_repo/toxcore/toxcore/toxcore/events/events_alloc.m new file mode 100644 index 0000000..f661c5c --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/events_alloc.m @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include + +#include "../ccompat.h" + +Tox_Events_State *tox_events_alloc(void *user_data) +{ + Tox_Events_State *state = (Tox_Events_State *)user_data; + assert(state != nullptr); + + if (state->events != nullptr) { + // Already allocated. + return state; + } + + state->events = (Tox_Events *)calloc(1, sizeof(Tox_Events)); + + if (state->events == nullptr) { + // It's still null => allocation failed. + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + } else { + *state->events = (Tox_Events) { + nullptr + }; + } + + return state; +} + +void tox_events_free(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + tox_events_clear_conference_connected(events); + tox_events_clear_conference_invite(events); + tox_events_clear_conference_message(events); + tox_events_clear_conference_peer_list_changed(events); + tox_events_clear_conference_peer_name(events); + tox_events_clear_conference_title(events); + tox_events_clear_file_chunk_request(events); + tox_events_clear_file_recv_chunk(events); + tox_events_clear_file_recv_control(events); + tox_events_clear_file_recv(events); + tox_events_clear_friend_connection_status(events); + tox_events_clear_friend_lossless_packet(events); + tox_events_clear_friend_lossy_packet(events); + tox_events_clear_friend_message(events); + tox_events_clear_friend_name(events); + tox_events_clear_friend_read_receipt(events); + tox_events_clear_friend_request(events); + tox_events_clear_friend_status(events); + tox_events_clear_friend_status_message(events); + tox_events_clear_friend_typing(events); + tox_events_clear_self_connection_status(events); + free(events); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/file_chunk_request.m b/local_pod_repo/toxcore/toxcore/toxcore/events/file_chunk_request.m new file mode 100644 index 0000000..f5a9421 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/file_chunk_request.m @@ -0,0 +1,243 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_File_Chunk_Request { + uint32_t friend_number; + uint32_t file_number; + uint64_t position; + uint16_t length; +}; + +non_null() +static void tox_event_file_chunk_request_construct(Tox_Event_File_Chunk_Request *file_chunk_request) +{ + *file_chunk_request = (Tox_Event_File_Chunk_Request) { + 0 + }; +} +non_null() +static void tox_event_file_chunk_request_destruct(Tox_Event_File_Chunk_Request *file_chunk_request) +{ + return; +} + +non_null() +static void tox_event_file_chunk_request_set_friend_number(Tox_Event_File_Chunk_Request *file_chunk_request, + uint32_t friend_number) +{ + assert(file_chunk_request != nullptr); + file_chunk_request->friend_number = friend_number; +} +uint32_t tox_event_file_chunk_request_get_friend_number(const Tox_Event_File_Chunk_Request *file_chunk_request) +{ + assert(file_chunk_request != nullptr); + return file_chunk_request->friend_number; +} + +non_null() +static void tox_event_file_chunk_request_set_file_number(Tox_Event_File_Chunk_Request *file_chunk_request, + uint32_t file_number) +{ + assert(file_chunk_request != nullptr); + file_chunk_request->file_number = file_number; +} +uint32_t tox_event_file_chunk_request_get_file_number(const Tox_Event_File_Chunk_Request *file_chunk_request) +{ + assert(file_chunk_request != nullptr); + return file_chunk_request->file_number; +} + +non_null() +static void tox_event_file_chunk_request_set_position(Tox_Event_File_Chunk_Request *file_chunk_request, + uint64_t position) +{ + assert(file_chunk_request != nullptr); + file_chunk_request->position = position; +} +uint64_t tox_event_file_chunk_request_get_position(const Tox_Event_File_Chunk_Request *file_chunk_request) +{ + assert(file_chunk_request != nullptr); + return file_chunk_request->position; +} + +non_null() +static void tox_event_file_chunk_request_set_length(Tox_Event_File_Chunk_Request *file_chunk_request, uint16_t length) +{ + assert(file_chunk_request != nullptr); + file_chunk_request->length = length; +} +uint16_t tox_event_file_chunk_request_get_length(const Tox_Event_File_Chunk_Request *file_chunk_request) +{ + assert(file_chunk_request != nullptr); + return file_chunk_request->length; +} + +non_null() +static bool tox_event_file_chunk_request_pack( + const Tox_Event_File_Chunk_Request *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FILE_CHUNK_REQUEST) + && bin_pack_array(bp, 4) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_u32(bp, event->file_number) + && bin_pack_u64(bp, event->position) + && bin_pack_u16(bp, event->length); +} + +non_null() +static bool tox_event_file_chunk_request_unpack( + Tox_Event_File_Chunk_Request *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 4)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && bin_unpack_u32(bu, &event->file_number) + && bin_unpack_u64(bu, &event->position) + && bin_unpack_u16(bu, &event->length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_File_Chunk_Request *tox_events_add_file_chunk_request(Tox_Events *events) +{ + if (events->file_chunk_request_size == UINT32_MAX) { + return nullptr; + } + + if (events->file_chunk_request_size == events->file_chunk_request_capacity) { + const uint32_t new_file_chunk_request_capacity = events->file_chunk_request_capacity * 2 + 1; + Tox_Event_File_Chunk_Request *new_file_chunk_request = (Tox_Event_File_Chunk_Request *)realloc( + events->file_chunk_request, new_file_chunk_request_capacity * sizeof(Tox_Event_File_Chunk_Request)); + + if (new_file_chunk_request == nullptr) { + return nullptr; + } + + events->file_chunk_request = new_file_chunk_request; + events->file_chunk_request_capacity = new_file_chunk_request_capacity; + } + + Tox_Event_File_Chunk_Request *const file_chunk_request = &events->file_chunk_request[events->file_chunk_request_size]; + tox_event_file_chunk_request_construct(file_chunk_request); + ++events->file_chunk_request_size; + return file_chunk_request; +} + +void tox_events_clear_file_chunk_request(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->file_chunk_request_size; ++i) { + tox_event_file_chunk_request_destruct(&events->file_chunk_request[i]); + } + + free(events->file_chunk_request); + events->file_chunk_request = nullptr; + events->file_chunk_request_size = 0; + events->file_chunk_request_capacity = 0; +} + +uint32_t tox_events_get_file_chunk_request_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->file_chunk_request_size; +} + +const Tox_Event_File_Chunk_Request *tox_events_get_file_chunk_request(const Tox_Events *events, uint32_t index) +{ + assert(index < events->file_chunk_request_size); + assert(events->file_chunk_request != nullptr); + return &events->file_chunk_request[index]; +} + +bool tox_events_pack_file_chunk_request(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_file_chunk_request_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_file_chunk_request_pack(tox_events_get_file_chunk_request(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_file_chunk_request(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_File_Chunk_Request *event = tox_events_add_file_chunk_request(events); + + if (event == nullptr) { + return false; + } + + return tox_event_file_chunk_request_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_file_chunk_request(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, + size_t length, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_File_Chunk_Request *file_chunk_request = tox_events_add_file_chunk_request(state->events); + + if (file_chunk_request == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_file_chunk_request_set_friend_number(file_chunk_request, friend_number); + tox_event_file_chunk_request_set_file_number(file_chunk_request, file_number); + tox_event_file_chunk_request_set_position(file_chunk_request, position); + tox_event_file_chunk_request_set_length(file_chunk_request, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/file_recv.m b/local_pod_repo/toxcore/toxcore/toxcore/events/file_recv.m new file mode 100644 index 0000000..a907b3f --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/file_recv.m @@ -0,0 +1,282 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_File_Recv { + uint32_t friend_number; + uint32_t file_number; + uint32_t kind; + uint64_t file_size; + uint8_t *filename; + uint32_t filename_length; +}; + +non_null() +static void tox_event_file_recv_construct(Tox_Event_File_Recv *file_recv) +{ + *file_recv = (Tox_Event_File_Recv) { + 0 + }; +} +non_null() +static void tox_event_file_recv_destruct(Tox_Event_File_Recv *file_recv) +{ + free(file_recv->filename); +} + +non_null() +static void tox_event_file_recv_set_friend_number(Tox_Event_File_Recv *file_recv, + uint32_t friend_number) +{ + assert(file_recv != nullptr); + file_recv->friend_number = friend_number; +} +uint32_t tox_event_file_recv_get_friend_number(const Tox_Event_File_Recv *file_recv) +{ + assert(file_recv != nullptr); + return file_recv->friend_number; +} + +non_null() +static void tox_event_file_recv_set_file_number(Tox_Event_File_Recv *file_recv, + uint32_t file_number) +{ + assert(file_recv != nullptr); + file_recv->file_number = file_number; +} +uint32_t tox_event_file_recv_get_file_number(const Tox_Event_File_Recv *file_recv) +{ + assert(file_recv != nullptr); + return file_recv->file_number; +} + +non_null() +static void tox_event_file_recv_set_kind(Tox_Event_File_Recv *file_recv, + uint32_t kind) +{ + assert(file_recv != nullptr); + file_recv->kind = kind; +} +uint32_t tox_event_file_recv_get_kind(const Tox_Event_File_Recv *file_recv) +{ + assert(file_recv != nullptr); + return file_recv->kind; +} + +non_null() +static void tox_event_file_recv_set_file_size(Tox_Event_File_Recv *file_recv, + uint64_t file_size) +{ + assert(file_recv != nullptr); + file_recv->file_size = file_size; +} +uint64_t tox_event_file_recv_get_file_size(const Tox_Event_File_Recv *file_recv) +{ + assert(file_recv != nullptr); + return file_recv->file_size; +} + +non_null() +static bool tox_event_file_recv_set_filename(Tox_Event_File_Recv *file_recv, const uint8_t *filename, + uint32_t filename_length) +{ + assert(file_recv != nullptr); + + if (file_recv->filename != nullptr) { + free(file_recv->filename); + file_recv->filename = nullptr; + file_recv->filename_length = 0; + } + + file_recv->filename = (uint8_t *)malloc(filename_length); + + if (file_recv->filename == nullptr) { + return false; + } + + memcpy(file_recv->filename, filename, filename_length); + file_recv->filename_length = filename_length; + return true; +} +uint32_t tox_event_file_recv_get_filename_length(const Tox_Event_File_Recv *file_recv) +{ + assert(file_recv != nullptr); + return file_recv->filename_length; +} +const uint8_t *tox_event_file_recv_get_filename(const Tox_Event_File_Recv *file_recv) +{ + assert(file_recv != nullptr); + return file_recv->filename; +} + +non_null() +static bool tox_event_file_recv_pack( + const Tox_Event_File_Recv *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FILE_RECV) + && bin_pack_array(bp, 5) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_u32(bp, event->file_number) + && bin_pack_u32(bp, event->kind) + && bin_pack_u64(bp, event->file_size) + && bin_pack_bin(bp, event->filename, event->filename_length); +} + +non_null() +static bool tox_event_file_recv_unpack( + Tox_Event_File_Recv *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 5)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && bin_unpack_u32(bu, &event->file_number) + && bin_unpack_u32(bu, &event->kind) + && bin_unpack_u64(bu, &event->file_size) + && bin_unpack_bin(bu, &event->filename, &event->filename_length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_File_Recv *tox_events_add_file_recv(Tox_Events *events) +{ + if (events->file_recv_size == UINT32_MAX) { + return nullptr; + } + + if (events->file_recv_size == events->file_recv_capacity) { + const uint32_t new_file_recv_capacity = events->file_recv_capacity * 2 + 1; + Tox_Event_File_Recv *new_file_recv = (Tox_Event_File_Recv *)realloc( + events->file_recv, new_file_recv_capacity * sizeof(Tox_Event_File_Recv)); + + if (new_file_recv == nullptr) { + return nullptr; + } + + events->file_recv = new_file_recv; + events->file_recv_capacity = new_file_recv_capacity; + } + + Tox_Event_File_Recv *const file_recv = &events->file_recv[events->file_recv_size]; + tox_event_file_recv_construct(file_recv); + ++events->file_recv_size; + return file_recv; +} + +void tox_events_clear_file_recv(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->file_recv_size; ++i) { + tox_event_file_recv_destruct(&events->file_recv[i]); + } + + free(events->file_recv); + events->file_recv = nullptr; + events->file_recv_size = 0; + events->file_recv_capacity = 0; +} + +uint32_t tox_events_get_file_recv_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->file_recv_size; +} + +const Tox_Event_File_Recv *tox_events_get_file_recv(const Tox_Events *events, uint32_t index) +{ + assert(index < events->file_recv_size); + assert(events->file_recv != nullptr); + return &events->file_recv[index]; +} + +bool tox_events_pack_file_recv(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_file_recv_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_file_recv_pack(tox_events_get_file_recv(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_file_recv(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_File_Recv *event = tox_events_add_file_recv(events); + + if (event == nullptr) { + return false; + } + + return tox_event_file_recv_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_file_recv(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t kind, + uint64_t file_size, const uint8_t *filename, size_t filename_length, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_File_Recv *file_recv = tox_events_add_file_recv(state->events); + + if (file_recv == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_file_recv_set_friend_number(file_recv, friend_number); + tox_event_file_recv_set_file_number(file_recv, file_number); + tox_event_file_recv_set_kind(file_recv, kind); + tox_event_file_recv_set_file_size(file_recv, file_size); + tox_event_file_recv_set_filename(file_recv, filename, filename_length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/file_recv_chunk.m b/local_pod_repo/toxcore/toxcore/toxcore/events/file_recv_chunk.m new file mode 100644 index 0000000..0fe7784 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/file_recv_chunk.m @@ -0,0 +1,265 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_File_Recv_Chunk { + uint32_t friend_number; + uint32_t file_number; + uint64_t position; + uint8_t *data; + uint32_t data_length; +}; + +non_null() +static void tox_event_file_recv_chunk_construct(Tox_Event_File_Recv_Chunk *file_recv_chunk) +{ + *file_recv_chunk = (Tox_Event_File_Recv_Chunk) { + 0 + }; +} +non_null() +static void tox_event_file_recv_chunk_destruct(Tox_Event_File_Recv_Chunk *file_recv_chunk) +{ + free(file_recv_chunk->data); +} + +non_null() +static void tox_event_file_recv_chunk_set_friend_number(Tox_Event_File_Recv_Chunk *file_recv_chunk, + uint32_t friend_number) +{ + assert(file_recv_chunk != nullptr); + file_recv_chunk->friend_number = friend_number; +} +uint32_t tox_event_file_recv_chunk_get_friend_number(const Tox_Event_File_Recv_Chunk *file_recv_chunk) +{ + assert(file_recv_chunk != nullptr); + return file_recv_chunk->friend_number; +} + +non_null() +static void tox_event_file_recv_chunk_set_file_number(Tox_Event_File_Recv_Chunk *file_recv_chunk, + uint32_t file_number) +{ + assert(file_recv_chunk != nullptr); + file_recv_chunk->file_number = file_number; +} +uint32_t tox_event_file_recv_chunk_get_file_number(const Tox_Event_File_Recv_Chunk *file_recv_chunk) +{ + assert(file_recv_chunk != nullptr); + return file_recv_chunk->file_number; +} + +non_null() +static void tox_event_file_recv_chunk_set_position(Tox_Event_File_Recv_Chunk *file_recv_chunk, + uint64_t position) +{ + assert(file_recv_chunk != nullptr); + file_recv_chunk->position = position; +} +uint64_t tox_event_file_recv_chunk_get_position(const Tox_Event_File_Recv_Chunk *file_recv_chunk) +{ + assert(file_recv_chunk != nullptr); + return file_recv_chunk->position; +} + +non_null() +static bool tox_event_file_recv_chunk_set_data(Tox_Event_File_Recv_Chunk *file_recv_chunk, const uint8_t *data, + uint32_t data_length) +{ + assert(file_recv_chunk != nullptr); + + if (file_recv_chunk->data != nullptr) { + free(file_recv_chunk->data); + file_recv_chunk->data = nullptr; + file_recv_chunk->data_length = 0; + } + + file_recv_chunk->data = (uint8_t *)malloc(data_length); + + if (file_recv_chunk->data == nullptr) { + return false; + } + + memcpy(file_recv_chunk->data, data, data_length); + file_recv_chunk->data_length = data_length; + return true; +} +uint32_t tox_event_file_recv_chunk_get_length(const Tox_Event_File_Recv_Chunk *file_recv_chunk) +{ + assert(file_recv_chunk != nullptr); + return file_recv_chunk->data_length; +} +const uint8_t *tox_event_file_recv_chunk_get_data(const Tox_Event_File_Recv_Chunk *file_recv_chunk) +{ + assert(file_recv_chunk != nullptr); + return file_recv_chunk->data; +} + +non_null() +static bool tox_event_file_recv_chunk_pack( + const Tox_Event_File_Recv_Chunk *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FILE_RECV_CHUNK) + && bin_pack_array(bp, 4) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_u32(bp, event->file_number) + && bin_pack_u64(bp, event->position) + && bin_pack_bin(bp, event->data, event->data_length); +} + +non_null() +static bool tox_event_file_recv_chunk_unpack( + Tox_Event_File_Recv_Chunk *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 4)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && bin_unpack_u32(bu, &event->file_number) + && bin_unpack_u64(bu, &event->position) + && bin_unpack_bin(bu, &event->data, &event->data_length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_File_Recv_Chunk *tox_events_add_file_recv_chunk(Tox_Events *events) +{ + if (events->file_recv_chunk_size == UINT32_MAX) { + return nullptr; + } + + if (events->file_recv_chunk_size == events->file_recv_chunk_capacity) { + const uint32_t new_file_recv_chunk_capacity = events->file_recv_chunk_capacity * 2 + 1; + Tox_Event_File_Recv_Chunk *new_file_recv_chunk = (Tox_Event_File_Recv_Chunk *)realloc( + events->file_recv_chunk, new_file_recv_chunk_capacity * sizeof(Tox_Event_File_Recv_Chunk)); + + if (new_file_recv_chunk == nullptr) { + return nullptr; + } + + events->file_recv_chunk = new_file_recv_chunk; + events->file_recv_chunk_capacity = new_file_recv_chunk_capacity; + } + + Tox_Event_File_Recv_Chunk *const file_recv_chunk = &events->file_recv_chunk[events->file_recv_chunk_size]; + tox_event_file_recv_chunk_construct(file_recv_chunk); + ++events->file_recv_chunk_size; + return file_recv_chunk; +} + +void tox_events_clear_file_recv_chunk(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->file_recv_chunk_size; ++i) { + tox_event_file_recv_chunk_destruct(&events->file_recv_chunk[i]); + } + + free(events->file_recv_chunk); + events->file_recv_chunk = nullptr; + events->file_recv_chunk_size = 0; + events->file_recv_chunk_capacity = 0; +} + +uint32_t tox_events_get_file_recv_chunk_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->file_recv_chunk_size; +} + +const Tox_Event_File_Recv_Chunk *tox_events_get_file_recv_chunk(const Tox_Events *events, uint32_t index) +{ + assert(index < events->file_recv_chunk_size); + assert(events->file_recv_chunk != nullptr); + return &events->file_recv_chunk[index]; +} + +bool tox_events_pack_file_recv_chunk(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_file_recv_chunk_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_file_recv_chunk_pack(tox_events_get_file_recv_chunk(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_file_recv_chunk(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_File_Recv_Chunk *event = tox_events_add_file_recv_chunk(events); + + if (event == nullptr) { + return false; + } + + return tox_event_file_recv_chunk_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_file_recv_chunk(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, + const uint8_t *data, size_t length, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_File_Recv_Chunk *file_recv_chunk = tox_events_add_file_recv_chunk(state->events); + + if (file_recv_chunk == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_file_recv_chunk_set_friend_number(file_recv_chunk, friend_number); + tox_event_file_recv_chunk_set_file_number(file_recv_chunk, file_number); + tox_event_file_recv_chunk_set_position(file_recv_chunk, position); + tox_event_file_recv_chunk_set_data(file_recv_chunk, data, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/file_recv_control.m b/local_pod_repo/toxcore/toxcore/toxcore/events/file_recv_control.m new file mode 100644 index 0000000..af04b33 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/file_recv_control.m @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" +#include "../tox_unpack.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_File_Recv_Control { + uint32_t friend_number; + uint32_t file_number; + Tox_File_Control control; +}; + +non_null() +static void tox_event_file_recv_control_construct(Tox_Event_File_Recv_Control *file_recv_control) +{ + *file_recv_control = (Tox_Event_File_Recv_Control) { + 0 + }; +} +non_null() +static void tox_event_file_recv_control_destruct(Tox_Event_File_Recv_Control *file_recv_control) +{ + return; +} + +non_null() +static void tox_event_file_recv_control_set_friend_number(Tox_Event_File_Recv_Control *file_recv_control, + uint32_t friend_number) +{ + assert(file_recv_control != nullptr); + file_recv_control->friend_number = friend_number; +} +uint32_t tox_event_file_recv_control_get_friend_number(const Tox_Event_File_Recv_Control *file_recv_control) +{ + assert(file_recv_control != nullptr); + return file_recv_control->friend_number; +} + +non_null() +static void tox_event_file_recv_control_set_file_number(Tox_Event_File_Recv_Control *file_recv_control, + uint32_t file_number) +{ + assert(file_recv_control != nullptr); + file_recv_control->file_number = file_number; +} +uint32_t tox_event_file_recv_control_get_file_number(const Tox_Event_File_Recv_Control *file_recv_control) +{ + assert(file_recv_control != nullptr); + return file_recv_control->file_number; +} + +non_null() +static void tox_event_file_recv_control_set_control(Tox_Event_File_Recv_Control *file_recv_control, + Tox_File_Control control) +{ + assert(file_recv_control != nullptr); + file_recv_control->control = control; +} +Tox_File_Control tox_event_file_recv_control_get_control(const Tox_Event_File_Recv_Control *file_recv_control) +{ + assert(file_recv_control != nullptr); + return file_recv_control->control; +} + +non_null() +static bool tox_event_file_recv_control_pack( + const Tox_Event_File_Recv_Control *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FILE_RECV_CONTROL) + && bin_pack_array(bp, 3) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_u32(bp, event->file_number) + && bin_pack_u32(bp, event->control); +} + +non_null() +static bool tox_event_file_recv_control_unpack( + Tox_Event_File_Recv_Control *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 3)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && bin_unpack_u32(bu, &event->file_number) + && tox_unpack_file_control(bu, &event->control); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_File_Recv_Control *tox_events_add_file_recv_control(Tox_Events *events) +{ + if (events->file_recv_control_size == UINT32_MAX) { + return nullptr; + } + + if (events->file_recv_control_size == events->file_recv_control_capacity) { + const uint32_t new_file_recv_control_capacity = events->file_recv_control_capacity * 2 + 1; + Tox_Event_File_Recv_Control *new_file_recv_control = (Tox_Event_File_Recv_Control *)realloc( + events->file_recv_control, new_file_recv_control_capacity * sizeof(Tox_Event_File_Recv_Control)); + + if (new_file_recv_control == nullptr) { + return nullptr; + } + + events->file_recv_control = new_file_recv_control; + events->file_recv_control_capacity = new_file_recv_control_capacity; + } + + Tox_Event_File_Recv_Control *const file_recv_control = &events->file_recv_control[events->file_recv_control_size]; + tox_event_file_recv_control_construct(file_recv_control); + ++events->file_recv_control_size; + return file_recv_control; +} + +void tox_events_clear_file_recv_control(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->file_recv_control_size; ++i) { + tox_event_file_recv_control_destruct(&events->file_recv_control[i]); + } + + free(events->file_recv_control); + events->file_recv_control = nullptr; + events->file_recv_control_size = 0; + events->file_recv_control_capacity = 0; +} + +uint32_t tox_events_get_file_recv_control_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->file_recv_control_size; +} + +const Tox_Event_File_Recv_Control *tox_events_get_file_recv_control(const Tox_Events *events, uint32_t index) +{ + assert(index < events->file_recv_control_size); + assert(events->file_recv_control != nullptr); + return &events->file_recv_control[index]; +} + +bool tox_events_pack_file_recv_control(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_file_recv_control_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_file_recv_control_pack(tox_events_get_file_recv_control(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_file_recv_control(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_File_Recv_Control *event = tox_events_add_file_recv_control(events); + + if (event == nullptr) { + return false; + } + + return tox_event_file_recv_control_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_file_recv_control(Tox *tox, uint32_t friend_number, uint32_t file_number, + Tox_File_Control control, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_File_Recv_Control *file_recv_control = tox_events_add_file_recv_control(state->events); + + if (file_recv_control == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_file_recv_control_set_friend_number(file_recv_control, friend_number); + tox_event_file_recv_control_set_file_number(file_recv_control, file_number); + tox_event_file_recv_control_set_control(file_recv_control, control); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/friend_connection_status.m b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_connection_status.m new file mode 100644 index 0000000..3d325ef --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_connection_status.m @@ -0,0 +1,215 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" +#include "../tox_unpack.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Friend_Connection_Status { + uint32_t friend_number; + Tox_Connection connection_status; +}; + +non_null() +static void tox_event_friend_connection_status_construct(Tox_Event_Friend_Connection_Status *friend_connection_status) +{ + *friend_connection_status = (Tox_Event_Friend_Connection_Status) { + 0 + }; +} +non_null() +static void tox_event_friend_connection_status_destruct(Tox_Event_Friend_Connection_Status *friend_connection_status) +{ + return; +} + +non_null() +static void tox_event_friend_connection_status_set_friend_number(Tox_Event_Friend_Connection_Status + *friend_connection_status, uint32_t friend_number) +{ + assert(friend_connection_status != nullptr); + friend_connection_status->friend_number = friend_number; +} +uint32_t tox_event_friend_connection_status_get_friend_number(const Tox_Event_Friend_Connection_Status + *friend_connection_status) +{ + assert(friend_connection_status != nullptr); + return friend_connection_status->friend_number; +} + +non_null() +static void tox_event_friend_connection_status_set_connection_status(Tox_Event_Friend_Connection_Status + *friend_connection_status, Tox_Connection connection_status) +{ + assert(friend_connection_status != nullptr); + friend_connection_status->connection_status = connection_status; +} +Tox_Connection tox_event_friend_connection_status_get_connection_status(const Tox_Event_Friend_Connection_Status + *friend_connection_status) +{ + assert(friend_connection_status != nullptr); + return friend_connection_status->connection_status; +} + +non_null() +static bool tox_event_friend_connection_status_pack( + const Tox_Event_Friend_Connection_Status *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FRIEND_CONNECTION_STATUS) + && bin_pack_array(bp, 2) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_u32(bp, event->connection_status); +} + +non_null() +static bool tox_event_friend_connection_status_unpack( + Tox_Event_Friend_Connection_Status *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 2)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && tox_unpack_connection(bu, &event->connection_status); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Friend_Connection_Status *tox_events_add_friend_connection_status(Tox_Events *events) +{ + if (events->friend_connection_status_size == UINT32_MAX) { + return nullptr; + } + + if (events->friend_connection_status_size == events->friend_connection_status_capacity) { + const uint32_t new_friend_connection_status_capacity = events->friend_connection_status_capacity * 2 + 1; + Tox_Event_Friend_Connection_Status *new_friend_connection_status = (Tox_Event_Friend_Connection_Status *)realloc( + events->friend_connection_status, new_friend_connection_status_capacity * sizeof(Tox_Event_Friend_Connection_Status)); + + if (new_friend_connection_status == nullptr) { + return nullptr; + } + + events->friend_connection_status = new_friend_connection_status; + events->friend_connection_status_capacity = new_friend_connection_status_capacity; + } + + Tox_Event_Friend_Connection_Status *const friend_connection_status = + &events->friend_connection_status[events->friend_connection_status_size]; + tox_event_friend_connection_status_construct(friend_connection_status); + ++events->friend_connection_status_size; + return friend_connection_status; +} + +void tox_events_clear_friend_connection_status(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->friend_connection_status_size; ++i) { + tox_event_friend_connection_status_destruct(&events->friend_connection_status[i]); + } + + free(events->friend_connection_status); + events->friend_connection_status = nullptr; + events->friend_connection_status_size = 0; + events->friend_connection_status_capacity = 0; +} + +uint32_t tox_events_get_friend_connection_status_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->friend_connection_status_size; +} + +const Tox_Event_Friend_Connection_Status *tox_events_get_friend_connection_status(const Tox_Events *events, + uint32_t index) +{ + assert(index < events->friend_connection_status_size); + assert(events->friend_connection_status != nullptr); + return &events->friend_connection_status[index]; +} + +bool tox_events_pack_friend_connection_status(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_friend_connection_status_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_friend_connection_status_pack(tox_events_get_friend_connection_status(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_friend_connection_status(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Friend_Connection_Status *event = tox_events_add_friend_connection_status(events); + + if (event == nullptr) { + return false; + } + + return tox_event_friend_connection_status_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_friend_connection_status(Tox *tox, uint32_t friend_number, Tox_Connection connection_status, + void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Friend_Connection_Status *friend_connection_status = tox_events_add_friend_connection_status(state->events); + + if (friend_connection_status == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_friend_connection_status_set_friend_number(friend_connection_status, friend_number); + tox_event_friend_connection_status_set_connection_status(friend_connection_status, connection_status); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/friend_lossless_packet.m b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_lossless_packet.m new file mode 100644 index 0000000..944abd4 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_lossless_packet.m @@ -0,0 +1,233 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Friend_Lossless_Packet { + uint32_t friend_number; + uint8_t *data; + uint32_t data_length; +}; + +non_null() +static void tox_event_friend_lossless_packet_construct(Tox_Event_Friend_Lossless_Packet *friend_lossless_packet) +{ + *friend_lossless_packet = (Tox_Event_Friend_Lossless_Packet) { + 0 + }; +} +non_null() +static void tox_event_friend_lossless_packet_destruct(Tox_Event_Friend_Lossless_Packet *friend_lossless_packet) +{ + free(friend_lossless_packet->data); +} + +non_null() +static void tox_event_friend_lossless_packet_set_friend_number(Tox_Event_Friend_Lossless_Packet *friend_lossless_packet, + uint32_t friend_number) +{ + assert(friend_lossless_packet != nullptr); + friend_lossless_packet->friend_number = friend_number; +} +uint32_t tox_event_friend_lossless_packet_get_friend_number(const Tox_Event_Friend_Lossless_Packet + *friend_lossless_packet) +{ + assert(friend_lossless_packet != nullptr); + return friend_lossless_packet->friend_number; +} + +non_null() +static bool tox_event_friend_lossless_packet_set_data(Tox_Event_Friend_Lossless_Packet *friend_lossless_packet, + const uint8_t *data, uint32_t data_length) +{ + assert(friend_lossless_packet != nullptr); + + if (friend_lossless_packet->data != nullptr) { + free(friend_lossless_packet->data); + friend_lossless_packet->data = nullptr; + friend_lossless_packet->data_length = 0; + } + + friend_lossless_packet->data = (uint8_t *)malloc(data_length); + + if (friend_lossless_packet->data == nullptr) { + return false; + } + + memcpy(friend_lossless_packet->data, data, data_length); + friend_lossless_packet->data_length = data_length; + return true; +} +uint32_t tox_event_friend_lossless_packet_get_data_length(const Tox_Event_Friend_Lossless_Packet *friend_lossless_packet) +{ + assert(friend_lossless_packet != nullptr); + return friend_lossless_packet->data_length; +} +const uint8_t *tox_event_friend_lossless_packet_get_data(const Tox_Event_Friend_Lossless_Packet *friend_lossless_packet) +{ + assert(friend_lossless_packet != nullptr); + return friend_lossless_packet->data; +} + +non_null() +static bool tox_event_friend_lossless_packet_pack( + const Tox_Event_Friend_Lossless_Packet *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FRIEND_LOSSLESS_PACKET) + && bin_pack_array(bp, 2) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_bin(bp, event->data, event->data_length); +} + +non_null() +static bool tox_event_friend_lossless_packet_unpack( + Tox_Event_Friend_Lossless_Packet *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 2)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && bin_unpack_bin(bu, &event->data, &event->data_length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Friend_Lossless_Packet *tox_events_add_friend_lossless_packet(Tox_Events *events) +{ + if (events->friend_lossless_packet_size == UINT32_MAX) { + return nullptr; + } + + if (events->friend_lossless_packet_size == events->friend_lossless_packet_capacity) { + const uint32_t new_friend_lossless_packet_capacity = events->friend_lossless_packet_capacity * 2 + 1; + Tox_Event_Friend_Lossless_Packet *new_friend_lossless_packet = (Tox_Event_Friend_Lossless_Packet *)realloc( + events->friend_lossless_packet, new_friend_lossless_packet_capacity * sizeof(Tox_Event_Friend_Lossless_Packet)); + + if (new_friend_lossless_packet == nullptr) { + return nullptr; + } + + events->friend_lossless_packet = new_friend_lossless_packet; + events->friend_lossless_packet_capacity = new_friend_lossless_packet_capacity; + } + + Tox_Event_Friend_Lossless_Packet *const friend_lossless_packet = + &events->friend_lossless_packet[events->friend_lossless_packet_size]; + tox_event_friend_lossless_packet_construct(friend_lossless_packet); + ++events->friend_lossless_packet_size; + return friend_lossless_packet; +} + +void tox_events_clear_friend_lossless_packet(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->friend_lossless_packet_size; ++i) { + tox_event_friend_lossless_packet_destruct(&events->friend_lossless_packet[i]); + } + + free(events->friend_lossless_packet); + events->friend_lossless_packet = nullptr; + events->friend_lossless_packet_size = 0; + events->friend_lossless_packet_capacity = 0; +} + +uint32_t tox_events_get_friend_lossless_packet_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->friend_lossless_packet_size; +} + +const Tox_Event_Friend_Lossless_Packet *tox_events_get_friend_lossless_packet(const Tox_Events *events, uint32_t index) +{ + assert(index < events->friend_lossless_packet_size); + assert(events->friend_lossless_packet != nullptr); + return &events->friend_lossless_packet[index]; +} + +bool tox_events_pack_friend_lossless_packet(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_friend_lossless_packet_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_friend_lossless_packet_pack(tox_events_get_friend_lossless_packet(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_friend_lossless_packet(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Friend_Lossless_Packet *event = tox_events_add_friend_lossless_packet(events); + + if (event == nullptr) { + return false; + } + + return tox_event_friend_lossless_packet_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_friend_lossless_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Friend_Lossless_Packet *friend_lossless_packet = tox_events_add_friend_lossless_packet(state->events); + + if (friend_lossless_packet == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_friend_lossless_packet_set_friend_number(friend_lossless_packet, friend_number); + tox_event_friend_lossless_packet_set_data(friend_lossless_packet, data, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/friend_lossy_packet.m b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_lossy_packet.m new file mode 100644 index 0000000..a787fce --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_lossy_packet.m @@ -0,0 +1,232 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Friend_Lossy_Packet { + uint32_t friend_number; + uint8_t *data; + uint32_t data_length; +}; + +non_null() +static void tox_event_friend_lossy_packet_construct(Tox_Event_Friend_Lossy_Packet *friend_lossy_packet) +{ + *friend_lossy_packet = (Tox_Event_Friend_Lossy_Packet) { + 0 + }; +} +non_null() +static void tox_event_friend_lossy_packet_destruct(Tox_Event_Friend_Lossy_Packet *friend_lossy_packet) +{ + free(friend_lossy_packet->data); +} + +non_null() +static void tox_event_friend_lossy_packet_set_friend_number(Tox_Event_Friend_Lossy_Packet *friend_lossy_packet, + uint32_t friend_number) +{ + assert(friend_lossy_packet != nullptr); + friend_lossy_packet->friend_number = friend_number; +} +uint32_t tox_event_friend_lossy_packet_get_friend_number(const Tox_Event_Friend_Lossy_Packet *friend_lossy_packet) +{ + assert(friend_lossy_packet != nullptr); + return friend_lossy_packet->friend_number; +} + +non_null() +static bool tox_event_friend_lossy_packet_set_data(Tox_Event_Friend_Lossy_Packet *friend_lossy_packet, + const uint8_t *data, uint32_t data_length) +{ + assert(friend_lossy_packet != nullptr); + + if (friend_lossy_packet->data != nullptr) { + free(friend_lossy_packet->data); + friend_lossy_packet->data = nullptr; + friend_lossy_packet->data_length = 0; + } + + friend_lossy_packet->data = (uint8_t *)malloc(data_length); + + if (friend_lossy_packet->data == nullptr) { + return false; + } + + memcpy(friend_lossy_packet->data, data, data_length); + friend_lossy_packet->data_length = data_length; + return true; +} +uint32_t tox_event_friend_lossy_packet_get_data_length(const Tox_Event_Friend_Lossy_Packet *friend_lossy_packet) +{ + assert(friend_lossy_packet != nullptr); + return friend_lossy_packet->data_length; +} +const uint8_t *tox_event_friend_lossy_packet_get_data(const Tox_Event_Friend_Lossy_Packet *friend_lossy_packet) +{ + assert(friend_lossy_packet != nullptr); + return friend_lossy_packet->data; +} + +non_null() +static bool tox_event_friend_lossy_packet_pack( + const Tox_Event_Friend_Lossy_Packet *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FRIEND_LOSSY_PACKET) + && bin_pack_array(bp, 2) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_bin(bp, event->data, event->data_length); +} + +non_null() +static bool tox_event_friend_lossy_packet_unpack( + Tox_Event_Friend_Lossy_Packet *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 2)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && bin_unpack_bin(bu, &event->data, &event->data_length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Friend_Lossy_Packet *tox_events_add_friend_lossy_packet(Tox_Events *events) +{ + if (events->friend_lossy_packet_size == UINT32_MAX) { + return nullptr; + } + + if (events->friend_lossy_packet_size == events->friend_lossy_packet_capacity) { + const uint32_t new_friend_lossy_packet_capacity = events->friend_lossy_packet_capacity * 2 + 1; + Tox_Event_Friend_Lossy_Packet *new_friend_lossy_packet = (Tox_Event_Friend_Lossy_Packet *)realloc( + events->friend_lossy_packet, new_friend_lossy_packet_capacity * sizeof(Tox_Event_Friend_Lossy_Packet)); + + if (new_friend_lossy_packet == nullptr) { + return nullptr; + } + + events->friend_lossy_packet = new_friend_lossy_packet; + events->friend_lossy_packet_capacity = new_friend_lossy_packet_capacity; + } + + Tox_Event_Friend_Lossy_Packet *const friend_lossy_packet = + &events->friend_lossy_packet[events->friend_lossy_packet_size]; + tox_event_friend_lossy_packet_construct(friend_lossy_packet); + ++events->friend_lossy_packet_size; + return friend_lossy_packet; +} + +void tox_events_clear_friend_lossy_packet(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->friend_lossy_packet_size; ++i) { + tox_event_friend_lossy_packet_destruct(&events->friend_lossy_packet[i]); + } + + free(events->friend_lossy_packet); + events->friend_lossy_packet = nullptr; + events->friend_lossy_packet_size = 0; + events->friend_lossy_packet_capacity = 0; +} + +uint32_t tox_events_get_friend_lossy_packet_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->friend_lossy_packet_size; +} + +const Tox_Event_Friend_Lossy_Packet *tox_events_get_friend_lossy_packet(const Tox_Events *events, uint32_t index) +{ + assert(index < events->friend_lossy_packet_size); + assert(events->friend_lossy_packet != nullptr); + return &events->friend_lossy_packet[index]; +} + +bool tox_events_pack_friend_lossy_packet(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_friend_lossy_packet_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_friend_lossy_packet_pack(tox_events_get_friend_lossy_packet(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_friend_lossy_packet(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Friend_Lossy_Packet *event = tox_events_add_friend_lossy_packet(events); + + if (event == nullptr) { + return false; + } + + return tox_event_friend_lossy_packet_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_friend_lossy_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Friend_Lossy_Packet *friend_lossy_packet = tox_events_add_friend_lossy_packet(state->events); + + if (friend_lossy_packet == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_friend_lossy_packet_set_friend_number(friend_lossy_packet, friend_number); + tox_event_friend_lossy_packet_set_data(friend_lossy_packet, data, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/friend_message.m b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_message.m new file mode 100644 index 0000000..8fa6a19 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_message.m @@ -0,0 +1,248 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" +#include "../tox_unpack.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Friend_Message { + uint32_t friend_number; + Tox_Message_Type type; + uint8_t *message; + uint32_t message_length; +}; + +non_null() +static void tox_event_friend_message_construct(Tox_Event_Friend_Message *friend_message) +{ + *friend_message = (Tox_Event_Friend_Message) { + 0 + }; +} +non_null() +static void tox_event_friend_message_destruct(Tox_Event_Friend_Message *friend_message) +{ + free(friend_message->message); +} + +non_null() +static void tox_event_friend_message_set_friend_number(Tox_Event_Friend_Message *friend_message, + uint32_t friend_number) +{ + assert(friend_message != nullptr); + friend_message->friend_number = friend_number; +} +uint32_t tox_event_friend_message_get_friend_number(const Tox_Event_Friend_Message *friend_message) +{ + assert(friend_message != nullptr); + return friend_message->friend_number; +} + +non_null() +static void tox_event_friend_message_set_type(Tox_Event_Friend_Message *friend_message, Tox_Message_Type type) +{ + assert(friend_message != nullptr); + friend_message->type = type; +} +Tox_Message_Type tox_event_friend_message_get_type(const Tox_Event_Friend_Message *friend_message) +{ + assert(friend_message != nullptr); + return friend_message->type; +} + +non_null() +static bool tox_event_friend_message_set_message(Tox_Event_Friend_Message *friend_message, const uint8_t *message, + uint32_t message_length) +{ + assert(friend_message != nullptr); + + if (friend_message->message != nullptr) { + free(friend_message->message); + friend_message->message = nullptr; + friend_message->message_length = 0; + } + + friend_message->message = (uint8_t *)malloc(message_length); + + if (friend_message->message == nullptr) { + return false; + } + + memcpy(friend_message->message, message, message_length); + friend_message->message_length = message_length; + return true; +} +uint32_t tox_event_friend_message_get_message_length(const Tox_Event_Friend_Message *friend_message) +{ + assert(friend_message != nullptr); + return friend_message->message_length; +} +const uint8_t *tox_event_friend_message_get_message(const Tox_Event_Friend_Message *friend_message) +{ + assert(friend_message != nullptr); + return friend_message->message; +} + +non_null() +static bool tox_event_friend_message_pack( + const Tox_Event_Friend_Message *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FRIEND_MESSAGE) + && bin_pack_array(bp, 3) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_u32(bp, event->type) + && bin_pack_bin(bp, event->message, event->message_length); +} + +non_null() +static bool tox_event_friend_message_unpack( + Tox_Event_Friend_Message *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 3)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && tox_unpack_message_type(bu, &event->type) + && bin_unpack_bin(bu, &event->message, &event->message_length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Friend_Message *tox_events_add_friend_message(Tox_Events *events) +{ + if (events->friend_message_size == UINT32_MAX) { + return nullptr; + } + + if (events->friend_message_size == events->friend_message_capacity) { + const uint32_t new_friend_message_capacity = events->friend_message_capacity * 2 + 1; + Tox_Event_Friend_Message *new_friend_message = (Tox_Event_Friend_Message *)realloc( + events->friend_message, new_friend_message_capacity * sizeof(Tox_Event_Friend_Message)); + + if (new_friend_message == nullptr) { + return nullptr; + } + + events->friend_message = new_friend_message; + events->friend_message_capacity = new_friend_message_capacity; + } + + Tox_Event_Friend_Message *const friend_message = &events->friend_message[events->friend_message_size]; + tox_event_friend_message_construct(friend_message); + ++events->friend_message_size; + return friend_message; +} + +void tox_events_clear_friend_message(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->friend_message_size; ++i) { + tox_event_friend_message_destruct(&events->friend_message[i]); + } + + free(events->friend_message); + events->friend_message = nullptr; + events->friend_message_size = 0; + events->friend_message_capacity = 0; +} + +uint32_t tox_events_get_friend_message_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->friend_message_size; +} + +const Tox_Event_Friend_Message *tox_events_get_friend_message(const Tox_Events *events, uint32_t index) +{ + assert(index < events->friend_message_size); + assert(events->friend_message != nullptr); + return &events->friend_message[index]; +} + +bool tox_events_pack_friend_message(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_friend_message_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_friend_message_pack(tox_events_get_friend_message(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_friend_message(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Friend_Message *event = tox_events_add_friend_message(events); + + if (event == nullptr) { + return false; + } + + return tox_event_friend_message_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_friend_message(Tox *tox, uint32_t friend_number, Tox_Message_Type type, const uint8_t *message, + size_t length, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Friend_Message *friend_message = tox_events_add_friend_message(state->events); + + if (friend_message == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_friend_message_set_friend_number(friend_message, friend_number); + tox_event_friend_message_set_type(friend_message, type); + tox_event_friend_message_set_message(friend_message, message, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/friend_name.m b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_name.m new file mode 100644 index 0000000..1239183 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_name.m @@ -0,0 +1,231 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Friend_Name { + uint32_t friend_number; + uint8_t *name; + uint32_t name_length; +}; + +non_null() +static void tox_event_friend_name_construct(Tox_Event_Friend_Name *friend_name) +{ + *friend_name = (Tox_Event_Friend_Name) { + 0 + }; +} +non_null() +static void tox_event_friend_name_destruct(Tox_Event_Friend_Name *friend_name) +{ + free(friend_name->name); +} + +non_null() +static void tox_event_friend_name_set_friend_number(Tox_Event_Friend_Name *friend_name, + uint32_t friend_number) +{ + assert(friend_name != nullptr); + friend_name->friend_number = friend_number; +} +uint32_t tox_event_friend_name_get_friend_number(const Tox_Event_Friend_Name *friend_name) +{ + assert(friend_name != nullptr); + return friend_name->friend_number; +} + +non_null() +static bool tox_event_friend_name_set_name(Tox_Event_Friend_Name *friend_name, const uint8_t *name, + uint32_t name_length) +{ + assert(friend_name != nullptr); + + if (friend_name->name != nullptr) { + free(friend_name->name); + friend_name->name = nullptr; + friend_name->name_length = 0; + } + + friend_name->name = (uint8_t *)malloc(name_length); + + if (friend_name->name == nullptr) { + return false; + } + + memcpy(friend_name->name, name, name_length); + friend_name->name_length = name_length; + return true; +} +uint32_t tox_event_friend_name_get_name_length(const Tox_Event_Friend_Name *friend_name) +{ + assert(friend_name != nullptr); + return friend_name->name_length; +} +const uint8_t *tox_event_friend_name_get_name(const Tox_Event_Friend_Name *friend_name) +{ + assert(friend_name != nullptr); + return friend_name->name; +} + +non_null() +static bool tox_event_friend_name_pack( + const Tox_Event_Friend_Name *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FRIEND_NAME) + && bin_pack_array(bp, 2) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_bin(bp, event->name, event->name_length); +} + +non_null() +static bool tox_event_friend_name_unpack( + Tox_Event_Friend_Name *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 2)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && bin_unpack_bin(bu, &event->name, &event->name_length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Friend_Name *tox_events_add_friend_name(Tox_Events *events) +{ + if (events->friend_name_size == UINT32_MAX) { + return nullptr; + } + + if (events->friend_name_size == events->friend_name_capacity) { + const uint32_t new_friend_name_capacity = events->friend_name_capacity * 2 + 1; + Tox_Event_Friend_Name *new_friend_name = (Tox_Event_Friend_Name *)realloc( + events->friend_name, new_friend_name_capacity * sizeof(Tox_Event_Friend_Name)); + + if (new_friend_name == nullptr) { + return nullptr; + } + + events->friend_name = new_friend_name; + events->friend_name_capacity = new_friend_name_capacity; + } + + Tox_Event_Friend_Name *const friend_name = &events->friend_name[events->friend_name_size]; + tox_event_friend_name_construct(friend_name); + ++events->friend_name_size; + return friend_name; +} + +void tox_events_clear_friend_name(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->friend_name_size; ++i) { + tox_event_friend_name_destruct(&events->friend_name[i]); + } + + free(events->friend_name); + events->friend_name = nullptr; + events->friend_name_size = 0; + events->friend_name_capacity = 0; +} + +uint32_t tox_events_get_friend_name_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->friend_name_size; +} + +const Tox_Event_Friend_Name *tox_events_get_friend_name(const Tox_Events *events, uint32_t index) +{ + assert(index < events->friend_name_size); + assert(events->friend_name != nullptr); + return &events->friend_name[index]; +} + +bool tox_events_pack_friend_name(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_friend_name_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_friend_name_pack(tox_events_get_friend_name(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_friend_name(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Friend_Name *event = tox_events_add_friend_name(events); + + if (event == nullptr) { + return false; + } + + return tox_event_friend_name_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_friend_name(Tox *tox, uint32_t friend_number, const uint8_t *name, size_t length, + void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Friend_Name *friend_name = tox_events_add_friend_name(state->events); + + if (friend_name == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_friend_name_set_friend_number(friend_name, friend_number); + tox_event_friend_name_set_name(friend_name, name, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/friend_read_receipt.m b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_read_receipt.m new file mode 100644 index 0000000..1485b67 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_read_receipt.m @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Friend_Read_Receipt { + uint32_t friend_number; + uint32_t message_id; +}; + +non_null() +static void tox_event_friend_read_receipt_construct(Tox_Event_Friend_Read_Receipt *friend_read_receipt) +{ + *friend_read_receipt = (Tox_Event_Friend_Read_Receipt) { + 0 + }; +} +non_null() +static void tox_event_friend_read_receipt_destruct(Tox_Event_Friend_Read_Receipt *friend_read_receipt) +{ + return; +} + +non_null() +static void tox_event_friend_read_receipt_set_friend_number(Tox_Event_Friend_Read_Receipt *friend_read_receipt, + uint32_t friend_number) +{ + assert(friend_read_receipt != nullptr); + friend_read_receipt->friend_number = friend_number; +} +uint32_t tox_event_friend_read_receipt_get_friend_number(const Tox_Event_Friend_Read_Receipt *friend_read_receipt) +{ + assert(friend_read_receipt != nullptr); + return friend_read_receipt->friend_number; +} + +non_null() +static void tox_event_friend_read_receipt_set_message_id(Tox_Event_Friend_Read_Receipt *friend_read_receipt, + uint32_t message_id) +{ + assert(friend_read_receipt != nullptr); + friend_read_receipt->message_id = message_id; +} +uint32_t tox_event_friend_read_receipt_get_message_id(const Tox_Event_Friend_Read_Receipt *friend_read_receipt) +{ + assert(friend_read_receipt != nullptr); + return friend_read_receipt->message_id; +} + +non_null() +static bool tox_event_friend_read_receipt_pack( + const Tox_Event_Friend_Read_Receipt *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FRIEND_READ_RECEIPT) + && bin_pack_array(bp, 2) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_u32(bp, event->message_id); +} + +non_null() +static bool tox_event_friend_read_receipt_unpack( + Tox_Event_Friend_Read_Receipt *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 2)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && bin_unpack_u32(bu, &event->message_id); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Friend_Read_Receipt *tox_events_add_friend_read_receipt(Tox_Events *events) +{ + if (events->friend_read_receipt_size == UINT32_MAX) { + return nullptr; + } + + if (events->friend_read_receipt_size == events->friend_read_receipt_capacity) { + const uint32_t new_friend_read_receipt_capacity = events->friend_read_receipt_capacity * 2 + 1; + Tox_Event_Friend_Read_Receipt *new_friend_read_receipt = (Tox_Event_Friend_Read_Receipt *)realloc( + events->friend_read_receipt, new_friend_read_receipt_capacity * sizeof(Tox_Event_Friend_Read_Receipt)); + + if (new_friend_read_receipt == nullptr) { + return nullptr; + } + + events->friend_read_receipt = new_friend_read_receipt; + events->friend_read_receipt_capacity = new_friend_read_receipt_capacity; + } + + Tox_Event_Friend_Read_Receipt *const friend_read_receipt = + &events->friend_read_receipt[events->friend_read_receipt_size]; + tox_event_friend_read_receipt_construct(friend_read_receipt); + ++events->friend_read_receipt_size; + return friend_read_receipt; +} + +void tox_events_clear_friend_read_receipt(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->friend_read_receipt_size; ++i) { + tox_event_friend_read_receipt_destruct(&events->friend_read_receipt[i]); + } + + free(events->friend_read_receipt); + events->friend_read_receipt = nullptr; + events->friend_read_receipt_size = 0; + events->friend_read_receipt_capacity = 0; +} + +uint32_t tox_events_get_friend_read_receipt_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->friend_read_receipt_size; +} + +const Tox_Event_Friend_Read_Receipt *tox_events_get_friend_read_receipt(const Tox_Events *events, uint32_t index) +{ + assert(index < events->friend_read_receipt_size); + assert(events->friend_read_receipt != nullptr); + return &events->friend_read_receipt[index]; +} + +bool tox_events_pack_friend_read_receipt(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_friend_read_receipt_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_friend_read_receipt_pack(tox_events_get_friend_read_receipt(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_friend_read_receipt(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Friend_Read_Receipt *event = tox_events_add_friend_read_receipt(events); + + if (event == nullptr) { + return false; + } + + return tox_event_friend_read_receipt_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_friend_read_receipt(Tox *tox, uint32_t friend_number, uint32_t message_id, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Friend_Read_Receipt *friend_read_receipt = tox_events_add_friend_read_receipt(state->events); + + if (friend_read_receipt == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_friend_read_receipt_set_friend_number(friend_read_receipt, friend_number); + tox_event_friend_read_receipt_set_message_id(friend_read_receipt, message_id); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/friend_request.m b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_request.m new file mode 100644 index 0000000..3366f54 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_request.m @@ -0,0 +1,232 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Friend_Request { + uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; + uint8_t *message; + uint32_t message_length; +}; + +non_null() +static void tox_event_friend_request_construct(Tox_Event_Friend_Request *friend_request) +{ + *friend_request = (Tox_Event_Friend_Request) { + 0 + }; +} +non_null() +static void tox_event_friend_request_destruct(Tox_Event_Friend_Request *friend_request) +{ + free(friend_request->message); +} + +non_null() +static bool tox_event_friend_request_set_public_key(Tox_Event_Friend_Request *friend_request, const uint8_t *public_key) +{ + assert(friend_request != nullptr); + + memcpy(friend_request->public_key, public_key, TOX_PUBLIC_KEY_SIZE); + return true; +} +const uint8_t *tox_event_friend_request_get_public_key(const Tox_Event_Friend_Request *friend_request) +{ + assert(friend_request != nullptr); + return friend_request->public_key; +} + +non_null() +static bool tox_event_friend_request_set_message(Tox_Event_Friend_Request *friend_request, const uint8_t *message, + uint32_t message_length) +{ + assert(friend_request != nullptr); + + if (friend_request->message != nullptr) { + free(friend_request->message); + friend_request->message = nullptr; + friend_request->message_length = 0; + } + + friend_request->message = (uint8_t *)malloc(message_length); + + if (friend_request->message == nullptr) { + return false; + } + + memcpy(friend_request->message, message, message_length); + friend_request->message_length = message_length; + return true; +} +uint32_t tox_event_friend_request_get_message_length(const Tox_Event_Friend_Request *friend_request) +{ + assert(friend_request != nullptr); + return friend_request->message_length; +} +const uint8_t *tox_event_friend_request_get_message(const Tox_Event_Friend_Request *friend_request) +{ + assert(friend_request != nullptr); + return friend_request->message; +} + +non_null() +static bool tox_event_friend_request_pack( + const Tox_Event_Friend_Request *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FRIEND_REQUEST) + && bin_pack_array(bp, 2) + && bin_pack_bin(bp, event->public_key, TOX_PUBLIC_KEY_SIZE) + && bin_pack_bin(bp, event->message, event->message_length); +} + +non_null() +static bool tox_event_friend_request_unpack( + Tox_Event_Friend_Request *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 2)) { + return false; + } + + return bin_unpack_bin_fixed(bu, event->public_key, TOX_PUBLIC_KEY_SIZE) + && bin_unpack_bin(bu, &event->message, &event->message_length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Friend_Request *tox_events_add_friend_request(Tox_Events *events) +{ + if (events->friend_request_size == UINT32_MAX) { + return nullptr; + } + + if (events->friend_request_size == events->friend_request_capacity) { + const uint32_t new_friend_request_capacity = events->friend_request_capacity * 2 + 1; + Tox_Event_Friend_Request *new_friend_request = (Tox_Event_Friend_Request *)realloc( + events->friend_request, new_friend_request_capacity * sizeof(Tox_Event_Friend_Request)); + + if (new_friend_request == nullptr) { + return nullptr; + } + + events->friend_request = new_friend_request; + events->friend_request_capacity = new_friend_request_capacity; + } + + Tox_Event_Friend_Request *const friend_request = &events->friend_request[events->friend_request_size]; + tox_event_friend_request_construct(friend_request); + ++events->friend_request_size; + return friend_request; +} + +void tox_events_clear_friend_request(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->friend_request_size; ++i) { + tox_event_friend_request_destruct(&events->friend_request[i]); + } + + free(events->friend_request); + events->friend_request = nullptr; + events->friend_request_size = 0; + events->friend_request_capacity = 0; +} + +uint32_t tox_events_get_friend_request_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->friend_request_size; +} + +const Tox_Event_Friend_Request *tox_events_get_friend_request(const Tox_Events *events, uint32_t index) +{ + assert(index < events->friend_request_size); + assert(events->friend_request != nullptr); + return &events->friend_request[index]; +} + +bool tox_events_pack_friend_request(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_friend_request_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_friend_request_pack(tox_events_get_friend_request(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_friend_request(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Friend_Request *event = tox_events_add_friend_request(events); + + if (event == nullptr) { + return false; + } + + return tox_event_friend_request_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_friend_request(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, + void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Friend_Request *friend_request = tox_events_add_friend_request(state->events); + + if (friend_request == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_friend_request_set_public_key(friend_request, public_key); + tox_event_friend_request_set_message(friend_request, message, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/friend_status.m b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_status.m new file mode 100644 index 0000000..c6eabda --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_status.m @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include +#include "../bin_unpack.h" + +#include "../bin_pack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" +#include "../tox_unpack.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Friend_Status { + uint32_t friend_number; + Tox_User_Status status; +}; + +non_null() +static void tox_event_friend_status_construct(Tox_Event_Friend_Status *friend_status) +{ + *friend_status = (Tox_Event_Friend_Status) { + 0 + }; +} +non_null() +static void tox_event_friend_status_destruct(Tox_Event_Friend_Status *friend_status) +{ + return; +} + +non_null() +static void tox_event_friend_status_set_friend_number(Tox_Event_Friend_Status *friend_status, + uint32_t friend_number) +{ + assert(friend_status != nullptr); + friend_status->friend_number = friend_number; +} +uint32_t tox_event_friend_status_get_friend_number(const Tox_Event_Friend_Status *friend_status) +{ + assert(friend_status != nullptr); + return friend_status->friend_number; +} + +non_null() +static void tox_event_friend_status_set_status(Tox_Event_Friend_Status *friend_status, + Tox_User_Status status) +{ + assert(friend_status != nullptr); + friend_status->status = status; +} +Tox_User_Status tox_event_friend_status_get_status(const Tox_Event_Friend_Status *friend_status) +{ + assert(friend_status != nullptr); + return friend_status->status; +} + +non_null() +static bool tox_event_friend_status_pack( + const Tox_Event_Friend_Status *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FRIEND_STATUS) + && bin_pack_array(bp, 2) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_u32(bp, event->status); +} + +non_null() +static bool tox_event_friend_status_unpack( + Tox_Event_Friend_Status *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 2)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && tox_unpack_user_status(bu, &event->status); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Friend_Status *tox_events_add_friend_status(Tox_Events *events) +{ + if (events->friend_status_size == UINT32_MAX) { + return nullptr; + } + + if (events->friend_status_size == events->friend_status_capacity) { + const uint32_t new_friend_status_capacity = events->friend_status_capacity * 2 + 1; + Tox_Event_Friend_Status *new_friend_status = (Tox_Event_Friend_Status *)realloc( + events->friend_status, new_friend_status_capacity * sizeof(Tox_Event_Friend_Status)); + + if (new_friend_status == nullptr) { + return nullptr; + } + + events->friend_status = new_friend_status; + events->friend_status_capacity = new_friend_status_capacity; + } + + Tox_Event_Friend_Status *const friend_status = &events->friend_status[events->friend_status_size]; + tox_event_friend_status_construct(friend_status); + ++events->friend_status_size; + return friend_status; +} + +void tox_events_clear_friend_status(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->friend_status_size; ++i) { + tox_event_friend_status_destruct(&events->friend_status[i]); + } + + free(events->friend_status); + events->friend_status = nullptr; + events->friend_status_size = 0; + events->friend_status_capacity = 0; +} + +uint32_t tox_events_get_friend_status_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->friend_status_size; +} + +const Tox_Event_Friend_Status *tox_events_get_friend_status(const Tox_Events *events, uint32_t index) +{ + assert(index < events->friend_status_size); + assert(events->friend_status != nullptr); + return &events->friend_status[index]; +} + +bool tox_events_pack_friend_status(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_friend_status_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_friend_status_pack(tox_events_get_friend_status(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_friend_status(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Friend_Status *event = tox_events_add_friend_status(events); + + if (event == nullptr) { + return false; + } + + return tox_event_friend_status_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_friend_status(Tox *tox, uint32_t friend_number, Tox_User_Status status, + void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Friend_Status *friend_status = tox_events_add_friend_status(state->events); + + if (friend_status == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_friend_status_set_friend_number(friend_status, friend_number); + tox_event_friend_status_set_status(friend_status, status); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/friend_status_message.m b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_status_message.m new file mode 100644 index 0000000..91fa88e --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_status_message.m @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Friend_Status_Message { + uint32_t friend_number; + uint8_t *message; + uint32_t message_length; +}; + +non_null() +static void tox_event_friend_status_message_construct(Tox_Event_Friend_Status_Message *friend_status_message) +{ + *friend_status_message = (Tox_Event_Friend_Status_Message) { + 0 + }; +} +non_null() +static void tox_event_friend_status_message_destruct(Tox_Event_Friend_Status_Message *friend_status_message) +{ + free(friend_status_message->message); +} + +non_null() +static void tox_event_friend_status_message_set_friend_number(Tox_Event_Friend_Status_Message *friend_status_message, + uint32_t friend_number) +{ + assert(friend_status_message != nullptr); + friend_status_message->friend_number = friend_number; +} +uint32_t tox_event_friend_status_message_get_friend_number(const Tox_Event_Friend_Status_Message *friend_status_message) +{ + assert(friend_status_message != nullptr); + return friend_status_message->friend_number; +} + +non_null() +static bool tox_event_friend_status_message_set_message(Tox_Event_Friend_Status_Message *friend_status_message, + const uint8_t *message, uint32_t message_length) +{ + assert(friend_status_message != nullptr); + + if (friend_status_message->message != nullptr) { + free(friend_status_message->message); + friend_status_message->message = nullptr; + friend_status_message->message_length = 0; + } + + friend_status_message->message = (uint8_t *)malloc(message_length); + + if (friend_status_message->message == nullptr) { + return false; + } + + memcpy(friend_status_message->message, message, message_length); + friend_status_message->message_length = message_length; + return true; +} +uint32_t tox_event_friend_status_message_get_message_length(const Tox_Event_Friend_Status_Message + *friend_status_message) +{ + assert(friend_status_message != nullptr); + return friend_status_message->message_length; +} +const uint8_t *tox_event_friend_status_message_get_message(const Tox_Event_Friend_Status_Message + *friend_status_message) +{ + assert(friend_status_message != nullptr); + return friend_status_message->message; +} + +non_null() +static bool tox_event_friend_status_message_pack( + const Tox_Event_Friend_Status_Message *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FRIEND_STATUS_MESSAGE) + && bin_pack_array(bp, 2) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_bin(bp, event->message, event->message_length); +} + +non_null() +static bool tox_event_friend_status_message_unpack( + Tox_Event_Friend_Status_Message *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 2)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && bin_unpack_bin(bu, &event->message, &event->message_length); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Friend_Status_Message *tox_events_add_friend_status_message(Tox_Events *events) +{ + if (events->friend_status_message_size == UINT32_MAX) { + return nullptr; + } + + if (events->friend_status_message_size == events->friend_status_message_capacity) { + const uint32_t new_friend_status_message_capacity = events->friend_status_message_capacity * 2 + 1; + Tox_Event_Friend_Status_Message *new_friend_status_message = (Tox_Event_Friend_Status_Message *)realloc( + events->friend_status_message, new_friend_status_message_capacity * sizeof(Tox_Event_Friend_Status_Message)); + + if (new_friend_status_message == nullptr) { + return nullptr; + } + + events->friend_status_message = new_friend_status_message; + events->friend_status_message_capacity = new_friend_status_message_capacity; + } + + Tox_Event_Friend_Status_Message *const friend_status_message = + &events->friend_status_message[events->friend_status_message_size]; + tox_event_friend_status_message_construct(friend_status_message); + ++events->friend_status_message_size; + return friend_status_message; +} + +void tox_events_clear_friend_status_message(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->friend_status_message_size; ++i) { + tox_event_friend_status_message_destruct(&events->friend_status_message[i]); + } + + free(events->friend_status_message); + events->friend_status_message = nullptr; + events->friend_status_message_size = 0; + events->friend_status_message_capacity = 0; +} + +uint32_t tox_events_get_friend_status_message_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->friend_status_message_size; +} + +const Tox_Event_Friend_Status_Message *tox_events_get_friend_status_message(const Tox_Events *events, uint32_t index) +{ + assert(index < events->friend_status_message_size); + assert(events->friend_status_message != nullptr); + return &events->friend_status_message[index]; +} + +bool tox_events_pack_friend_status_message(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_friend_status_message_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_friend_status_message_pack(tox_events_get_friend_status_message(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_friend_status_message(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Friend_Status_Message *event = tox_events_add_friend_status_message(events); + + if (event == nullptr) { + return false; + } + + return tox_event_friend_status_message_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_friend_status_message(Tox *tox, uint32_t friend_number, const uint8_t *message, + size_t length, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Friend_Status_Message *friend_status_message = tox_events_add_friend_status_message(state->events); + + if (friend_status_message == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_friend_status_message_set_friend_number(friend_status_message, friend_number); + tox_event_friend_status_message_set_message(friend_status_message, message, length); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/friend_typing.m b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_typing.m new file mode 100644 index 0000000..83c4dba --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/friend_typing.m @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Friend_Typing { + uint32_t friend_number; + bool typing; +}; + +non_null() +static void tox_event_friend_typing_construct(Tox_Event_Friend_Typing *friend_typing) +{ + *friend_typing = (Tox_Event_Friend_Typing) { + 0 + }; +} +non_null() +static void tox_event_friend_typing_destruct(Tox_Event_Friend_Typing *friend_typing) +{ + return; +} + +non_null() +static void tox_event_friend_typing_set_friend_number(Tox_Event_Friend_Typing *friend_typing, + uint32_t friend_number) +{ + assert(friend_typing != nullptr); + friend_typing->friend_number = friend_number; +} +uint32_t tox_event_friend_typing_get_friend_number(const Tox_Event_Friend_Typing *friend_typing) +{ + assert(friend_typing != nullptr); + return friend_typing->friend_number; +} + +non_null() +static void tox_event_friend_typing_set_typing(Tox_Event_Friend_Typing *friend_typing, bool typing) +{ + assert(friend_typing != nullptr); + friend_typing->typing = typing; +} +bool tox_event_friend_typing_get_typing(const Tox_Event_Friend_Typing *friend_typing) +{ + assert(friend_typing != nullptr); + return friend_typing->typing; +} + +non_null() +static bool tox_event_friend_typing_pack( + const Tox_Event_Friend_Typing *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_FRIEND_TYPING) + && bin_pack_array(bp, 2) + && bin_pack_u32(bp, event->friend_number) + && bin_pack_bool(bp, event->typing); +} + +non_null() +static bool tox_event_friend_typing_unpack( + Tox_Event_Friend_Typing *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + if (!bin_unpack_array_fixed(bu, 2)) { + return false; + } + + return bin_unpack_u32(bu, &event->friend_number) + && bin_unpack_bool(bu, &event->typing); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Friend_Typing *tox_events_add_friend_typing(Tox_Events *events) +{ + if (events->friend_typing_size == UINT32_MAX) { + return nullptr; + } + + if (events->friend_typing_size == events->friend_typing_capacity) { + const uint32_t new_friend_typing_capacity = events->friend_typing_capacity * 2 + 1; + Tox_Event_Friend_Typing *new_friend_typing = (Tox_Event_Friend_Typing *)realloc( + events->friend_typing, new_friend_typing_capacity * sizeof(Tox_Event_Friend_Typing)); + + if (new_friend_typing == nullptr) { + return nullptr; + } + + events->friend_typing = new_friend_typing; + events->friend_typing_capacity = new_friend_typing_capacity; + } + + Tox_Event_Friend_Typing *const friend_typing = &events->friend_typing[events->friend_typing_size]; + tox_event_friend_typing_construct(friend_typing); + ++events->friend_typing_size; + return friend_typing; +} + +void tox_events_clear_friend_typing(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->friend_typing_size; ++i) { + tox_event_friend_typing_destruct(&events->friend_typing[i]); + } + + free(events->friend_typing); + events->friend_typing = nullptr; + events->friend_typing_size = 0; + events->friend_typing_capacity = 0; +} + +uint32_t tox_events_get_friend_typing_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->friend_typing_size; +} + +const Tox_Event_Friend_Typing *tox_events_get_friend_typing(const Tox_Events *events, uint32_t index) +{ + assert(index < events->friend_typing_size); + assert(events->friend_typing != nullptr); + return &events->friend_typing[index]; +} + +bool tox_events_pack_friend_typing(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_friend_typing_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_friend_typing_pack(tox_events_get_friend_typing(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_friend_typing(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Friend_Typing *event = tox_events_add_friend_typing(events); + + if (event == nullptr) { + return false; + } + + return tox_event_friend_typing_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_friend_typing(Tox *tox, uint32_t friend_number, bool typing, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Friend_Typing *friend_typing = tox_events_add_friend_typing(state->events); + + if (friend_typing == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_friend_typing_set_friend_number(friend_typing, friend_number); + tox_event_friend_typing_set_typing(friend_typing, typing); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/events/self_connection_status.m b/local_pod_repo/toxcore/toxcore/toxcore/events/self_connection_status.m new file mode 100644 index 0000000..0e381de --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/events/self_connection_status.m @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "events_alloc.h" + +#include +#include +#include + +#include "../bin_pack.h" +#include "../bin_unpack.h" +#include "../ccompat.h" +#include "../tox.h" +#include "../tox_events.h" +#include "../tox_unpack.h" + + +/***************************************************** + * + * :: struct and accessors + * + *****************************************************/ + + +struct Tox_Event_Self_Connection_Status { + Tox_Connection connection_status; +}; + +non_null() +static void tox_event_self_connection_status_construct(Tox_Event_Self_Connection_Status *self_connection_status) +{ + *self_connection_status = (Tox_Event_Self_Connection_Status) { + TOX_CONNECTION_NONE + }; +} +non_null() +static void tox_event_self_connection_status_destruct(Tox_Event_Self_Connection_Status *self_connection_status) +{ + return; +} + +non_null() +static void tox_event_self_connection_status_set_connection_status(Tox_Event_Self_Connection_Status + *self_connection_status, Tox_Connection connection_status) +{ + assert(self_connection_status != nullptr); + self_connection_status->connection_status = connection_status; +} +Tox_Connection tox_event_self_connection_status_get_connection_status(const Tox_Event_Self_Connection_Status + *self_connection_status) +{ + assert(self_connection_status != nullptr); + return self_connection_status->connection_status; +} + +non_null() +static bool tox_event_self_connection_status_pack( + const Tox_Event_Self_Connection_Status *event, Bin_Pack *bp) +{ + assert(event != nullptr); + return bin_pack_array(bp, 2) + && bin_pack_u32(bp, TOX_EVENT_SELF_CONNECTION_STATUS) + && bin_pack_u32(bp, event->connection_status); +} + +non_null() +static bool tox_event_self_connection_status_unpack( + Tox_Event_Self_Connection_Status *event, Bin_Unpack *bu) +{ + assert(event != nullptr); + return tox_unpack_connection(bu, &event->connection_status); +} + + +/***************************************************** + * + * :: add/clear/get + * + *****************************************************/ + + +non_null() +static Tox_Event_Self_Connection_Status *tox_events_add_self_connection_status(Tox_Events *events) +{ + if (events->self_connection_status_size == UINT32_MAX) { + return nullptr; + } + + if (events->self_connection_status_size == events->self_connection_status_capacity) { + const uint32_t new_self_connection_status_capacity = events->self_connection_status_capacity * 2 + 1; + Tox_Event_Self_Connection_Status *new_self_connection_status = (Tox_Event_Self_Connection_Status *)realloc( + events->self_connection_status, new_self_connection_status_capacity * sizeof(Tox_Event_Self_Connection_Status)); + + if (new_self_connection_status == nullptr) { + return nullptr; + } + + events->self_connection_status = new_self_connection_status; + events->self_connection_status_capacity = new_self_connection_status_capacity; + } + + Tox_Event_Self_Connection_Status *const self_connection_status = + &events->self_connection_status[events->self_connection_status_size]; + tox_event_self_connection_status_construct(self_connection_status); + ++events->self_connection_status_size; + return self_connection_status; +} + +void tox_events_clear_self_connection_status(Tox_Events *events) +{ + if (events == nullptr) { + return; + } + + for (uint32_t i = 0; i < events->self_connection_status_size; ++i) { + tox_event_self_connection_status_destruct(&events->self_connection_status[i]); + } + + free(events->self_connection_status); + events->self_connection_status = nullptr; + events->self_connection_status_size = 0; + events->self_connection_status_capacity = 0; +} + +uint32_t tox_events_get_self_connection_status_size(const Tox_Events *events) +{ + if (events == nullptr) { + return 0; + } + + return events->self_connection_status_size; +} + +const Tox_Event_Self_Connection_Status *tox_events_get_self_connection_status(const Tox_Events *events, uint32_t index) +{ + assert(index < events->self_connection_status_size); + assert(events->self_connection_status != nullptr); + return &events->self_connection_status[index]; +} + +bool tox_events_pack_self_connection_status(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t size = tox_events_get_self_connection_status_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_self_connection_status_pack(tox_events_get_self_connection_status(events, i), bp)) { + return false; + } + } + return true; +} + +bool tox_events_unpack_self_connection_status(Tox_Events *events, Bin_Unpack *bu) +{ + Tox_Event_Self_Connection_Status *event = tox_events_add_self_connection_status(events); + + if (event == nullptr) { + return false; + } + + return tox_event_self_connection_status_unpack(event, bu); +} + + +/***************************************************** + * + * :: event handler + * + *****************************************************/ + + +void tox_events_handle_self_connection_status(Tox *tox, Tox_Connection connection_status, void *user_data) +{ + Tox_Events_State *state = tox_events_alloc(user_data); + assert(state != nullptr); + + if (state->events == nullptr) { + return; + } + + Tox_Event_Self_Connection_Status *self_connection_status = tox_events_add_self_connection_status(state->events); + + if (self_connection_status == nullptr) { + state->error = TOX_ERR_EVENTS_ITERATE_MALLOC; + return; + } + + tox_event_self_connection_status_set_connection_status(self_connection_status, connection_status); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/forwarding.h b/local_pod_repo/toxcore/toxcore/toxcore/forwarding.h new file mode 100644 index 0000000..36ce8ad --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/forwarding.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2019-2022 The TokTok team. + */ + +#ifndef C_TOXCORE_TOXCORE_FORWARDING_H +#define C_TOXCORE_TOXCORE_FORWARDING_H + +#include "DHT.h" +#include "network.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SENDBACK_IPPORT 0 +#define SENDBACK_FORWARD 1 +#define SENDBACK_TCP 2 + +#define MAX_SENDBACK_SIZE (0xff - 1) +#define MAX_FORWARD_DATA_SIZE (MAX_UDP_PACKET_SIZE - (1 + 1 + MAX_SENDBACK_SIZE)) + +#define MAX_FORWARD_CHAIN_LENGTH 4 + +#define MAX_PACKED_IPPORT_SIZE (1 + SIZE_IP6 + sizeof(uint16_t)) + +typedef struct Forwarding Forwarding; + +non_null() +DHT *forwarding_get_dht(Forwarding *forwarding); + +/** + * @brief Send data to forwarder for forwarding via chain of dht nodes. + * Destination is last key in the chain. + * + * @param data Must be of length at most MAX_FORWARD_DATA_SIZE. + * @param chain_length Number of intermediate nodes in chain. + * Must be at least 1 and at most MAX_FORWARD_CHAIN_LENGTH. + * @param chain_keys Public keys of chain nodes. Must be of length + * `chain_length * CRYPTO_PUBLIC_KEY_SIZE`. + * + * @return true on success, false otherwise. + */ +non_null() +bool send_forward_request(Networking_Core *net, const IP_Port *forwarder, + const uint8_t *chain_keys, uint16_t chain_length, + const uint8_t *data, uint16_t data_length); + +/** Returns size of packet written by create_forward_chain_packet. */ +uint16_t forward_chain_packet_size(uint16_t chain_length, uint16_t data_length); + +/** + * @brief Create forward request packet for forwarding data via chain of dht nodes. + * Destination is last key in the chain. + * + * @param data Must be of length at most MAX_FORWARD_DATA_SIZE. + * @param chain_length Number of intermediate nodes in chain. + * Must be at least 1 and at most MAX_FORWARD_CHAIN_LENGTH. + * @param chain_keys Public keys of chain nodes. Must be of length + * `chain_length * CRYPTO_PUBLIC_KEY_SIZE`. + * @param packet Must be of size at least + * `forward_chain_packet_size(chain_length, data_length)` bytes. + * + * @return true on success, false otherwise. + */ +non_null() +bool create_forward_chain_packet(const uint8_t *chain_keys, uint16_t chain_length, + const uint8_t *data, uint16_t data_length, + uint8_t *packet); + +/** + * @brief Send reply to forwarded packet via forwarder. + * @param sendback Must be of size at most MAX_SENDBACK_SIZE. + * @param data Must be of size at most MAX_FORWARD_DATA_SIZE. + * + * @return true on success, false otherwise. + */ +non_null() +bool forward_reply(Networking_Core *net, const IP_Port *forwarder, + const uint8_t *sendback, uint16_t sendback_length, + const uint8_t *data, uint16_t length); + + +/** + * @brief Set callback to handle a forwarded request. + * To reply to the packet, callback should use `forward_reply()` to send a reply + * forwarded via forwarder, passing the provided sendback. + */ +typedef void forwarded_request_cb(void *object, const IP_Port *forwarder, const uint8_t *sendback, + uint16_t sendback_length, const uint8_t *data, + uint16_t length, void *userdata); +non_null(1) nullable(2, 3) +void set_callback_forwarded_request(Forwarding *forwarding, forwarded_request_cb *function, void *object); + +/** @brief Set callback to handle a forwarded response. */ +typedef void forwarded_response_cb(void *object, const uint8_t *data, uint16_t length, void *userdata); +non_null(1) nullable(2, 3) +void set_callback_forwarded_response(Forwarding *forwarding, forwarded_response_cb *function, void *object); + +/** @brief Send forwarding packet to dest with given sendback data and data. */ +non_null(1, 2, 5) nullable(3) +bool send_forwarding(const Forwarding *forwarding, const IP_Port *dest, + const uint8_t *sendback_data, uint16_t sendback_data_len, + const uint8_t *data, uint16_t length); + +typedef bool forward_reply_cb(void *object, const uint8_t *sendback_data, uint16_t sendback_data_len, + const uint8_t *data, uint16_t length); + +/** + * @brief Set callback to handle a forward reply with an otherwise unhandled + * sendback. + */ +non_null(1) nullable(2, 3) +void set_callback_forward_reply(Forwarding *forwarding, forward_reply_cb *function, void *object); + +non_null() +Forwarding *new_forwarding(const Logger *log, const Random *rng, const Mono_Time *mono_time, DHT *dht); + +nullable(1) +void kill_forwarding(Forwarding *forwarding); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/forwarding.m b/local_pod_repo/toxcore/toxcore/toxcore/forwarding.m new file mode 100644 index 0000000..5e885ab --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/forwarding.m @@ -0,0 +1,395 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2019-2022 The TokTok team. + */ + +#include "forwarding.h" + +#include +#include +#include + +#include "DHT.h" +#include "ccompat.h" +#include "timed_auth.h" + +struct Forwarding { + const Logger *log; + const Random *rng; + DHT *dht; + const Mono_Time *mono_time; + Networking_Core *net; + + uint8_t hmac_key[CRYPTO_HMAC_KEY_SIZE]; + + forward_reply_cb *forward_reply_callback; + void *forward_reply_callback_object; + + forwarded_request_cb *forwarded_request_callback; + void *forwarded_request_callback_object; + + forwarded_response_cb *forwarded_response_callback; + void *forwarded_response_callback_object; +}; + +DHT *forwarding_get_dht(Forwarding *forwarding) +{ + return forwarding->dht; +} + +#define SENDBACK_TIMEOUT 3600 + +bool send_forward_request(Networking_Core *net, const IP_Port *forwarder, + const uint8_t *chain_keys, uint16_t chain_length, + const uint8_t *data, uint16_t data_length) +{ + if (chain_length == 0 || chain_length > MAX_FORWARD_CHAIN_LENGTH + || data_length > MAX_FORWARD_DATA_SIZE) { + return false; + } + + const uint16_t len = forward_chain_packet_size(chain_length, data_length); + VLA(uint8_t, packet, len); + + return create_forward_chain_packet(chain_keys, chain_length, data, data_length, packet) + && sendpacket(net, forwarder, packet, len) == len; +} + +uint16_t forward_chain_packet_size(uint16_t chain_length, uint16_t data_length) +{ + return chain_length * (1 + CRYPTO_PUBLIC_KEY_SIZE) + data_length; +} + +bool create_forward_chain_packet(const uint8_t *chain_keys, uint16_t chain_length, + const uint8_t *data, uint16_t data_length, + uint8_t *packet) +{ + if (chain_length == 0 || chain_length > MAX_FORWARD_CHAIN_LENGTH + || data_length > MAX_FORWARD_DATA_SIZE) { + return false; + } + + uint16_t offset = 0; + + for (uint16_t j = 0; j < chain_length; ++j) { + packet[offset] = NET_PACKET_FORWARD_REQUEST; + ++offset; + memcpy(packet + offset, chain_keys + j * CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); + offset += CRYPTO_PUBLIC_KEY_SIZE; + } + + memcpy(packet + offset, data, data_length); + return true; +} + +non_null() +static uint16_t forwarding_packet_length(uint16_t sendback_data_len, uint16_t data_length) +{ + const uint16_t sendback_len = sendback_data_len == 0 ? 0 : TIMED_AUTH_SIZE + sendback_data_len; + return 1 + 1 + sendback_len + data_length; +} + +non_null(1, 4, 6) nullable(2) +static bool create_forwarding_packet(const Forwarding *forwarding, + const uint8_t *sendback_data, uint16_t sendback_data_len, + const uint8_t *data, uint16_t length, + uint8_t *packet) +{ + packet[0] = NET_PACKET_FORWARDING; + + if (sendback_data_len == 0) { + packet[1] = 0; + memcpy(packet + 1 + 1, data, length); + } else { + const uint16_t sendback_len = TIMED_AUTH_SIZE + sendback_data_len; + + if (sendback_len > MAX_SENDBACK_SIZE) { + return false; + } + + packet[1] = sendback_len; + generate_timed_auth(forwarding->mono_time, SENDBACK_TIMEOUT, forwarding->hmac_key, sendback_data, + sendback_data_len, packet + 1 + 1); + + if (sendback_data_len != 0) { + assert(sendback_data != nullptr); + memcpy(packet + 1 + 1 + TIMED_AUTH_SIZE, sendback_data, sendback_data_len); + } + + memcpy(packet + 1 + 1 + sendback_len, data, length); + } + + return true; +} + +bool send_forwarding(const Forwarding *forwarding, const IP_Port *dest, + const uint8_t *sendback_data, uint16_t sendback_data_len, + const uint8_t *data, uint16_t length) +{ + if (length > MAX_FORWARD_DATA_SIZE) { + return false; + } + + const uint16_t len = forwarding_packet_length(sendback_data_len, length); + VLA(uint8_t, packet, len); + create_forwarding_packet(forwarding, sendback_data, sendback_data_len, data, length, packet); + return sendpacket(forwarding->net, dest, packet, len) == len; +} + +#define FORWARD_REQUEST_MIN_PACKET_SIZE (1 + CRYPTO_PUBLIC_KEY_SIZE) + +non_null(1) nullable(2, 4) +static bool handle_forward_request_dht(const Forwarding *forwarding, + const uint8_t *sendback_data, uint16_t sendback_data_len, + const uint8_t *packet, uint16_t length) +{ + if (length < FORWARD_REQUEST_MIN_PACKET_SIZE) { + return false; + } + + const uint8_t *const public_key = packet + 1; + const uint8_t *const forward_data = packet + (1 + CRYPTO_PUBLIC_KEY_SIZE); + const uint16_t forward_data_len = length - (1 + CRYPTO_PUBLIC_KEY_SIZE); + + if (TIMED_AUTH_SIZE + sendback_data_len > MAX_SENDBACK_SIZE || + forward_data_len > MAX_FORWARD_DATA_SIZE) { + return false; + } + + const uint16_t len = forwarding_packet_length(sendback_data_len, forward_data_len); + VLA(uint8_t, forwarding_packet, len); + + create_forwarding_packet(forwarding, sendback_data, sendback_data_len, forward_data, forward_data_len, + forwarding_packet); + + return route_packet(forwarding->dht, public_key, forwarding_packet, len) == len; +} + +non_null(1, 2) nullable(3, 5) +static int handle_forward_request(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + const Forwarding *forwarding = (const Forwarding *)object; + + uint8_t sendback_data[1 + MAX_PACKED_IPPORT_SIZE]; + sendback_data[0] = SENDBACK_IPPORT; + + const int ipport_length = pack_ip_port(forwarding->log, sendback_data + 1, MAX_PACKED_IPPORT_SIZE, source); + + if (ipport_length == -1) { + return 1; + } + + return handle_forward_request_dht(forwarding, sendback_data, 1 + ipport_length, packet, length) ? 0 : 1; +} + +#define MIN_NONEMPTY_SENDBACK_SIZE TIMED_AUTH_SIZE +#define FORWARD_REPLY_MIN_PACKET_SIZE (1 + 1 + MIN_NONEMPTY_SENDBACK_SIZE) + +non_null(1, 2) nullable(3, 5) +static int handle_forward_reply(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + const Forwarding *forwarding = (const Forwarding *)object; + + if (length < FORWARD_REPLY_MIN_PACKET_SIZE) { + return 1; + } + + const uint8_t sendback_len = packet[1]; + const uint8_t *const sendback_auth = packet + 1 + 1; + const uint8_t *const sendback_data = sendback_auth + TIMED_AUTH_SIZE; + + if (sendback_len > MAX_SENDBACK_SIZE) { + /* value 0xff is reserved for possible future expansion */ + return 1; + } + + if (sendback_len < TIMED_AUTH_SIZE + 1) { + return 1; + } + + const uint16_t sendback_data_len = sendback_len - TIMED_AUTH_SIZE; + + if (length < 1 + 1 + sendback_len) { + return 1; + } + + const uint8_t *const to_forward = packet + (1 + 1 + sendback_len); + const uint16_t to_forward_len = length - (1 + 1 + sendback_len); + + if (!check_timed_auth(forwarding->mono_time, SENDBACK_TIMEOUT, forwarding->hmac_key, sendback_data, sendback_data_len, + sendback_auth)) { + return 1; + } + + if (sendback_data[0] == SENDBACK_IPPORT) { + IP_Port dest; + + if (unpack_ip_port(&dest, sendback_data + 1, sendback_data_len - 1, false) + != sendback_data_len - 1) { + return 1; + } + + return send_forwarding(forwarding, &dest, nullptr, 0, to_forward, to_forward_len) ? 0 : 1; + } + + if (sendback_data[0] == SENDBACK_FORWARD) { + IP_Port forwarder; + const int ipport_length = unpack_ip_port(&forwarder, sendback_data + 1, sendback_data_len - 1, false); + + if (ipport_length == -1) { + return 1; + } + + const uint8_t *const forward_sendback = sendback_data + (1 + ipport_length); + const uint16_t forward_sendback_len = sendback_data_len - (1 + ipport_length); + + return forward_reply(forwarding->net, &forwarder, forward_sendback, forward_sendback_len, to_forward, + to_forward_len) ? 0 : 1; + } + + if (forwarding->forward_reply_callback == nullptr) { + return 1; + } + + return forwarding->forward_reply_callback(forwarding->forward_reply_callback_object, + sendback_data, sendback_data_len, + to_forward, to_forward_len) ? 0 : 1; +} + +#define FORWARDING_MIN_PACKET_SIZE (1 + 1) + +non_null(1, 2) nullable(3, 5) +static int handle_forwarding(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + const Forwarding *forwarding = (const Forwarding *)object; + + if (length < FORWARDING_MIN_PACKET_SIZE) { + return 1; + } + + const uint8_t sendback_len = packet[1]; + + if (length < 1 + 1 + sendback_len) { + return 1; + } + + const uint8_t *const sendback = packet + 1 + 1; + + const uint8_t *const forwarded = sendback + sendback_len; + const uint16_t forwarded_len = length - (1 + 1 + sendback_len); + + if (forwarded_len >= 1 && forwarded[0] == NET_PACKET_FORWARD_REQUEST) { + VLA(uint8_t, sendback_data, 1 + MAX_PACKED_IPPORT_SIZE + sendback_len); + sendback_data[0] = SENDBACK_FORWARD; + + const int ipport_length = pack_ip_port(forwarding->log, sendback_data + 1, MAX_PACKED_IPPORT_SIZE, source); + + if (ipport_length == -1) { + return 1; + } + + memcpy(sendback_data + 1 + ipport_length, sendback, sendback_len); + + return handle_forward_request_dht(forwarding, sendback_data, 1 + ipport_length + sendback_len, forwarded, + forwarded_len) ? 0 : 1; + } + + if (sendback_len > 0) { + if (forwarding->forwarded_request_callback == nullptr) { + return 1; + } + + forwarding->forwarded_request_callback(forwarding->forwarded_request_callback_object, + source, sendback, sendback_len, + forwarded, forwarded_len, userdata); + return 0; + } else { + if (forwarding->forwarded_response_callback == nullptr) { + return 1; + } + + forwarding->forwarded_response_callback(forwarding->forwarded_response_callback_object, + forwarded, forwarded_len, userdata); + return 0; + } +} + +bool forward_reply(Networking_Core *net, const IP_Port *forwarder, + const uint8_t *sendback, uint16_t sendback_length, + const uint8_t *data, uint16_t length) +{ + if (sendback_length > MAX_SENDBACK_SIZE || + length > MAX_FORWARD_DATA_SIZE) { + return false; + } + + const uint16_t len = 1 + 1 + sendback_length + length; + VLA(uint8_t, packet, len); + packet[0] = NET_PACKET_FORWARD_REPLY; + packet[1] = (uint8_t) sendback_length; + memcpy(packet + 1 + 1, sendback, sendback_length); + memcpy(packet + 1 + 1 + sendback_length, data, length); + return sendpacket(net, forwarder, packet, len) == len; +} + +void set_callback_forwarded_request(Forwarding *forwarding, forwarded_request_cb *function, void *object) +{ + forwarding->forwarded_request_callback = function; + forwarding->forwarded_request_callback_object = object; +} + +void set_callback_forwarded_response(Forwarding *forwarding, forwarded_response_cb *function, void *object) +{ + forwarding->forwarded_response_callback = function; + forwarding->forwarded_response_callback_object = object; +} + +void set_callback_forward_reply(Forwarding *forwarding, forward_reply_cb *function, void *object) +{ + forwarding->forward_reply_callback = function; + forwarding->forward_reply_callback_object = object; +} + +Forwarding *new_forwarding(const Logger *log, const Random *rng, const Mono_Time *mono_time, DHT *dht) +{ + if (log == nullptr || mono_time == nullptr || dht == nullptr) { + return nullptr; + } + + Forwarding *forwarding = (Forwarding *)calloc(1, sizeof(Forwarding)); + + if (forwarding == nullptr) { + return nullptr; + } + + forwarding->log = log; + forwarding->rng = rng; + forwarding->mono_time = mono_time; + forwarding->dht = dht; + forwarding->net = dht_get_net(dht); + + networking_registerhandler(forwarding->net, NET_PACKET_FORWARD_REQUEST, &handle_forward_request, forwarding); + networking_registerhandler(forwarding->net, NET_PACKET_FORWARD_REPLY, &handle_forward_reply, forwarding); + networking_registerhandler(forwarding->net, NET_PACKET_FORWARDING, &handle_forwarding, forwarding); + + new_hmac_key(forwarding->rng, forwarding->hmac_key); + + return forwarding; +} + +void kill_forwarding(Forwarding *forwarding) +{ + if (forwarding == nullptr) { + return; + } + + networking_registerhandler(forwarding->net, NET_PACKET_FORWARD_REQUEST, nullptr, nullptr); + networking_registerhandler(forwarding->net, NET_PACKET_FORWARD_REPLY, nullptr, nullptr); + networking_registerhandler(forwarding->net, NET_PACKET_FORWARDING, nullptr, nullptr); + + crypto_memzero(forwarding->hmac_key, CRYPTO_HMAC_KEY_SIZE); + + free(forwarding); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/friend_connection.h b/local_pod_repo/toxcore/toxcore/toxcore/friend_connection.h new file mode 100644 index 0000000..93bd511 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/friend_connection.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * Connection to friends. + */ +#ifndef C_TOXCORE_TOXCORE_FRIEND_CONNECTION_H +#define C_TOXCORE_TOXCORE_FRIEND_CONNECTION_H + +#include "DHT.h" +#include "LAN_discovery.h" +#include "net_crypto.h" +#include "onion_client.h" + +#define MAX_FRIEND_CONNECTION_CALLBACKS 2 +#define MESSENGER_CALLBACK_INDEX 0 +#define GROUPCHAT_CALLBACK_INDEX 1 + +#define PACKET_ID_ALIVE 16 +#define PACKET_ID_SHARE_RELAYS 17 +#define PACKET_ID_FRIEND_REQUESTS 18 + +/** Interval between the sending of ping packets. */ +#define FRIEND_PING_INTERVAL 8 + +/** If no packets are received from friend in this time interval, kill the connection. */ +#define FRIEND_CONNECTION_TIMEOUT (FRIEND_PING_INTERVAL * 4) + +/** Time before friend is removed from the DHT after last hearing about him. */ +#define FRIEND_DHT_TIMEOUT BAD_NODE_TIMEOUT + +#define FRIEND_MAX_STORED_TCP_RELAYS (MAX_FRIEND_TCP_CONNECTIONS * 4) + +/** Max number of tcp relays sent to friends */ +#define MAX_SHARED_RELAYS RECOMMENDED_FRIEND_TCP_CONNECTIONS + +/** How often we share our TCP relays with each friend connection */ +#define SHARE_RELAYS_INTERVAL (60 * 2) + + +typedef enum Friendconn_Status { + FRIENDCONN_STATUS_NONE, + FRIENDCONN_STATUS_CONNECTING, + FRIENDCONN_STATUS_CONNECTED, +} Friendconn_Status; + +typedef struct Friend_Connections Friend_Connections; + +non_null() Net_Crypto *friendconn_net_crypto(const Friend_Connections *fr_c); + +/** @return friendcon_id corresponding to the real public key on success. + * @retval -1 on failure. + */ +non_null() +int getfriend_conn_id_pk(const Friend_Connections *fr_c, const uint8_t *real_pk); + +/** @brief Increases lock_count for the connection with friendcon_id by 1. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +non_null() +int friend_connection_lock(const Friend_Connections *fr_c, int friendcon_id); + +/** + * @retval FRIENDCONN_STATUS_CONNECTED if the friend is connected. + * @retval FRIENDCONN_STATUS_CONNECTING if the friend isn't connected. + * @retval FRIENDCONN_STATUS_NONE on failure. + */ +non_null() +unsigned int friend_con_connected(const Friend_Connections *fr_c, int friendcon_id); + +/** @brief Copy public keys associated to friendcon_id. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +non_null(3) nullable(1, 2) +int get_friendcon_public_keys(uint8_t *real_pk, uint8_t *dht_temp_pk, const Friend_Connections *fr_c, int friendcon_id); + +/** Set temp dht key for connection. */ +non_null() +void set_dht_temp_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t *dht_temp_pk, void *userdata); + +typedef int global_status_cb(void *object, int id, bool status, void *userdata); + +typedef int fc_status_cb(void *object, int id, bool status, void *userdata); +typedef int fc_data_cb(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); +typedef int fc_lossy_data_cb(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); + +/** Set global status callback for friend connections. */ +non_null(1) nullable(2, 3) +void set_global_status_callback(Friend_Connections *fr_c, global_status_cb *global_status_callback, void *object); + +/** @brief Set the callbacks for the friend connection. + * @param index is the index (0 to (MAX_FRIEND_CONNECTION_CALLBACKS - 1)) we + * want the callback to set in the array. + * + * @retval 0 on success. + * @retval -1 on failure + */ +non_null(1) nullable(4, 5, 6, 7) +int friend_connection_callbacks(const Friend_Connections *fr_c, int friendcon_id, unsigned int index, + fc_status_cb *status_callback, + fc_data_cb *data_callback, + fc_lossy_data_cb *lossy_data_callback, + void *object, int number); + +/** @brief return the crypt_connection_id for the connection. + * + * @return crypt_connection_id on success. + * @retval -1 on failure. + */ +non_null() +int friend_connection_crypt_connection_id(const Friend_Connections *fr_c, int friendcon_id); + +/** @brief Create a new friend connection. + * If one to that real public key already exists, increase lock count and return it. + * + * @retval -1 on failure. + * @return connection id on success. + */ +non_null() +int new_friend_connection(Friend_Connections *fr_c, const uint8_t *real_public_key); + +/** @brief Kill a friend connection. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +int kill_friend_connection(Friend_Connections *fr_c, int friendcon_id); + +/** @brief Send a Friend request packet. + * + * @retval -1 if failure. + * @retval 0 if it sent the friend request directly to the friend. + * @return the number of peers it was routed through if it did not send it directly. + */ +non_null() +int send_friend_request_packet( + Friend_Connections *fr_c, int friendcon_id, uint32_t nospam_num, const uint8_t *data, uint16_t length); + +typedef int fr_request_cb( + void *object, const uint8_t *source_pubkey, const uint8_t *data, uint16_t len, void *userdata); + +/** @brief Set friend request callback. + * + * This function will be called every time a friend request packet is received. + */ +non_null() +void set_friend_request_callback(Friend_Connections *fr_c, fr_request_cb *fr_request_callback, void *object); + +/** Create new friend_connections instance. */ +non_null() +Friend_Connections *new_friend_connections( + const Logger *logger, const Mono_Time *mono_time, const Network *ns, + Onion_Client *onion_c, bool local_discovery_enabled); + +/** main friend_connections loop. */ +non_null() +void do_friend_connections(Friend_Connections *fr_c, void *userdata); + +/** Free everything related with friend_connections. */ +nullable(1) +void kill_friend_connections(Friend_Connections *fr_c); + +typedef struct Friend_Conn Friend_Conn; + +non_null() Friend_Conn *get_conn(const Friend_Connections *fr_c, int friendcon_id); +non_null() int friend_conn_get_onion_friendnum(const Friend_Conn *fc); +non_null() const IP_Port *friend_conn_get_dht_ip_port(const Friend_Conn *fc); + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/friend_connection.m b/local_pod_repo/toxcore/toxcore/toxcore/friend_connection.m new file mode 100644 index 0000000..9e5dee7 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/friend_connection.m @@ -0,0 +1,1024 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * Connection to friends. + */ +#include "friend_connection.h" + +#include +#include + +#include "ccompat.h" +#include "mono_time.h" +#include "util.h" + +#define PORTS_PER_DISCOVERY 10 + +typedef struct Friend_Conn_Callbacks { + fc_status_cb *status_callback; + fc_data_cb *data_callback; + fc_lossy_data_cb *lossy_data_callback; + + void *callback_object; + int callback_id; +} Friend_Conn_Callbacks; + +struct Friend_Conn { + uint8_t status; + + uint8_t real_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t dht_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint16_t dht_lock; + IP_Port dht_ip_port; + uint64_t dht_pk_lastrecv; + uint64_t dht_ip_port_lastrecv; + + int onion_friendnum; + int crypt_connection_id; + + uint64_t ping_lastrecv; + uint64_t ping_lastsent; + uint64_t share_relays_lastsent; + + Friend_Conn_Callbacks callbacks[MAX_FRIEND_CONNECTION_CALLBACKS]; + + uint16_t lock_count; + + Node_format tcp_relays[FRIEND_MAX_STORED_TCP_RELAYS]; + uint16_t tcp_relay_counter; + uint32_t tcp_relay_share_index; + + bool hosting_tcp_relay; +}; + +static const Friend_Conn empty_friend_conn = {0}; + + +struct Friend_Connections { + const Mono_Time *mono_time; + const Logger *logger; + Net_Crypto *net_crypto; + DHT *dht; + Broadcast_Info *broadcast; + Onion_Client *onion_c; + + Friend_Conn *conns; + uint32_t num_cons; + + fr_request_cb *fr_request_callback; + void *fr_request_object; + + global_status_cb *global_status_callback; + void *global_status_callback_object; + + uint64_t last_lan_discovery; + uint16_t next_lan_port; + + bool local_discovery_enabled; +}; + +int friend_conn_get_onion_friendnum(const Friend_Conn *fc) +{ + return fc->onion_friendnum; +} + +Net_Crypto *friendconn_net_crypto(const Friend_Connections *fr_c) +{ + return fr_c->net_crypto; +} + +const IP_Port *friend_conn_get_dht_ip_port(const Friend_Conn *fc) +{ + return &fc->dht_ip_port; +} + + +/** + * @retval true if the friendcon_id is valid. + * @retval false if the friendcon_id is not valid. + */ +non_null() +static bool friendconn_id_valid(const Friend_Connections *fr_c, int friendcon_id) +{ + return (unsigned int)friendcon_id < fr_c->num_cons && + fr_c->conns != nullptr && + fr_c->conns[friendcon_id].status != FRIENDCONN_STATUS_NONE; +} + + +/** @brief Set the size of the friend connections list to num. + * + * @retval false if realloc fails. + * @retval true if it succeeds. + */ +non_null() +static bool realloc_friendconns(Friend_Connections *fr_c, uint32_t num) +{ + if (num == 0) { + free(fr_c->conns); + fr_c->conns = nullptr; + return true; + } + + Friend_Conn *newgroup_cons = (Friend_Conn *)realloc(fr_c->conns, num * sizeof(Friend_Conn)); + + if (newgroup_cons == nullptr) { + return false; + } + + fr_c->conns = newgroup_cons; + return true; +} + +/** @brief Create a new empty friend connection. + * + * @retval -1 on failure. + * @return friendcon_id on success. + */ +non_null() +static int create_friend_conn(Friend_Connections *fr_c) +{ + for (uint32_t i = 0; i < fr_c->num_cons; ++i) { + if (fr_c->conns[i].status == FRIENDCONN_STATUS_NONE) { + return i; + } + } + + if (!realloc_friendconns(fr_c, fr_c->num_cons + 1)) { + return -1; + } + + const int id = fr_c->num_cons; + ++fr_c->num_cons; + fr_c->conns[id] = empty_friend_conn; + + return id; +} + +/** @brief Wipe a friend connection. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int wipe_friend_conn(Friend_Connections *fr_c, int friendcon_id) +{ + if (!friendconn_id_valid(fr_c, friendcon_id)) { + return -1; + } + + fr_c->conns[friendcon_id] = empty_friend_conn; + + uint32_t i; + + for (i = fr_c->num_cons; i != 0; --i) { + if (fr_c->conns[i - 1].status != FRIENDCONN_STATUS_NONE) { + break; + } + } + + if (fr_c->num_cons != i) { + fr_c->num_cons = i; + realloc_friendconns(fr_c, fr_c->num_cons); + } + + return 0; +} + +Friend_Conn *get_conn(const Friend_Connections *fr_c, int friendcon_id) +{ + if (!friendconn_id_valid(fr_c, friendcon_id)) { + return nullptr; + } + + return &fr_c->conns[friendcon_id]; +} + +/** + * @return friendcon_id corresponding to the real public key on success. + * @retval -1 on failure. + */ +int getfriend_conn_id_pk(const Friend_Connections *fr_c, const uint8_t *real_pk) +{ + for (uint32_t i = 0; i < fr_c->num_cons; ++i) { + const Friend_Conn *friend_con = get_conn(fr_c, i); + + if (friend_con != nullptr) { + if (pk_equal(friend_con->real_public_key, real_pk)) { + return i; + } + } + } + + return -1; +} + +/** @brief Add a TCP relay associated to the friend. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int friend_add_tcp_relay(Friend_Connections *fr_c, int friendcon_id, const IP_Port *ip_port, + const uint8_t *public_key) +{ + IP_Port ipp_copy = *ip_port; + + Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return -1; + } + + /* Local ip and same pk means that they are hosting a TCP relay. */ + if (ip_is_local(&ipp_copy.ip) && pk_equal(friend_con->dht_temp_pk, public_key)) { + if (!net_family_is_unspec(friend_con->dht_ip_port.ip.family)) { + ipp_copy.ip = friend_con->dht_ip_port.ip; + } else { + friend_con->hosting_tcp_relay = 0; + } + } + + const uint16_t index = friend_con->tcp_relay_counter % FRIEND_MAX_STORED_TCP_RELAYS; + + for (unsigned i = 0; i < FRIEND_MAX_STORED_TCP_RELAYS; ++i) { + if (!net_family_is_unspec(friend_con->tcp_relays[i].ip_port.ip.family) + && pk_equal(friend_con->tcp_relays[i].public_key, public_key)) { + friend_con->tcp_relays[i] = empty_node_format; + } + } + + friend_con->tcp_relays[index].ip_port = ipp_copy; + memcpy(friend_con->tcp_relays[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + ++friend_con->tcp_relay_counter; + + return add_tcp_relay_peer(fr_c->net_crypto, friend_con->crypt_connection_id, &ipp_copy, public_key); +} + +/** Connect to number saved relays for friend. */ +non_null() +static void connect_to_saved_tcp_relays(Friend_Connections *fr_c, int friendcon_id, unsigned int number) +{ + const Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return; + } + + for (unsigned i = 0; (i < FRIEND_MAX_STORED_TCP_RELAYS) && (number != 0); ++i) { + const uint16_t index = (friend_con->tcp_relay_counter - (i + 1)) % FRIEND_MAX_STORED_TCP_RELAYS; + + if (!net_family_is_unspec(friend_con->tcp_relays[index].ip_port.ip.family)) { + if (add_tcp_relay_peer(fr_c->net_crypto, friend_con->crypt_connection_id, &friend_con->tcp_relays[index].ip_port, + friend_con->tcp_relays[index].public_key) == 0) { + --number; + } + } + } +} + +non_null() +static unsigned int send_relays(Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return 0; + } + + Node_format nodes[MAX_SHARED_RELAYS] = {{{0}}}; + uint8_t data[1024]; + + const uint32_t n = copy_connected_tcp_relays_index(fr_c->net_crypto, nodes, MAX_SHARED_RELAYS, + friend_con->tcp_relay_share_index); + + friend_con->tcp_relay_share_index += MAX_SHARED_RELAYS; + + for (uint32_t i = 0; i < n; ++i) { + /* Associated the relays being sent with this connection. + * On receiving the peer will do the same which will establish the connection. */ + friend_add_tcp_relay(fr_c, friendcon_id, &nodes[i].ip_port, nodes[i].public_key); + } + + int length = pack_nodes(fr_c->logger, data + 1, sizeof(data) - 1, nodes, n); + + if (length <= 0) { + return 0; + } + + data[0] = PACKET_ID_SHARE_RELAYS; + ++length; + + if (write_cryptpacket(fr_c->net_crypto, friend_con->crypt_connection_id, data, length, false) != -1) { + friend_con->share_relays_lastsent = mono_time_get(fr_c->mono_time); + return 1; + } + + return 0; +} + +/** callback for recv TCP relay nodes. */ +non_null() +static int tcp_relay_node_callback(void *object, uint32_t number, const IP_Port *ip_port, const uint8_t *public_key) +{ + Friend_Connections *fr_c = (Friend_Connections *)object; + const Friend_Conn *friend_con = get_conn(fr_c, number); + + if (friend_con == nullptr) { + return -1; + } + + if (friend_con->crypt_connection_id != -1) { + return friend_add_tcp_relay(fr_c, number, ip_port, public_key); + } + + return add_tcp_relay(fr_c->net_crypto, ip_port, public_key); +} + +non_null() +static int friend_new_connection(Friend_Connections *fr_c, int friendcon_id); + +/** Callback for DHT ip_port changes. */ +non_null() +static void dht_ip_callback(void *object, int32_t number, const IP_Port *ip_port) +{ + Friend_Connections *const fr_c = (Friend_Connections *)object; + Friend_Conn *const friend_con = get_conn(fr_c, number); + + if (friend_con == nullptr) { + return; + } + + if (friend_con->crypt_connection_id == -1) { + friend_new_connection(fr_c, number); + } + + set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, ip_port, true); + friend_con->dht_ip_port = *ip_port; + friend_con->dht_ip_port_lastrecv = mono_time_get(fr_c->mono_time); + + if (friend_con->hosting_tcp_relay) { + friend_add_tcp_relay(fr_c, number, ip_port, friend_con->dht_temp_pk); + friend_con->hosting_tcp_relay = 0; + } +} + +non_null() +static void change_dht_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t *dht_public_key) +{ + Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return; + } + + friend_con->dht_pk_lastrecv = mono_time_get(fr_c->mono_time); + + if (friend_con->dht_lock > 0) { + if (dht_delfriend(fr_c->dht, friend_con->dht_temp_pk, friend_con->dht_lock) != 0) { + LOGGER_ERROR(fr_c->logger, "a. Could not delete dht peer. Please report this."); + return; + } + + friend_con->dht_lock = 0; + } + + dht_addfriend(fr_c->dht, dht_public_key, dht_ip_callback, fr_c, friendcon_id, &friend_con->dht_lock); + memcpy(friend_con->dht_temp_pk, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); +} + +non_null() +static int handle_status(void *object, int number, bool status, void *userdata) +{ + Friend_Connections *const fr_c = (Friend_Connections *)object; + Friend_Conn *const friend_con = get_conn(fr_c, number); + + if (friend_con == nullptr) { + return -1; + } + + bool status_changed = false; + + if (status) { /* Went online. */ + status_changed = true; + friend_con->status = FRIENDCONN_STATUS_CONNECTED; + friend_con->ping_lastrecv = mono_time_get(fr_c->mono_time); + friend_con->share_relays_lastsent = 0; + onion_set_friend_online(fr_c->onion_c, friend_con->onion_friendnum, status); + } else { /* Went offline. */ + if (friend_con->status != FRIENDCONN_STATUS_CONNECTING) { + status_changed = true; + friend_con->dht_pk_lastrecv = mono_time_get(fr_c->mono_time); + onion_set_friend_online(fr_c->onion_c, friend_con->onion_friendnum, status); + } + + friend_con->status = FRIENDCONN_STATUS_CONNECTING; + friend_con->crypt_connection_id = -1; + friend_con->hosting_tcp_relay = 0; + } + + if (status_changed) { + if (fr_c->global_status_callback != nullptr) { + fr_c->global_status_callback(fr_c->global_status_callback_object, number, status, userdata); + } + + for (unsigned i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { + if (friend_con->callbacks[i].status_callback != nullptr) { + friend_con->callbacks[i].status_callback( + friend_con->callbacks[i].callback_object, + friend_con->callbacks[i].callback_id, status, userdata); + } + } + } + + return 0; +} + +/** Callback for dht public key changes. */ +non_null() +static void dht_pk_callback(void *object, int32_t number, const uint8_t *dht_public_key, void *userdata) +{ + Friend_Connections *const fr_c = (Friend_Connections *)object; + Friend_Conn *const friend_con = get_conn(fr_c, number); + + if (friend_con == nullptr) { + return; + } + + if (pk_equal(friend_con->dht_temp_pk, dht_public_key)) { + return; + } + + change_dht_pk(fr_c, number, dht_public_key); + + /* if pk changed, create a new connection.*/ + if (friend_con->crypt_connection_id != -1) { + crypto_kill(fr_c->net_crypto, friend_con->crypt_connection_id); + friend_con->crypt_connection_id = -1; + handle_status(object, number, false, userdata); /* Going offline. */ + } + + friend_new_connection(fr_c, number); + onion_set_friend_DHT_pubkey(fr_c->onion_c, friend_con->onion_friendnum, dht_public_key); +} + +non_null() +static int handle_packet(void *object, int number, const uint8_t *data, uint16_t length, void *userdata) +{ + if (length == 0) { + return -1; + } + + Friend_Connections *const fr_c = (Friend_Connections *)object; + Friend_Conn *friend_con = get_conn(fr_c, number); + + if (friend_con == nullptr) { + return -1; + } + + if (data[0] == PACKET_ID_FRIEND_REQUESTS) { + if (fr_c->fr_request_callback != nullptr) { + fr_c->fr_request_callback(fr_c->fr_request_object, friend_con->real_public_key, data, length, userdata); + } + + return 0; + } + + if (data[0] == PACKET_ID_ALIVE) { + friend_con->ping_lastrecv = mono_time_get(fr_c->mono_time); + return 0; + } + + if (data[0] == PACKET_ID_SHARE_RELAYS) { + Node_format nodes[MAX_SHARED_RELAYS]; + const int n = unpack_nodes(nodes, MAX_SHARED_RELAYS, nullptr, data + 1, length - 1, true); + + if (n == -1) { + return -1; + } + + for (int j = 0; j < n; ++j) { + friend_add_tcp_relay(fr_c, number, &nodes[j].ip_port, nodes[j].public_key); + } + + return 0; + } + + for (unsigned i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { + if (friend_con->callbacks[i].data_callback != nullptr) { + friend_con->callbacks[i].data_callback( + friend_con->callbacks[i].callback_object, + friend_con->callbacks[i].callback_id, data, length, userdata); + } + + friend_con = get_conn(fr_c, number); + + if (friend_con == nullptr) { + return -1; + } + } + + return 0; +} + +non_null() +static int handle_lossy_packet(void *object, int number, const uint8_t *data, uint16_t length, void *userdata) +{ + if (length == 0) { + return -1; + } + + const Friend_Connections *const fr_c = (const Friend_Connections *)object; + const Friend_Conn *friend_con = get_conn(fr_c, number); + + if (friend_con == nullptr) { + return -1; + } + + for (unsigned i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { + if (friend_con->callbacks[i].lossy_data_callback != nullptr) { + friend_con->callbacks[i].lossy_data_callback( + friend_con->callbacks[i].callback_object, + friend_con->callbacks[i].callback_id, data, length, userdata); + } + + friend_con = get_conn(fr_c, number); + + if (friend_con == nullptr) { + return -1; + } + } + + return 0; +} + +non_null() +static int handle_new_connections(void *object, const New_Connection *n_c) +{ + Friend_Connections *const fr_c = (Friend_Connections *)object; + const int friendcon_id = getfriend_conn_id_pk(fr_c, n_c->public_key); + Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return -1; + } + + if (friend_con->crypt_connection_id != -1) { + return -1; + } + + const int id = accept_crypto_connection(fr_c->net_crypto, n_c); + + if (id == -1) { + return -1; + } + + connection_status_handler(fr_c->net_crypto, id, &handle_status, fr_c, friendcon_id); + connection_data_handler(fr_c->net_crypto, id, &handle_packet, fr_c, friendcon_id); + connection_lossy_data_handler(fr_c->net_crypto, id, &handle_lossy_packet, fr_c, friendcon_id); + friend_con->crypt_connection_id = id; + + if (!net_family_is_ipv4(n_c->source.ip.family) && !net_family_is_ipv6(n_c->source.ip.family)) { + set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, &friend_con->dht_ip_port, false); + } else { + friend_con->dht_ip_port = n_c->source; + friend_con->dht_ip_port_lastrecv = mono_time_get(fr_c->mono_time); + } + + if (!pk_equal(friend_con->dht_temp_pk, n_c->dht_public_key)) { + change_dht_pk(fr_c, friendcon_id, n_c->dht_public_key); + } + + nc_dht_pk_callback(fr_c->net_crypto, id, &dht_pk_callback, fr_c, friendcon_id); + return 0; +} + +static int friend_new_connection(Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return -1; + } + + if (friend_con->crypt_connection_id != -1) { + return -1; + } + + /* If dht_temp_pk does not contains a pk. */ + if (friend_con->dht_lock == 0) { + return -1; + } + + const int id = new_crypto_connection(fr_c->net_crypto, friend_con->real_public_key, friend_con->dht_temp_pk); + + if (id == -1) { + return -1; + } + + friend_con->crypt_connection_id = id; + connection_status_handler(fr_c->net_crypto, id, &handle_status, fr_c, friendcon_id); + connection_data_handler(fr_c->net_crypto, id, &handle_packet, fr_c, friendcon_id); + connection_lossy_data_handler(fr_c->net_crypto, id, &handle_lossy_packet, fr_c, friendcon_id); + nc_dht_pk_callback(fr_c->net_crypto, id, &dht_pk_callback, fr_c, friendcon_id); + + return 0; +} + +non_null() +static int send_ping(const Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return -1; + } + + const uint8_t ping = PACKET_ID_ALIVE; + const int64_t ret = write_cryptpacket(fr_c->net_crypto, friend_con->crypt_connection_id, &ping, sizeof(ping), false); + + if (ret != -1) { + friend_con->ping_lastsent = mono_time_get(fr_c->mono_time); + return 0; + } + + return -1; +} + +/** @brief Increases lock_count for the connection with friendcon_id by 1. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int friend_connection_lock(const Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return -1; + } + + ++friend_con->lock_count; + return 0; +} + +/** + * @retval FRIENDCONN_STATUS_CONNECTED if the friend is connected. + * @retval FRIENDCONN_STATUS_CONNECTING if the friend isn't connected. + * @retval FRIENDCONN_STATUS_NONE on failure. + */ +unsigned int friend_con_connected(const Friend_Connections *fr_c, int friendcon_id) +{ + const Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return 0; + } + + return friend_con->status; +} + +/** @brief Copy public keys associated to friendcon_id. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int get_friendcon_public_keys(uint8_t *real_pk, uint8_t *dht_temp_pk, const Friend_Connections *fr_c, int friendcon_id) +{ + const Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return -1; + } + + if (real_pk != nullptr) { + memcpy(real_pk, friend_con->real_public_key, CRYPTO_PUBLIC_KEY_SIZE); + } + + if (dht_temp_pk != nullptr) { + memcpy(dht_temp_pk, friend_con->dht_temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + } + + return 0; +} + +/** Set temp dht key for connection. */ +void set_dht_temp_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t *dht_temp_pk, void *userdata) +{ + dht_pk_callback(fr_c, friendcon_id, dht_temp_pk, userdata); +} + +/** @brief Set the callbacks for the friend connection. + * @param index is the index (0 to (MAX_FRIEND_CONNECTION_CALLBACKS - 1)) we + * want the callback to set in the array. + * + * @retval 0 on success. + * @retval -1 on failure + */ +int friend_connection_callbacks(const Friend_Connections *fr_c, int friendcon_id, unsigned int index, + fc_status_cb *status_callback, + fc_data_cb *data_callback, + fc_lossy_data_cb *lossy_data_callback, + void *object, int number) +{ + Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return -1; + } + + if (index >= MAX_FRIEND_CONNECTION_CALLBACKS) { + return -1; + } + + if (object != nullptr && (status_callback == nullptr || data_callback == nullptr || lossy_data_callback == nullptr)) { + LOGGER_ERROR(fr_c->logger, "non-null user data object but null callbacks"); + return -1; + } + + friend_con->callbacks[index].status_callback = status_callback; + friend_con->callbacks[index].data_callback = data_callback; + friend_con->callbacks[index].lossy_data_callback = lossy_data_callback; + + friend_con->callbacks[index].callback_object = object; + friend_con->callbacks[index].callback_id = number; + + return 0; +} + +/** Set global status callback for friend connections. */ +void set_global_status_callback(Friend_Connections *fr_c, global_status_cb *global_status_callback, void *object) +{ + if (object != nullptr && global_status_callback == nullptr) { + LOGGER_ERROR(fr_c->logger, "non-null user data object but null callback"); + object = nullptr; + } + + fr_c->global_status_callback = global_status_callback; + fr_c->global_status_callback_object = object; +} + +/** @brief return the crypt_connection_id for the connection. + * + * @return crypt_connection_id on success. + * @retval -1 on failure. + */ +int friend_connection_crypt_connection_id(const Friend_Connections *fr_c, int friendcon_id) +{ + const Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return -1; + } + + return friend_con->crypt_connection_id; +} + +/** @brief Create a new friend connection. + * If one to that real public key already exists, increase lock count and return it. + * + * @retval -1 on failure. + * @return connection id on success. + */ +int new_friend_connection(Friend_Connections *fr_c, const uint8_t *real_public_key) +{ + int friendcon_id = getfriend_conn_id_pk(fr_c, real_public_key); + + if (friendcon_id != -1) { + ++fr_c->conns[friendcon_id].lock_count; + return friendcon_id; + } + + friendcon_id = create_friend_conn(fr_c); + + if (friendcon_id == -1) { + return -1; + } + + const int32_t onion_friendnum = onion_addfriend(fr_c->onion_c, real_public_key); + + if (onion_friendnum == -1) { + return -1; + } + + Friend_Conn *const friend_con = &fr_c->conns[friendcon_id]; + + friend_con->crypt_connection_id = -1; + friend_con->status = FRIENDCONN_STATUS_CONNECTING; + memcpy(friend_con->real_public_key, real_public_key, CRYPTO_PUBLIC_KEY_SIZE); + friend_con->onion_friendnum = onion_friendnum; + + recv_tcp_relay_handler(fr_c->onion_c, onion_friendnum, &tcp_relay_node_callback, fr_c, friendcon_id); + onion_dht_pk_callback(fr_c->onion_c, onion_friendnum, &dht_pk_callback, fr_c, friendcon_id); + + return friendcon_id; +} + +/** @brief Kill a friend connection. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +int kill_friend_connection(Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return -1; + } + + if (friend_con->lock_count > 0) { + --friend_con->lock_count; + return 0; + } + + onion_delfriend(fr_c->onion_c, friend_con->onion_friendnum); + crypto_kill(fr_c->net_crypto, friend_con->crypt_connection_id); + + if (friend_con->dht_lock > 0) { + dht_delfriend(fr_c->dht, friend_con->dht_temp_pk, friend_con->dht_lock); + } + + return wipe_friend_conn(fr_c, friendcon_id); +} + + +/** @brief Set friend request callback. + * + * This function will be called every time a friend request packet is received. + */ +void set_friend_request_callback(Friend_Connections *fr_c, fr_request_cb *fr_request_callback, void *object) +{ + fr_c->fr_request_callback = fr_request_callback; + fr_c->fr_request_object = object; + oniondata_registerhandler(fr_c->onion_c, CRYPTO_PACKET_FRIEND_REQ, fr_request_callback, object); +} + +/** @brief Send a Friend request packet. + * + * @retval -1 if failure. + * @retval 0 if it sent the friend request directly to the friend. + * @return the number of peers it was routed through if it did not send it directly. + */ +int send_friend_request_packet(Friend_Connections *fr_c, int friendcon_id, uint32_t nospam_num, const uint8_t *data, + uint16_t length) +{ + if (1 + sizeof(nospam_num) + length > ONION_CLIENT_MAX_DATA_SIZE || length == 0) { + return -1; + } + + const Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con == nullptr) { + return -1; + } + + VLA(uint8_t, packet, 1 + sizeof(nospam_num) + length); + memcpy(packet + 1, &nospam_num, sizeof(nospam_num)); + memcpy(packet + 1 + sizeof(nospam_num), data, length); + + if (friend_con->status == FRIENDCONN_STATUS_CONNECTED) { + packet[0] = PACKET_ID_FRIEND_REQUESTS; + return write_cryptpacket(fr_c->net_crypto, friend_con->crypt_connection_id, packet, SIZEOF_VLA(packet), false) != -1 ? 1 : 0; + } + + packet[0] = CRYPTO_PACKET_FRIEND_REQ; + const int num = send_onion_data(fr_c->onion_c, friend_con->onion_friendnum, packet, SIZEOF_VLA(packet)); + + if (num <= 0) { + return -1; + } + + return num; +} + +/** Create new friend_connections instance. */ +Friend_Connections *new_friend_connections( + const Logger *logger, const Mono_Time *mono_time, const Network *ns, + Onion_Client *onion_c, bool local_discovery_enabled) +{ + if (onion_c == nullptr) { + return nullptr; + } + + Friend_Connections *const temp = (Friend_Connections *)calloc(1, sizeof(Friend_Connections)); + + if (temp == nullptr) { + return nullptr; + } + + temp->mono_time = mono_time; + temp->logger = logger; + temp->dht = onion_get_dht(onion_c); + temp->net_crypto = onion_get_net_crypto(onion_c); + temp->onion_c = onion_c; + temp->local_discovery_enabled = local_discovery_enabled; + // Don't include default port in port range + temp->next_lan_port = TOX_PORTRANGE_FROM + 1; + + new_connection_handler(temp->net_crypto, &handle_new_connections, temp); + + if (temp->local_discovery_enabled) { + temp->broadcast = lan_discovery_init(ns); + + if (temp->broadcast == nullptr) { + LOGGER_ERROR(logger, "could not initialise LAN discovery"); + } + } + + return temp; +} + +/** Send a LAN discovery packet every LAN_DISCOVERY_INTERVAL seconds. */ +non_null() +static void lan_discovery(Friend_Connections *fr_c) +{ + if (fr_c->last_lan_discovery + LAN_DISCOVERY_INTERVAL < mono_time_get(fr_c->mono_time)) { + const uint16_t first = fr_c->next_lan_port; + uint16_t last = first + PORTS_PER_DISCOVERY; + last = last > TOX_PORTRANGE_TO ? TOX_PORTRANGE_TO : last; + + // Always send to default port + lan_discovery_send(dht_get_net(fr_c->dht), fr_c->broadcast, dht_get_self_public_key(fr_c->dht), + net_htons(TOX_PORT_DEFAULT)); + + // And check some extra ports + for (uint16_t port = first; port < last; ++port) { + lan_discovery_send(dht_get_net(fr_c->dht), fr_c->broadcast, dht_get_self_public_key(fr_c->dht), net_htons(port)); + } + + // Don't include default port in port range + fr_c->next_lan_port = last != TOX_PORTRANGE_TO ? last : TOX_PORTRANGE_FROM + 1; + fr_c->last_lan_discovery = mono_time_get(fr_c->mono_time); + } +} + +/** main friend_connections loop. */ +void do_friend_connections(Friend_Connections *fr_c, void *userdata) +{ + const uint64_t temp_time = mono_time_get(fr_c->mono_time); + + for (uint32_t i = 0; i < fr_c->num_cons; ++i) { + Friend_Conn *const friend_con = get_conn(fr_c, i); + + if (friend_con != nullptr) { + if (friend_con->status == FRIENDCONN_STATUS_CONNECTING) { + if (friend_con->dht_pk_lastrecv + FRIEND_DHT_TIMEOUT < temp_time) { + if (friend_con->dht_lock > 0) { + dht_delfriend(fr_c->dht, friend_con->dht_temp_pk, friend_con->dht_lock); + friend_con->dht_lock = 0; + memset(friend_con->dht_temp_pk, 0, CRYPTO_PUBLIC_KEY_SIZE); + } + } + + if (friend_con->dht_ip_port_lastrecv + FRIEND_DHT_TIMEOUT < temp_time) { + friend_con->dht_ip_port.ip.family = net_family_unspec(); + } + + if (friend_con->dht_lock > 0) { + if (friend_new_connection(fr_c, i) == 0) { + set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, &friend_con->dht_ip_port, false); + connect_to_saved_tcp_relays(fr_c, i, MAX_FRIEND_TCP_CONNECTIONS / 2); /* Only fill it half up. */ + } + } + } else if (friend_con->status == FRIENDCONN_STATUS_CONNECTED) { + if (friend_con->ping_lastsent + FRIEND_PING_INTERVAL < temp_time) { + send_ping(fr_c, i); + } + + if (friend_con->share_relays_lastsent + SHARE_RELAYS_INTERVAL < temp_time) { + send_relays(fr_c, i); + } + + if (friend_con->ping_lastrecv + FRIEND_CONNECTION_TIMEOUT < temp_time) { + /* If we stopped receiving ping packets, kill it. */ + crypto_kill(fr_c->net_crypto, friend_con->crypt_connection_id); + friend_con->crypt_connection_id = -1; + handle_status(fr_c, i, false, userdata); /* Going offline. */ + } + } + } + } + + if (fr_c->local_discovery_enabled) { + lan_discovery(fr_c); + } +} + +/** Free everything related with friend_connections. */ +void kill_friend_connections(Friend_Connections *fr_c) +{ + if (fr_c == nullptr) { + return; + } + + for (uint32_t i = 0; i < fr_c->num_cons; ++i) { + kill_friend_connection(fr_c, i); + } + + lan_discovery_kill(fr_c->broadcast); + free(fr_c); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/friend_requests.h b/local_pod_repo/toxcore/toxcore/toxcore/friend_requests.h new file mode 100644 index 0000000..2614527 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/friend_requests.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * Handle friend requests. + */ +#ifndef C_TOXCORE_TOXCORE_FRIEND_REQUESTS_H +#define C_TOXCORE_TOXCORE_FRIEND_REQUESTS_H + +#include "friend_connection.h" + +#define MAX_FRIEND_REQUEST_DATA_SIZE (ONION_CLIENT_MAX_DATA_SIZE - (1 + sizeof(uint32_t))) + +typedef struct Friend_Requests Friend_Requests; + +/** Set and get the nospam variable used to prevent one type of friend request spam. */ +non_null() void set_nospam(Friend_Requests *fr, uint32_t num); +non_null() uint32_t get_nospam(const Friend_Requests *fr); + +/** @brief Remove real_pk from received_requests list. + * + * @retval 0 if it removed it successfully. + * @retval -1 if it didn't find it. + */ +non_null() +int remove_request_received(Friend_Requests *fr, const uint8_t *real_pk); + +typedef void fr_friend_request_cb(void *object, const uint8_t *public_key, const uint8_t *message, size_t length, + void *user_data); + +/** Set the function that will be executed when a friend request for us is received. */ +non_null() +void callback_friendrequest(Friend_Requests *fr, fr_friend_request_cb *function, void *object); + +typedef int filter_function_cb(const uint8_t *public_key, void *user_data); + +/** @brief Set the function used to check if a friend request should be displayed to the user or not. + * It must return 0 if the request is ok (anything else if it is bad). + */ +non_null() +void set_filter_function(Friend_Requests *fr, filter_function_cb *function, void *userdata); + +/** Sets up friendreq packet handlers. */ +non_null() +void friendreq_init(Friend_Requests *fr, Friend_Connections *fr_c); + +Friend_Requests *friendreq_new(void); + +nullable(1) +void friendreq_kill(Friend_Requests *fr); + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/friend_requests.m b/local_pod_repo/toxcore/toxcore/toxcore/friend_requests.m new file mode 100644 index 0000000..7f18b1f --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/friend_requests.m @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Handle friend requests. + */ +#include "friend_requests.h" + +#include +#include + +#include "ccompat.h" +#include "util.h" + +/** + * NOTE: The following is just a temporary fix for the multiple friend requests received at the same time problem. + * TODO(irungentoo): Make this better (This will most likely tie in with the way we will handle spam). + */ +#define MAX_RECEIVED_STORED 32 + +struct Received_Requests { + uint8_t requests[MAX_RECEIVED_STORED][CRYPTO_PUBLIC_KEY_SIZE]; + uint16_t requests_index; +}; + +struct Friend_Requests { + uint32_t nospam; + fr_friend_request_cb *handle_friendrequest; + uint8_t handle_friendrequest_isset; + void *handle_friendrequest_object; + + filter_function_cb *filter_function; + void *filter_function_userdata; + + struct Received_Requests received; +}; + +/** Set and get the nospam variable used to prevent one type of friend request spam. */ +void set_nospam(Friend_Requests *fr, uint32_t num) +{ + fr->nospam = num; +} + +uint32_t get_nospam(const Friend_Requests *fr) +{ + return fr->nospam; +} + + +/** Set the function that will be executed when a friend request for us is received. */ +void callback_friendrequest(Friend_Requests *fr, fr_friend_request_cb *function, void *object) +{ + fr->handle_friendrequest = function; + fr->handle_friendrequest_isset = 1; + fr->handle_friendrequest_object = object; +} + +/** @brief Set the function used to check if a friend request should be displayed to the user or not. + * It must return 0 if the request is ok (anything else if it is bad). + */ +void set_filter_function(Friend_Requests *fr, filter_function_cb *function, void *userdata) +{ + fr->filter_function = function; + fr->filter_function_userdata = userdata; +} + +/** Add to list of received friend requests. */ +non_null() +static void addto_receivedlist(Friend_Requests *fr, const uint8_t *real_pk) +{ + if (fr->received.requests_index >= MAX_RECEIVED_STORED) { + fr->received.requests_index = 0; + } + + pk_copy(fr->received.requests[fr->received.requests_index], real_pk); + ++fr->received.requests_index; +} + +/** @brief Check if a friend request was already received. + * + * @retval false if it did not. + * @retval true if it did. + */ +non_null() +static bool request_received(const Friend_Requests *fr, const uint8_t *real_pk) +{ + for (uint32_t i = 0; i < MAX_RECEIVED_STORED; ++i) { + if (pk_equal(fr->received.requests[i], real_pk)) { + return true; + } + } + + return false; +} + +/** @brief Remove real_pk from received_requests list. + * + * @retval 0 if it removed it successfully. + * @retval -1 if it didn't find it. + */ +int remove_request_received(Friend_Requests *fr, const uint8_t *real_pk) +{ + for (uint32_t i = 0; i < MAX_RECEIVED_STORED; ++i) { + if (pk_equal(fr->received.requests[i], real_pk)) { + crypto_memzero(fr->received.requests[i], CRYPTO_PUBLIC_KEY_SIZE); + return 0; + } + } + + return -1; +} + + +non_null() +static int friendreq_handlepacket(void *object, const uint8_t *source_pubkey, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Friend_Requests *const fr = (Friend_Requests *)object; + + if (length <= 1 + sizeof(fr->nospam) || length > ONION_CLIENT_MAX_DATA_SIZE) { + return 1; + } + + ++packet; + --length; + + if (fr->handle_friendrequest_isset == 0) { + return 1; + } + + if (request_received(fr, source_pubkey)) { + return 1; + } + + if (memcmp(packet, &fr->nospam, sizeof(fr->nospam)) != 0) { + return 1; + } + + if (fr->filter_function != nullptr) { + if (fr->filter_function(source_pubkey, fr->filter_function_userdata) != 0) { + return 1; + } + } + + addto_receivedlist(fr, source_pubkey); + + const uint32_t message_len = length - sizeof(fr->nospam); + VLA(uint8_t, message, message_len + 1); + memcpy(message, packet + sizeof(fr->nospam), message_len); + message[SIZEOF_VLA(message) - 1] = 0; /* Be sure the message is null terminated. */ + + fr->handle_friendrequest(fr->handle_friendrequest_object, source_pubkey, message, message_len, userdata); + return 0; +} + +void friendreq_init(Friend_Requests *fr, Friend_Connections *fr_c) +{ + set_friend_request_callback(fr_c, &friendreq_handlepacket, fr); +} + +Friend_Requests *friendreq_new(void) +{ + return (Friend_Requests *)calloc(1, sizeof(Friend_Requests)); +} + +void friendreq_kill(Friend_Requests *fr) +{ + free(fr); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/group.h b/local_pod_repo/toxcore/toxcore/toxcore/group.h new file mode 100644 index 0000000..e6f6c44 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/group.h @@ -0,0 +1,398 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * Slightly better groupchats implementation. + */ +#ifndef C_TOXCORE_TOXCORE_GROUP_H +#define C_TOXCORE_TOXCORE_GROUP_H + +#include "Messenger.h" + +typedef enum Groupchat_Type { + GROUPCHAT_TYPE_TEXT, + GROUPCHAT_TYPE_AV, +} Groupchat_Type; + +typedef void peer_on_join_cb(void *object, uint32_t conference_number, uint32_t peer_number); +typedef void peer_on_leave_cb(void *object, uint32_t conference_number, void *peer_object); +typedef void group_on_delete_cb(void *object, uint32_t conference_number); + +/** @brief Callback for group invites. + * + * data of length is what needs to be passed to `join_groupchat()`. + */ +typedef void g_conference_invite_cb(Messenger *m, uint32_t friend_number, int type, const uint8_t *cookie, + size_t length, void *user_data); + +/** Callback for group connection. */ +typedef void g_conference_connected_cb(Messenger *m, uint32_t conference_number, void *user_data); + +/** Callback for group messages. */ +typedef void g_conference_message_cb(Messenger *m, uint32_t conference_number, uint32_t peer_number, int type, + const uint8_t *message, size_t length, void *user_data); + +/** Callback for peer nickname changes. */ +typedef void peer_name_cb(Messenger *m, uint32_t conference_number, uint32_t peer_number, const uint8_t *name, + size_t length, void *user_data); + +/** Set callback function for peer list changes. */ +typedef void peer_list_changed_cb(Messenger *m, uint32_t conference_number, void *user_data); + +/** @brief Callback for title changes. + * + * If peer_number == -1, then author is unknown (e.g. initial joining the group). + */ +typedef void title_cb(Messenger *m, uint32_t conference_number, uint32_t peer_number, const uint8_t *title, + size_t length, void *user_data); + +/** @brief Callback for lossy packets. + * + * NOTE: Handler must return 0 if packet is to be relayed, -1 if the packet should not be relayed. + */ +typedef int lossy_packet_cb(void *object, uint32_t conference_number, uint32_t peer_number, void *peer_object, + const uint8_t *packet, uint16_t length); + +typedef struct Group_Chats Group_Chats; + +non_null() +const Mono_Time *g_mono_time(const Group_Chats *g_c); + +/** Set the callback for group invites. */ +non_null() +void g_callback_group_invite(Group_Chats *g_c, g_conference_invite_cb *function); + +/** Set the callback for group connection. */ +non_null() +void g_callback_group_connected(Group_Chats *g_c, g_conference_connected_cb *function); + +/** Set the callback for group messages. */ +non_null() +void g_callback_group_message(Group_Chats *g_c, g_conference_message_cb *function); + + +/** Set callback function for title changes. */ +non_null() +void g_callback_group_title(Group_Chats *g_c, title_cb *function); + +/** @brief Set callback function for peer nickname changes. + * + * It gets called every time a peer changes their nickname. + */ +non_null() +void g_callback_peer_name(Group_Chats *g_c, peer_name_cb *function); + +/** @brief Set callback function for peer list changes. + * + * It gets called every time the name list changes(new peer, deleted peer) + */ +non_null() +void g_callback_peer_list_changed(Group_Chats *g_c, peer_list_changed_cb *function); + +/** @brief Creates a new groupchat and puts it in the chats array. + * + * @param rng Random number generator used for generating the group ID. + * @param type is one of `GROUPCHAT_TYPE_*` + * + * @return group number on success. + * @retval -1 on failure. + */ +non_null() +int add_groupchat(Group_Chats *g_c, const Random *rng, uint8_t type); + +/** @brief Delete a groupchat from the chats array, informing the group first as + * appropriate. + * + * @retval true on success. + * @retval false if groupnumber is invalid. + */ +non_null() +bool del_groupchat(Group_Chats *g_c, uint32_t groupnumber, bool leave_permanently); + +/** + * @brief Copy the public key of (frozen, if frozen is true) peernumber who is in + * groupnumber to pk. + * + * @param pk must be CRYPTO_PUBLIC_KEY_SIZE long. + * + * @retval 0 on success + * @retval -1 if groupnumber is invalid. + * @retval -2 if peernumber is invalid. + */ +non_null() +int group_peer_pubkey(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint8_t *pk, bool frozen); + +/** + * @brief Return the size of (frozen, if frozen is true) peernumber's name. + * + * @retval -1 if groupnumber is invalid. + * @retval -2 if peernumber is invalid. + */ +non_null() +int group_peername_size(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, bool frozen); + +/** + * @brief Copy the name of (frozen, if frozen is true) peernumber who is in + * groupnumber to name. + * + * @param name must be at least MAX_NAME_LENGTH long. + * + * @return length of name if success + * @retval -1 if groupnumber is invalid. + * @retval -2 if peernumber is invalid. + */ +non_null() +int group_peername(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint8_t *name, + bool frozen); + +/** + * @brief Copy last active timestamp of frozen peernumber who is in groupnumber to + * last_active. + * + * @retval 0 on success. + * @retval -1 if groupnumber is invalid. + * @retval -2 if peernumber is invalid. + */ +non_null() +int group_frozen_last_active( + const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint64_t *last_active); + +/** @brief Set maximum number of frozen peers. + * + * @retval 0 on success. + * @retval -1 if groupnumber is invalid. + */ +non_null() +int group_set_max_frozen(const Group_Chats *g_c, uint32_t groupnumber, uint32_t maxfrozen); + +/** @brief invite friendnumber to groupnumber. + * + * @retval 0 on success. + * @retval -1 if groupnumber is invalid. + * @retval -2 if invite packet failed to send. + * @retval -3 if we are not connected to the group chat. + */ +non_null() +int invite_friend(const Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber); + +/** @brief Join a group (we need to have been invited first). + * + * @param expected_type is the groupchat type we expect the chat we are joining + * to have. + * + * @return group number on success. + * @retval -1 if data length is invalid. + * @retval -2 if group is not the expected type. + * @retval -3 if friendnumber is invalid. + * @retval -4 if client is already in this group. + * @retval -5 if group instance failed to initialize. + * @retval -6 if join packet fails to send. + */ +non_null() +int join_groupchat( + Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_type, const uint8_t *data, uint16_t length); + +/** @brief send a group message + * @retval 0 on success + * @see send_message_group for error codes. + */ +non_null() +int group_message_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *message, uint16_t length); + +/** @brief send a group action + * @retval 0 on success + * @see send_message_group for error codes. + */ +non_null() +int group_action_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *action, uint16_t length); + +/** @brief set the group's title, limited to MAX_NAME_LENGTH. + * @retval 0 on success + * @retval -1 if groupnumber is invalid. + * @retval -2 if title is too long or empty. + * @retval -3 if packet fails to send. + */ +non_null() +int group_title_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *title, uint8_t title_len); + + +/** @brief return the group's title size. + * @retval -1 of groupnumber is invalid. + * @retval -2 if title is too long or empty. + */ +non_null() +int group_title_get_size(const Group_Chats *g_c, uint32_t groupnumber); + +/** @brief Get group title from groupnumber and put it in title. + * + * Title needs to be a valid memory location with a size of at least MAX_NAME_LENGTH (128) bytes. + * + * @return length of copied title if success. + * @retval -1 if groupnumber is invalid. + * @retval -2 if title is too long or empty. + */ +non_null() +int group_title_get(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *title); + +/** + * @return the number of (frozen, if frozen is true) peers in the group chat on success. + * @retval -1 if groupnumber is invalid. + */ +non_null() +int group_number_peers(const Group_Chats *g_c, uint32_t groupnumber, bool frozen); + +/** + * @retval 1 if the peernumber corresponds to ours. + * @retval 0 if the peernumber is not ours. + * @retval -1 if groupnumber is invalid. + * @retval -2 if peernumber is invalid. + * @retval -3 if we are not connected to the group chat. + */ +non_null() +int group_peernumber_is_ours(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber); + +/** Set handlers for custom lossy packets. */ +non_null() +void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, lossy_packet_cb *function); + +/** @brief High level function to send custom lossy packets. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +int send_group_lossy_packet(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length); + +/** + * @brief Return the number of chats in the instance m. + * + * You should use this to determine how much memory to allocate + * for copy_chatlist. + */ +non_null() +uint32_t count_chatlist(const Group_Chats *g_c); + +/** @brief Copy a list of valid chat IDs into the array out_list. + * + * If out_list is NULL, returns 0. + * Otherwise, returns the number of elements copied. + * If the array was too small, the contents + * of out_list will be truncated to list_size. + */ +non_null() +uint32_t copy_chatlist(const Group_Chats *g_c, uint32_t *out_list, uint32_t list_size); + +/** @brief return the type of groupchat (GROUPCHAT_TYPE_) that groupnumber is. + * + * @retval -1 on failure. + * @return type on success. + */ +non_null() +int group_get_type(const Group_Chats *g_c, uint32_t groupnumber); + +/** @brief Copies the unique id of `group_chat[groupnumber]` into `id`. + * + * @retval false on failure. + * @retval true on success. + */ +non_null() +bool conference_get_id(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *id); + +non_null() int32_t conference_by_id(const Group_Chats *g_c, const uint8_t *id); + +/** Send current name (set in messenger) to all online groups. */ +non_null() +void send_name_all_groups(const Group_Chats *g_c); + +/** @brief Set the object that is tied to the group chat. + * + * @retval 0 on success. + * @retval -1 on failure + */ +non_null(1) nullable(3) +int group_set_object(const Group_Chats *g_c, uint32_t groupnumber, void *object); + +/** @brief Set the object that is tied to the group peer. + * + * @retval 0 on success. + * @retval -1 on failure + */ +non_null(1) nullable(4) +int group_peer_set_object(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, void *object); + +/** @brief Return the object tied to the group chat previously set by group_set_object. + * + * @retval NULL on failure. + * @return object on success. + */ +non_null() +void *group_get_object(const Group_Chats *g_c, uint32_t groupnumber); + +/** @brief Return the object tied to the group chat peer previously set by group_peer_set_object. + * + * @retval NULL on failure. + * @return object on success. + */ +non_null() +void *group_peer_get_object(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber); + +/** @brief Set a function to be called when a new peer joins a group chat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +non_null(1) nullable(3) +int callback_groupchat_peer_new(const Group_Chats *g_c, uint32_t groupnumber, peer_on_join_cb *function); + +/** @brief Set a function to be called when a peer leaves a group chat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +non_null(1) nullable(3) +int callback_groupchat_peer_delete(const Group_Chats *g_c, uint32_t groupnumber, peer_on_leave_cb *function); + +/** @brief Set a function to be called when the group chat is deleted. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +non_null(1) nullable(3) +int callback_groupchat_delete(const Group_Chats *g_c, uint32_t groupnumber, group_on_delete_cb *function); + +/** Return size of the conferences data (for saving). */ +non_null() +uint32_t conferences_size(const Group_Chats *g_c); + +/** Save the conferences in data (must be allocated memory of size at least `conferences_size()`). */ +non_null() +uint8_t *conferences_save(const Group_Chats *g_c, uint8_t *data); + +/** + * Load a state section. + * + * @param data Data to load + * @param length Length of data + * @param type Type of section (`STATE_TYPE_*`) + * @param status Result of loading section is stored here if the section is handled. + * @return true iff section handled. + */ +non_null() +bool conferences_load_state_section( + Group_Chats *g_c, const uint8_t *data, uint32_t length, uint16_t type, State_Load_Status *status); + +/** Create new groupchat instance. */ +non_null() +Group_Chats *new_groupchats(const Mono_Time *mono_time, Messenger *m); + +/** main groupchats loop. */ +non_null(1) nullable(2) +void do_groupchats(Group_Chats *g_c, void *userdata); + +/** Free everything related with group chats. */ +nullable(1) +void kill_groupchats(Group_Chats *g_c); + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/group.m b/local_pod_repo/toxcore/toxcore/toxcore/group.m new file mode 100644 index 0000000..0e851b1 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/group.m @@ -0,0 +1,3867 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * Slightly better groupchats implementation. + */ +#include "group.h" + +#include +#include +#include + +#include "ccompat.h" +#include "mono_time.h" +#include "state.h" +#include "util.h" + +enum { + /** Connection is to one of the closest DESIRED_CLOSEST peers */ + GROUPCHAT_CONNECTION_REASON_CLOSEST = 1 << 0, + + /** Connection is to a peer we are introducing to the conference */ + GROUPCHAT_CONNECTION_REASON_INTRODUCING = 1 << 1, + + /** Connection is to a peer who is introducing us to the conference */ + GROUPCHAT_CONNECTION_REASON_INTRODUCER = 1 << 2, +}; + +typedef enum Groupchat_Connection_Type { + GROUPCHAT_CONNECTION_NONE, + GROUPCHAT_CONNECTION_CONNECTING, + GROUPCHAT_CONNECTION_ONLINE, +} Groupchat_Connection_Type; + +typedef enum Groupchat_Status { + GROUPCHAT_STATUS_NONE, + GROUPCHAT_STATUS_VALID, + GROUPCHAT_STATUS_CONNECTED, +} Groupchat_Status; + +#define GROUP_ID_LENGTH CRYPTO_SYMMETRIC_KEY_SIZE + +#define DESIRED_CLOSEST 4 +#define MAX_GROUP_CONNECTIONS 16 +#define MAX_LAST_MESSAGE_INFOS 8 +#define MAX_LOSSY_COUNT 256 + +/** Maximum number of frozen peers to store; `group_set_max_frozen()` overrides. */ +#define MAX_FROZEN_DEFAULT 128 + +typedef struct Message_Info { + uint32_t message_number; + uint8_t message_id; +} Message_Info; + +typedef struct Group_Peer { + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + bool temp_pk_updated; + bool is_friend; + + uint64_t last_active; + + Message_Info + last_message_infos[MAX_LAST_MESSAGE_INFOS]; /* received messages, strictly decreasing in message_number */ + uint8_t num_last_message_infos; + + uint8_t nick[MAX_NAME_LENGTH]; + uint8_t nick_len; + bool nick_updated; + + uint16_t peer_number; + + uint8_t recv_lossy[MAX_LOSSY_COUNT]; + uint16_t bottom_lossy_number; + uint16_t top_lossy_number; + + void *object; +} Group_Peer; + +typedef struct Groupchat_Connection { + uint8_t type; /* `GROUPCHAT_CONNECTION_*` */ + uint8_t reasons; /* bit field with flags `GROUPCHAT_CONNECTION_REASON_*` */ + uint32_t number; + uint16_t group_number; +} Groupchat_Connection; + +typedef struct Groupchat_Closest { + /** + * Whether this peer is active in the closest_peers array. + */ + bool active; + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; +} Groupchat_Closest; + +typedef struct Group_c { + uint8_t status; + + bool need_send_name; + bool title_fresh; + + Group_Peer *group; + uint32_t numpeers; + + Group_Peer *frozen; + uint32_t numfrozen; + + uint32_t maxfrozen; + + Groupchat_Connection connections[MAX_GROUP_CONNECTIONS]; + + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + Groupchat_Closest closest_peers[DESIRED_CLOSEST]; + uint8_t changed; + + uint8_t type; + uint8_t id[GROUP_ID_LENGTH]; + + uint8_t title[MAX_NAME_LENGTH]; + uint8_t title_len; + + uint32_t message_number; + uint16_t lossy_message_number; + uint16_t peer_number; + + uint64_t last_sent_ping; + + uint32_t num_introducer_connections; + + void *object; + + peer_on_join_cb *peer_on_join; + peer_on_leave_cb *peer_on_leave; + group_on_delete_cb *group_on_delete; +} Group_c; + +struct Group_Chats { + const Mono_Time *mono_time; + + Messenger *m; + Friend_Connections *fr_c; + + Group_c *chats; + uint16_t num_chats; + + g_conference_invite_cb *invite_callback; + g_conference_connected_cb *connected_callback; + g_conference_message_cb *message_callback; + peer_name_cb *peer_name_callback; + peer_list_changed_cb *peer_list_changed_callback; + title_cb *title_callback; + + lossy_packet_cb *lossy_packethandlers[256]; +}; + +static const Group_c empty_group_c = {0}; +static const Group_Peer empty_group_peer = {{0}}; + +/** + * Packet type IDs as per the protocol specification. + */ +typedef enum Group_Message_Id { + GROUP_MESSAGE_PING_ID = 0, + GROUP_MESSAGE_NEW_PEER_ID = 16, + GROUP_MESSAGE_KILL_PEER_ID = 17, + GROUP_MESSAGE_FREEZE_PEER_ID = 18, + GROUP_MESSAGE_NAME_ID = 48, + GROUP_MESSAGE_TITLE_ID = 49, +} Group_Message_Id; + +#define GROUP_MESSAGE_NEW_PEER_LENGTH (sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2) +#define GROUP_MESSAGE_KILL_PEER_LENGTH (sizeof(uint16_t)) + +#define MAX_GROUP_MESSAGE_DATA_LEN (MAX_CRYPTO_DATA_SIZE - (1 + MIN_MESSAGE_PACKET_LEN)) + +typedef enum Invite_Id { + INVITE_ID = 0, + INVITE_ACCEPT_ID = 1, + INVITE_MEMBER_ID = 2, +} Invite_Id; + +#define INVITE_PACKET_SIZE (1 + sizeof(uint16_t) + 1 + GROUP_ID_LENGTH) +#define INVITE_ACCEPT_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH) +#define INVITE_MEMBER_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH + sizeof(uint16_t)) + +#define ONLINE_PACKET_DATA_SIZE (sizeof(uint16_t) + 1 + GROUP_ID_LENGTH) + +typedef enum Peer_Id { + PEER_INTRODUCED_ID = 1, + PEER_QUERY_ID = 8, + PEER_RESPONSE_ID = 9, + PEER_TITLE_ID = 10, +} Peer_Id; + +#define MIN_MESSAGE_PACKET_LEN (sizeof(uint16_t) * 2 + sizeof(uint32_t) + 1) + +static_assert(GROUP_ID_LENGTH == CRYPTO_PUBLIC_KEY_SIZE, + "GROUP_ID_LENGTH should be equal to CRYPTO_PUBLIC_KEY_SIZE"); + +const Mono_Time *g_mono_time(const Group_Chats *g_c) +{ + return g_c->mono_time; +} + +non_null() +static bool group_id_eq(const uint8_t *a, const uint8_t *b) +{ + return pk_equal(a, b); +} + +non_null() +static bool g_title_eq(Group_c *g, const uint8_t *title, uint8_t title_len) +{ + return memeq(g->title, g->title_len, title, title_len); +} + +non_null() +static bool g_peer_nick_eq(Group_Peer *peer, const uint8_t *nick, uint8_t nick_len) +{ + return memeq(peer->nick, peer->nick_len, nick, nick_len); +} + +/** + * @retval false if the groupnumber is not valid. + * @retval true if the groupnumber is valid. + */ +non_null() +static bool is_groupnumber_valid(const Group_Chats *g_c, uint32_t groupnumber) +{ + return groupnumber < g_c->num_chats + && g_c->chats != nullptr + && g_c->chats[groupnumber].status != GROUPCHAT_STATUS_NONE; +} + + +/** @brief Set the size of the groupchat list to num. + * + * @retval false if realloc fails. + * @retval true if it succeeds. + */ +non_null() +static bool realloc_conferences(Group_Chats *g_c, uint16_t num) +{ + if (num == 0) { + free(g_c->chats); + g_c->chats = nullptr; + return true; + } + + Group_c *newgroup_chats = (Group_c *)realloc(g_c->chats, num * sizeof(Group_c)); + + if (newgroup_chats == nullptr) { + return false; + } + + g_c->chats = newgroup_chats; + return true; +} + +non_null() +static void setup_conference(Group_c *g) +{ + *g = empty_group_c; + g->maxfrozen = MAX_FROZEN_DEFAULT; +} + +/** @brief Create a new empty groupchat connection. + * + * @retval -1 on failure. + * @return groupnumber on success. + */ +non_null() +static int32_t create_group_chat(Group_Chats *g_c) +{ + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + if (g_c->chats[i].status == GROUPCHAT_STATUS_NONE) { + return i; + } + } + + if (realloc_conferences(g_c, g_c->num_chats + 1)) { + const uint16_t id = g_c->num_chats; + ++g_c->num_chats; + setup_conference(&g_c->chats[id]); + return id; + } + + return -1; +} + +non_null() +static void wipe_group_c(Group_c *g) +{ + free(g->frozen); + free(g->group); + crypto_memzero(g, sizeof(Group_c)); +} + +/** @brief Wipe a groupchat. + * + * @retval true on success. + */ +non_null() +static bool wipe_group_chat(Group_Chats *g_c, uint32_t groupnumber) +{ + if (groupnumber >= g_c->num_chats || g_c->chats == nullptr) { + return false; + } + + wipe_group_c(&g_c->chats[groupnumber]); + + uint16_t i; + + for (i = g_c->num_chats; i != 0; --i) { + if (g_c->chats[i - 1].status != GROUPCHAT_STATUS_NONE) { + break; + } + } + + if (g_c->num_chats != i) { + g_c->num_chats = i; + realloc_conferences(g_c, g_c->num_chats); + } + + return true; +} + +non_null() +static Group_c *get_group_c(const Group_Chats *g_c, uint32_t groupnumber) +{ + if (!is_groupnumber_valid(g_c, groupnumber)) { + return nullptr; + } + + return &g_c->chats[groupnumber]; +} + +/** + * check if peer with real_pk is in peer array. + * + * @return peer index if peer is in group. + * @retval -1 if peer is not in group. + * + * TODO(irungentoo): make this more efficient. + */ +non_null() +static int peer_in_group(const Group_c *g, const uint8_t *real_pk) +{ + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (pk_equal(g->group[i].real_pk, real_pk)) { + return i; + } + } + + return -1; +} + +non_null() +static int frozen_in_group(const Group_c *g, const uint8_t *real_pk) +{ + for (uint32_t i = 0; i < g->numfrozen; ++i) { + if (pk_equal(g->frozen[i].real_pk, real_pk)) { + return i; + } + } + + return -1; +} + +/** + * check if group with the given type and id is in group array. + * + * @return group number if peer is in list. + * @retval -1 if group is not in list. + * + * TODO(irungentoo): make this more efficient and maybe use constant time comparisons? + */ +non_null() +static int32_t get_group_num(const Group_Chats *g_c, const uint8_t type, const uint8_t *id) +{ + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + if (g_c->chats[i].type == type && group_id_eq(g_c->chats[i].id, id)) { + return i; + } + } + + return -1; +} + +int32_t conference_by_id(const Group_Chats *g_c, const uint8_t *id) +{ + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + if (group_id_eq(g_c->chats[i].id, id)) { + return i; + } + } + + return -1; +} + +/** + * check if peer with peer_number is in peer array. + * + * @return peer index if peer is in chat. + * @retval -1 if peer is not in chat. + * + * TODO(irungentoo): make this more efficient. + */ +non_null() +static int get_peer_index(const Group_c *g, uint16_t peer_number) +{ + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (g->group[i].peer_number == peer_number) { + return i; + } + } + + return -1; +} + + +non_null() +static uint64_t calculate_comp_value(const uint8_t *pk1, const uint8_t *pk2) +{ + uint64_t cmp1 = 0; + uint64_t cmp2 = 0; + + for (size_t i = 0; i < sizeof(uint64_t); ++i) { + cmp1 = (cmp1 << 8) + (uint64_t)pk1[i]; + cmp2 = (cmp2 << 8) + (uint64_t)pk2[i]; + } + + return cmp1 - cmp2; +} + +typedef enum Groupchat_Closest_Change { + GROUPCHAT_CLOSEST_CHANGE_NONE, + GROUPCHAT_CLOSEST_CHANGE_ADDED, + GROUPCHAT_CLOSEST_CHANGE_REMOVED, +} Groupchat_Closest_Change; + +non_null() +static bool add_to_closest(Group_c *g, const uint8_t *real_pk, const uint8_t *temp_pk) +{ + if (pk_equal(g->real_pk, real_pk)) { + return false; + } + + unsigned int index = DESIRED_CLOSEST; + + for (unsigned int i = 0; i < DESIRED_CLOSEST; ++i) { + if (g->closest_peers[i].active && pk_equal(real_pk, g->closest_peers[i].real_pk)) { + return true; + } + } + + for (unsigned int i = 0; i < DESIRED_CLOSEST; ++i) { + if (!g->closest_peers[i].active) { + index = i; + break; + } + } + + if (index == DESIRED_CLOSEST) { + uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk); + uint64_t comp_d = 0; + + for (unsigned int i = 0; i < (DESIRED_CLOSEST / 2); ++i) { + const uint64_t comp = calculate_comp_value(g->real_pk, g->closest_peers[i].real_pk); + + if (comp > comp_val && comp > comp_d) { + index = i; + comp_d = comp; + } + } + + comp_val = calculate_comp_value(real_pk, g->real_pk); + + for (unsigned int i = DESIRED_CLOSEST / 2; i < DESIRED_CLOSEST; ++i) { + uint64_t comp = calculate_comp_value(g->closest_peers[i].real_pk, g->real_pk); + + if (comp > comp_val && comp > comp_d) { + index = i; + comp_d = comp; + } + } + } + + if (index == DESIRED_CLOSEST) { + return false; + } + + uint8_t old_real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t old_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + bool old = false; + + if (g->closest_peers[index].active) { + memcpy(old_real_pk, g->closest_peers[index].real_pk, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(old_temp_pk, g->closest_peers[index].temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + old = true; + } + + g->closest_peers[index].active = true; + memcpy(g->closest_peers[index].real_pk, real_pk, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(g->closest_peers[index].temp_pk, temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + + if (old) { + add_to_closest(g, old_real_pk, old_temp_pk); + } + + if (g->changed == GROUPCHAT_CLOSEST_CHANGE_NONE) { + g->changed = GROUPCHAT_CLOSEST_CHANGE_ADDED; + } + + return true; +} + +non_null() +static bool pk_in_closest_peers(const Group_c *g, const uint8_t *real_pk) +{ + for (unsigned int i = 0; i < DESIRED_CLOSEST; ++i) { + if (!g->closest_peers[i].active) { + continue; + } + + if (pk_equal(g->closest_peers[i].real_pk, real_pk)) { + return true; + } + } + + return false; +} + +non_null() +static void remove_connection_reason(Group_Chats *g_c, Group_c *g, uint16_t i, uint8_t reason); + +non_null() +static void purge_closest(Group_Chats *g_c, uint32_t groupnumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return; + } + + for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { + continue; + } + + if ((g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_CLOSEST) == 0) { + continue; + } + + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->connections[i].number); + + if (!pk_in_closest_peers(g, real_pk)) { + remove_connection_reason(g_c, g, i, GROUPCHAT_CONNECTION_REASON_CLOSEST); + } + } +} + +non_null() +static bool send_packet_online(const Friend_Connections *fr_c, int friendcon_id, uint16_t group_num, + uint8_t type, const uint8_t *id); + +non_null() +static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, Group_c *g, uint8_t reason, + bool lock); + +non_null(1) nullable(3) +static void add_closest_connections(Group_Chats *g_c, uint32_t groupnumber, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return; + } + + for (uint32_t i = 0; i < DESIRED_CLOSEST; ++i) { + if (!g->closest_peers[i].active) { + continue; + } + + int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->closest_peers[i].real_pk); + + bool fresh = false; + + if (friendcon_id == -1) { + friendcon_id = new_friend_connection(g_c->fr_c, g->closest_peers[i].real_pk); + fresh = true; + + if (friendcon_id == -1) { + continue; + } + + set_dht_temp_pk(g_c->fr_c, friendcon_id, g->closest_peers[i].temp_pk, userdata); + } + + const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g, + GROUPCHAT_CONNECTION_REASON_CLOSEST, !fresh); + + if (connection_index == -1) { + if (fresh) { + kill_friend_connection(g_c->fr_c, friendcon_id); + } + + continue; + } + + if (friend_con_connected(g_c->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED + && g->connections[connection_index].type == GROUPCHAT_CONNECTION_CONNECTING) { + send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->type, g->id); + } + } +} + +non_null(1) nullable(3) +static bool connect_to_closest(Group_Chats *g_c, uint32_t groupnumber, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return false; + } + + if (g->changed == GROUPCHAT_CLOSEST_CHANGE_NONE) { + return true; + } + + if (g->changed == GROUPCHAT_CLOSEST_CHANGE_REMOVED) { + for (uint32_t i = 0; i < g->numpeers; ++i) { + add_to_closest(g, g->group[i].real_pk, g->group[i].temp_pk); + } + } + + purge_closest(g_c, groupnumber); + + add_closest_connections(g_c, groupnumber, userdata); + + g->changed = GROUPCHAT_CLOSEST_CHANGE_NONE; + + return true; +} + +non_null() +static int get_frozen_index(const Group_c *g, uint16_t peer_number) +{ + for (uint32_t i = 0; i < g->numfrozen; ++i) { + if (g->frozen[i].peer_number == peer_number) { + return i; + } + } + + return -1; +} + +non_null() +static bool delete_frozen(Group_c *g, uint32_t frozen_index) +{ + if (frozen_index >= g->numfrozen) { + return false; + } + + --g->numfrozen; + + if (g->numfrozen == 0) { + free(g->frozen); + g->frozen = nullptr; + } else { + if (g->numfrozen != frozen_index) { + g->frozen[frozen_index] = g->frozen[g->numfrozen]; + } + + Group_Peer *const frozen_temp = (Group_Peer *)realloc(g->frozen, sizeof(Group_Peer) * g->numfrozen); + + if (frozen_temp == nullptr) { + return false; + } + + g->frozen = frozen_temp; + } + + return true; +} + +/** @brief Update last_active timestamp on peer, and thaw the peer if it is frozen. + * + * @return peer index if peer is in the conference. + * @retval -1 otherwise, and on error. + */ +non_null(1) nullable(4) +static int note_peer_active(Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_number, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + const int peer_index = get_peer_index(g, peer_number); + + if (peer_index != -1) { + g->group[peer_index].last_active = mono_time_get(g_c->mono_time); + return peer_index; + } + + const int frozen_index = get_frozen_index(g, peer_number); + + if (frozen_index == -1) { + return -1; + } + + /* Now thaw the peer */ + + Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * (g->numpeers + 1)); + + if (temp == nullptr) { + return -1; + } + + const uint32_t thawed_index = g->numpeers; + + g->group = temp; + g->group[thawed_index] = g->frozen[frozen_index]; + g->group[thawed_index].temp_pk_updated = false; + g->group[thawed_index].last_active = mono_time_get(g_c->mono_time); + + add_to_closest(g, g->group[thawed_index].real_pk, g->group[thawed_index].temp_pk); + + ++g->numpeers; + + delete_frozen(g, frozen_index); + + if (g_c->peer_list_changed_callback != nullptr) { + g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata); + } + + if (g->peer_on_join != nullptr) { + g->peer_on_join(g->object, groupnumber, thawed_index); + } + + g->need_send_name = true; + + return thawed_index; +} + +non_null(1) nullable(4) +static bool delpeer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void *userdata); + +non_null(1, 3) nullable(4) +static void delete_any_peer_with_pk(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *real_pk, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return; + } + + const int peer_index = peer_in_group(g, real_pk); + + if (peer_index >= 0) { + delpeer(g_c, groupnumber, peer_index, userdata); + } + + const int frozen_index = frozen_in_group(g, real_pk); + + if (frozen_index >= 0) { + delete_frozen(g, frozen_index); + } +} + +/** @brief Add a peer to the group chat, or update an existing peer. + * + * fresh indicates whether we should consider this information on the peer to + * be current, and so should update temp_pk and consider the peer active. + * + * do_gc_callback indicates whether we want to trigger callbacks set by the client + * via the public API. This should be set to false if this function is called + * from outside of the `tox_iterate()` loop. + * + * @return peer_index if success or peer already in chat. + * @retval -1 if error. + */ +non_null(1, 3, 4) nullable(6) +static int addpeer(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *real_pk, const uint8_t *temp_pk, + uint16_t peer_number, void *userdata, bool fresh, bool do_gc_callback) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + const int peer_index = fresh ? + note_peer_active(g_c, groupnumber, peer_number, userdata) : + get_peer_index(g, peer_number); + + if (peer_index != -1) { + if (!pk_equal(g->group[peer_index].real_pk, real_pk)) { + return -1; + } + + if (fresh || !g->group[peer_index].temp_pk_updated) { + pk_copy(g->group[peer_index].temp_pk, temp_pk); + g->group[peer_index].temp_pk_updated = true; + } + + return peer_index; + } + + if (!fresh) { + const int frozen_index = get_frozen_index(g, peer_number); + + if (frozen_index != -1) { + if (!pk_equal(g->frozen[frozen_index].real_pk, real_pk)) { + return -1; + } + + pk_copy(g->frozen[frozen_index].temp_pk, temp_pk); + + return -1; + } + } + + delete_any_peer_with_pk(g_c, groupnumber, real_pk, userdata); + + Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * (g->numpeers + 1)); + + if (temp == nullptr) { + return -1; + } + + temp[g->numpeers] = empty_group_peer; + g->group = temp; + + const uint32_t new_index = g->numpeers; + + pk_copy(g->group[new_index].real_pk, real_pk); + pk_copy(g->group[new_index].temp_pk, temp_pk); + g->group[new_index].temp_pk_updated = true; + g->group[new_index].peer_number = peer_number; + g->group[new_index].last_active = mono_time_get(g_c->mono_time); + g->group[new_index].is_friend = getfriend_id(g_c->m, real_pk) != -1; + ++g->numpeers; + + add_to_closest(g, real_pk, temp_pk); + + if (do_gc_callback && g_c->peer_list_changed_callback != nullptr) { + g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata); + } + + if (g->peer_on_join != nullptr) { + g->peer_on_join(g->object, groupnumber, new_index); + } + + return new_index; +} + +non_null() +static void remove_connection(Group_Chats *g_c, Group_c *g, uint16_t i) +{ + if ((g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_INTRODUCER) != 0) { + --g->num_introducer_connections; + } + + kill_friend_connection(g_c->fr_c, g->connections[i].number); + g->connections[i].type = GROUPCHAT_CONNECTION_NONE; +} + +non_null() +static void remove_from_closest(Group_c *g, int peer_index) +{ + for (uint32_t i = 0; i < DESIRED_CLOSEST; ++i) { + if (g->closest_peers[i].active + && pk_equal(g->closest_peers[i].real_pk, g->group[peer_index].real_pk)) { + g->closest_peers[i].active = false; + g->changed = GROUPCHAT_CLOSEST_CHANGE_REMOVED; + break; + } + } +} + +/** + * Delete a peer from the group chat. + * + * return true on success + */ +static bool delpeer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return false; + } + + remove_from_closest(g, peer_index); + + const int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->group[peer_index].real_pk); + + if (friendcon_id != -1) { + for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { + continue; + } + + if (g->connections[i].number == (unsigned int)friendcon_id) { + remove_connection(g_c, g, i); + break; + } + } + } + + --g->numpeers; + + void *peer_object = g->group[peer_index].object; + + if (g->numpeers == 0) { + free(g->group); + g->group = nullptr; + } else { + if (g->numpeers != (uint32_t)peer_index) { + g->group[peer_index] = g->group[g->numpeers]; + } + + Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * g->numpeers); + + if (temp == nullptr) { + return false; + } + + g->group = temp; + } + + if (g_c->peer_list_changed_callback != nullptr) { + g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata); + } + + if (g->peer_on_leave != nullptr) { + g->peer_on_leave(g->object, groupnumber, peer_object); + } + + return true; +} + +static int cmp_u64(uint64_t a, uint64_t b) +{ + return (a > b ? 1 : 0) - (a < b ? 1 : 0); +} + +/** Order peers with friends first and with more recently active earlier */ +non_null() +static int cmp_frozen(const void *a, const void *b) +{ + const Group_Peer *pa = (const Group_Peer *)a; + const Group_Peer *pb = (const Group_Peer *)b; + + if (pa->is_friend ^ pb->is_friend) { + return pa->is_friend ? -1 : 1; + } + + return cmp_u64(pb->last_active, pa->last_active); +} + +/** @brief Delete frozen peers as necessary to ensure at most `g->maxfrozen` remain. + * + * @retval true if any frozen peers are removed. + */ +non_null() +static bool delete_old_frozen(Group_c *g) +{ + if (g->numfrozen <= g->maxfrozen) { + return false; + } + + if (g->maxfrozen == 0) { + free(g->frozen); + g->frozen = nullptr; + g->numfrozen = 0; + return true; + } + + qsort(g->frozen, g->numfrozen, sizeof(Group_Peer), cmp_frozen); + + Group_Peer *temp = (Group_Peer *)realloc(g->frozen, sizeof(Group_Peer) * g->maxfrozen); + + if (temp == nullptr) { + return false; + } + + g->frozen = temp; + + g->numfrozen = g->maxfrozen; + + return true; +} + +non_null() +static bool try_send_rejoin(Group_Chats *g_c, Group_c *g, const uint8_t *real_pk); + +non_null(1) nullable(4) +static bool freeze_peer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return false; + } + + Group_Peer *temp = (Group_Peer *)realloc(g->frozen, sizeof(Group_Peer) * (g->numfrozen + 1)); + + if (temp == nullptr) { + return false; + } + + g->frozen = temp; + g->frozen[g->numfrozen] = g->group[peer_index]; + g->frozen[g->numfrozen].object = nullptr; + + if (!delpeer(g_c, groupnumber, peer_index, userdata)) { + return false; + } + + try_send_rejoin(g_c, g, g->frozen[g->numfrozen].real_pk); + + ++g->numfrozen; + + delete_old_frozen(g); + + return true; +} + + +/** @brief Set the nick for a peer. + * + * do_gc_callback indicates whether we want to trigger callbacks set by the client + * via the public API. This should be set to false if this function is called + * from outside of the `tox_iterate()` loop. + * + * @retval true on success. + */ +non_null(1, 4) nullable(6) +static bool setnick(Group_Chats *g_c, uint32_t groupnumber, int peer_index, const uint8_t *nick, uint16_t nick_len, + void *userdata, bool do_gc_callback) +{ + if (nick_len > MAX_NAME_LENGTH) { + return false; + } + + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return false; + } + + g->group[peer_index].nick_updated = true; + + if (g_peer_nick_eq(&g->group[peer_index], nick, nick_len)) { + /* same name as already stored */ + return true; + } + + if (nick_len > 0) { + memcpy(g->group[peer_index].nick, nick, nick_len); + } + + g->group[peer_index].nick_len = nick_len; + + if (do_gc_callback && g_c->peer_name_callback != nullptr) { + g_c->peer_name_callback(g_c->m, groupnumber, peer_index, nick, nick_len, userdata); + } + + return true; +} + +/** @brief Set the title for a group. + * + * @retval true on success. + */ +non_null(1, 4) nullable(6) +static bool settitle(Group_Chats *g_c, uint32_t groupnumber, int peer_index, const uint8_t *title, uint8_t title_len, + void *userdata) +{ + if (title_len > MAX_NAME_LENGTH || title_len == 0) { + return false; + } + + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return false; + } + + if (g_title_eq(g, title, title_len)) { + /* same title as already set */ + return true; + } + + memcpy(g->title, title, title_len); + g->title_len = title_len; + + g->title_fresh = true; + + if (g_c->title_callback != nullptr) { + g_c->title_callback(g_c->m, groupnumber, peer_index, title, title_len, userdata); + } + + return true; +} + +/** Check if the group has no online connection, and freeze all peers if so */ +non_null(1) nullable(3) +static void check_disconnected(Group_Chats *g_c, uint32_t groupnumber, void *userdata) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return; + } + + for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type == GROUPCHAT_CONNECTION_ONLINE) { + return; + } + } + + for (uint32_t i = 0; i < g->numpeers; ++i) { + while (i < g->numpeers && !pk_equal(g->group[i].real_pk, g->real_pk)) { + freeze_peer(g_c, groupnumber, i, userdata); + } + } +} + +non_null(1) nullable(5) +static void set_conns_type_connections(Group_Chats *g_c, uint32_t groupnumber, int friendcon_id, uint8_t type, + void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return; + } + + for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { + continue; + } + + if (g->connections[i].number != (unsigned int)friendcon_id) { + continue; + } + + if (type == GROUPCHAT_CONNECTION_ONLINE) { + send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->type, g->id); + } else { + g->connections[i].type = type; + check_disconnected(g_c, groupnumber, userdata); + } + } +} + +/** Set the type for all connections with friendcon_id */ +non_null(1) nullable(4) +static void set_conns_status_groups(Group_Chats *g_c, int friendcon_id, uint8_t type, void *userdata) +{ + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + set_conns_type_connections(g_c, i, friendcon_id, type, userdata); + } +} + +non_null() +static void rejoin_frozen_friend(Group_Chats *g_c, int friendcon_id) +{ + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, friendcon_id); + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + Group_c *g = get_group_c(g_c, i); + + if (g == nullptr) { + continue; + } + + for (uint32_t j = 0; j < g->numfrozen; ++j) { + if (pk_equal(g->frozen[j].real_pk, real_pk)) { + try_send_rejoin(g_c, g, real_pk); + break; + } + } + } +} + +non_null(1) nullable(4) +static int g_handle_any_status(void *object, int friendcon_id, bool status, void *userdata) +{ + Group_Chats *g_c = (Group_Chats *)object; + + if (status) { + rejoin_frozen_friend(g_c, friendcon_id); + } + + return 0; +} + +non_null(1) nullable(4) +static int g_handle_status(void *object, int friendcon_id, bool status, void *userdata) +{ + Group_Chats *g_c = (Group_Chats *)object; + + if (status) { /* Went online */ + set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CONNECTION_ONLINE, userdata); + } else { /* Went offline */ + set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CONNECTION_CONNECTING, userdata); + // TODO(irungentoo): remove timedout connections? + } + + return 0; +} + +non_null(1, 3) nullable(5) +static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata); +non_null(1, 3) nullable(5) +static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata); + +/** @brief Add friend to group chat. + * + * @return connections index on success + * @retval -1 on failure. + */ +static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, Group_c *g, uint8_t reason, + bool lock) +{ + uint16_t empty = MAX_GROUP_CONNECTIONS; + uint16_t ind = MAX_GROUP_CONNECTIONS; + + for (uint16_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { + empty = i; + continue; + } + + if (g->connections[i].number == (uint32_t)friendcon_id) { + ind = i; /* Already in list. */ + break; + } + } + + if (ind == MAX_GROUP_CONNECTIONS) { + if (empty == MAX_GROUP_CONNECTIONS) { + return -1; + } + + if (lock) { + friend_connection_lock(g_c->fr_c, friendcon_id); + } + + g->connections[empty].type = GROUPCHAT_CONNECTION_CONNECTING; + g->connections[empty].number = friendcon_id; + g->connections[empty].reasons = 0; + // TODO(irungentoo): + friend_connection_callbacks(g_c->m->fr_c, friendcon_id, GROUPCHAT_CALLBACK_INDEX, &g_handle_status, &g_handle_packet, + &handle_lossy, g_c, friendcon_id); + ind = empty; + } + + if ((g->connections[ind].reasons & reason) == 0) { + g->connections[ind].reasons |= reason; + + if (reason == GROUPCHAT_CONNECTION_REASON_INTRODUCER) { + ++g->num_introducer_connections; + } + } + + return ind; +} + +non_null() +static bool send_peer_introduced(const Group_Chats *g_c, int friendcon_id, uint16_t group_num); + +/** @brief Removes reason for keeping connection. + * + * Kills connection if this was the last reason. + */ +static void remove_connection_reason(Group_Chats *g_c, Group_c *g, uint16_t i, uint8_t reason) +{ + if ((g->connections[i].reasons & reason) == 0) { + return; + } + + g->connections[i].reasons &= ~reason; + + if (reason == GROUPCHAT_CONNECTION_REASON_INTRODUCER) { + --g->num_introducer_connections; + + if (g->connections[i].type == GROUPCHAT_CONNECTION_ONLINE) { + send_peer_introduced(g_c, g->connections[i].number, g->connections[i].group_number); + } + } + + if (g->connections[i].reasons == 0) { + kill_friend_connection(g_c->fr_c, g->connections[i].number); + g->connections[i].type = GROUPCHAT_CONNECTION_NONE; + } +} + +/** @brief Creates a new groupchat and puts it in the chats array. + * + * @param rng Random number generator used for generating the group ID. + * @param type is one of `GROUPCHAT_TYPE_*` + * + * @return group number on success. + * @retval -1 on failure. + */ +int add_groupchat(Group_Chats *g_c, const Random *rng, uint8_t type) +{ + const int32_t groupnumber = create_group_chat(g_c); + + if (groupnumber == -1) { + return -1; + } + + Group_c *g = &g_c->chats[groupnumber]; + + g->status = GROUPCHAT_STATUS_CONNECTED; + g->type = type; + new_symmetric_key(rng, g->id); + g->peer_number = 0; /* Founder is peer 0. */ + memcpy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE); + const int peer_index = addpeer(g_c, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), 0, nullptr, true, + false); + + if (peer_index == -1) { + return -1; + } + + setnick(g_c, groupnumber, peer_index, g_c->m->name, g_c->m->name_length, nullptr, false); + + return groupnumber; +} + +non_null() +static bool group_leave(const Group_Chats *g_c, uint32_t groupnumber, bool permanent); + +/** @brief Delete a groupchat from the chats array, informing the group first as + * appropriate. + * + * @retval true on success. + * @retval false if groupnumber is invalid. + */ +bool del_groupchat(Group_Chats *g_c, uint32_t groupnumber, bool leave_permanently) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return false; + } + + group_leave(g_c, groupnumber, leave_permanently); + + for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { + continue; + } + + g->connections[i].type = GROUPCHAT_CONNECTION_NONE; + kill_friend_connection(g_c->fr_c, g->connections[i].number); + } + + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (g->peer_on_leave != nullptr) { + g->peer_on_leave(g->object, groupnumber, g->group[i].object); + } + } + + if (g->group_on_delete != nullptr) { + g->group_on_delete(g->object, groupnumber); + } + + return wipe_group_chat(g_c, groupnumber); +} + +non_null() +static const Group_Peer *peer_in_list(const Group_c *g, uint32_t peernumber, bool frozen) +{ + const Group_Peer *list = frozen ? g->frozen : g->group; + const uint32_t num = frozen ? g->numfrozen : g->numpeers; + + if (peernumber >= num) { + return nullptr; + } + + return &list[peernumber]; +} + + +/** + * @brief Copy the public key of (frozen, if frozen is true) peernumber who is in + * groupnumber to pk. + * + * @param pk must be CRYPTO_PUBLIC_KEY_SIZE long. + * + * @retval 0 on success + * @retval -1 if groupnumber is invalid. + * @retval -2 if peernumber is invalid. + */ +int group_peer_pubkey(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint8_t *pk, bool frozen) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + const Group_Peer *peer = peer_in_list(g, peernumber, frozen); + + if (peer == nullptr) { + return -2; + } + + memcpy(pk, peer->real_pk, CRYPTO_PUBLIC_KEY_SIZE); + return 0; +} + +/** + * @brief Return the size of (frozen, if frozen is true) peernumber's name. + * + * @retval -1 if groupnumber is invalid. + * @retval -2 if peernumber is invalid. + */ +int group_peername_size(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, bool frozen) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + const Group_Peer *peer = peer_in_list(g, peernumber, frozen); + + if (peer == nullptr) { + return -2; + } + + return peer->nick_len; +} + +/** + * @brief Copy the name of (frozen, if frozen is true) peernumber who is in + * groupnumber to name. + * + * @param name must be at least MAX_NAME_LENGTH long. + * + * @return length of name if success + * @retval -1 if groupnumber is invalid. + * @retval -2 if peernumber is invalid. + */ +int group_peername(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint8_t *name, bool frozen) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + const Group_Peer *peer = peer_in_list(g, peernumber, frozen); + + if (peer == nullptr) { + return -2; + } + + if (peer->nick_len > 0) { + memcpy(name, peer->nick, peer->nick_len); + } + + return peer->nick_len; +} + +/** + * @brief Copy last active timestamp of frozen peernumber who is in groupnumber to + * last_active. + * + * @retval 0 on success. + * @retval -1 if groupnumber is invalid. + * @retval -2 if peernumber is invalid. + */ +int group_frozen_last_active(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, + uint64_t *last_active) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + if (peernumber >= g->numfrozen) { + return -2; + } + + *last_active = g->frozen[peernumber].last_active; + return 0; +} + +/** @brief Set maximum number of frozen peers. + * + * @retval 0 on success. + * @retval -1 if groupnumber is invalid. + */ +int group_set_max_frozen(const Group_Chats *g_c, uint32_t groupnumber, uint32_t maxfrozen) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + g->maxfrozen = maxfrozen; + delete_old_frozen(g); + return 0; +} + +/** + * @return the number of (frozen, if frozen is true) peers in the group chat on success. + * @retval -1 if groupnumber is invalid. + */ +int group_number_peers(const Group_Chats *g_c, uint32_t groupnumber, bool frozen) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + return frozen ? g->numfrozen : g->numpeers; +} + +/** + * @retval 1 if the peernumber corresponds to ours. + * @retval 0 if the peernumber is not ours. + * @retval -1 if groupnumber is invalid. + * @retval -2 if peernumber is invalid. + * @retval -3 if we are not connected to the group chat. + */ +int group_peernumber_is_ours(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + if (peernumber >= g->numpeers) { + return -2; + } + + if (g->status != GROUPCHAT_STATUS_CONNECTED) { + return -3; + } + + return (g->peer_number == g->group[peernumber].peer_number) ? 1 : 0; +} + +/** @brief return the type of groupchat (GROUPCHAT_TYPE_) that groupnumber is. + * + * @retval -1 on failure. + * @return type on success. + */ +int group_get_type(const Group_Chats *g_c, uint32_t groupnumber) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + return g->type; +} + +/** @brief Copies the unique id of `group_chat[groupnumber]` into `id`. + * + * @retval false on failure. + * @retval true on success. + */ +bool conference_get_id(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *id) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return false; + } + + if (id != nullptr) { + memcpy(id, g->id, sizeof(g->id)); + } + + return true; +} + +/** @brief Send a group packet to friendcon_id. + * + * @retval true on success + * @retval false on failure + */ +non_null() +static bool send_packet_group_peer(const Friend_Connections *fr_c, int friendcon_id, uint8_t packet_id, + uint16_t group_num, const uint8_t *data, uint16_t length) +{ + if (1 + sizeof(uint16_t) + length > MAX_CRYPTO_DATA_SIZE) { + return false; + } + + group_num = net_htons(group_num); + VLA(uint8_t, packet, 1 + sizeof(uint16_t) + length); + packet[0] = packet_id; + memcpy(packet + 1, &group_num, sizeof(uint16_t)); + memcpy(packet + 1 + sizeof(uint16_t), data, length); + return write_cryptpacket(friendconn_net_crypto(fr_c), friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, + SIZEOF_VLA(packet), false) != -1; +} + +/** @brief Send a group lossy packet to friendcon_id. + * + * @retval true on success + * @retval false on failure + */ +non_null() +static bool send_lossy_group_peer(const Friend_Connections *fr_c, int friendcon_id, uint8_t packet_id, + uint16_t group_num, const uint8_t *data, uint16_t length) +{ + if (1 + sizeof(uint16_t) + length > MAX_CRYPTO_DATA_SIZE) { + return false; + } + + group_num = net_htons(group_num); + VLA(uint8_t, packet, 1 + sizeof(uint16_t) + length); + packet[0] = packet_id; + memcpy(packet + 1, &group_num, sizeof(uint16_t)); + memcpy(packet + 1 + sizeof(uint16_t), data, length); + return send_lossy_cryptpacket(friendconn_net_crypto(fr_c), friend_connection_crypt_connection_id(fr_c, friendcon_id), + packet, SIZEOF_VLA(packet)) != -1; +} + +/** @brief invite friendnumber to groupnumber. + * + * @retval 0 on success. + * @retval -1 if groupnumber is invalid. + * @retval -2 if invite packet failed to send. + * @retval -3 if we are not connected to the group chat. + */ +int invite_friend(const Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + if (g->status != GROUPCHAT_STATUS_CONNECTED) { + return -3; + } + + uint8_t invite[INVITE_PACKET_SIZE]; + invite[0] = INVITE_ID; + const uint16_t groupchat_num = net_htons((uint16_t)groupnumber); + memcpy(invite + 1, &groupchat_num, sizeof(groupchat_num)); + invite[1 + sizeof(groupchat_num)] = g->type; + memcpy(invite + 1 + sizeof(groupchat_num) + 1, g->id, GROUP_ID_LENGTH); + + if (send_conference_invite_packet(g_c->m, friendnumber, invite, sizeof(invite))) { + return 0; + } + + return -2; +} + +/** @brief Send a rejoin packet to a peer if we have a friend connection to the peer. + * @retval true if a packet was sent. + * @retval false otherwise. + */ +static bool try_send_rejoin(Group_Chats *g_c, Group_c *g, const uint8_t *real_pk) +{ + const int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, real_pk); + + if (friendcon_id == -1) { + return false; + } + + uint8_t packet[1 + 1 + GROUP_ID_LENGTH]; + packet[0] = PACKET_ID_REJOIN_CONFERENCE; + packet[1] = g->type; + memcpy(packet + 2, g->id, GROUP_ID_LENGTH); + + if (write_cryptpacket(friendconn_net_crypto(g_c->fr_c), friend_connection_crypt_connection_id(g_c->fr_c, friendcon_id), + packet, sizeof(packet), false) == -1) { + return false; + } + + add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCER, true); + + return true; +} + +non_null() +static bool send_peer_query(const Group_Chats *g_c, int friendcon_id, uint16_t group_num); + +non_null() +static bool send_invite_response(Group_Chats *g_c, int groupnumber, uint32_t friendnumber, const uint8_t *data, + uint16_t length); + +/** @brief Join a group (we need to have been invited first). + * + * @param expected_type is the groupchat type we expect the chat we are joining + * to have. + * + * @return group number on success. + * @retval -1 if data length is invalid. + * @retval -2 if group is not the expected type. + * @retval -3 if friendnumber is invalid. + * @retval -4 if client is already in this group. + * @retval -5 if group instance failed to initialize. + * @retval -6 if join packet fails to send. + */ +int join_groupchat(Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_type, const uint8_t *data, uint16_t length) +{ + if (length != sizeof(uint16_t) + 1 + GROUP_ID_LENGTH) { + return -1; + } + + if (data[sizeof(uint16_t)] != expected_type) { + return -2; + } + + const int friendcon_id = getfriendcon_id(g_c->m, friendnumber); + + if (friendcon_id == -1) { + return -3; + } + + if (get_group_num(g_c, data[sizeof(uint16_t)], data + sizeof(uint16_t) + 1) != -1) { + return -4; + } + + const int groupnumber = create_group_chat(g_c); + + if (groupnumber == -1) { + return -5; + } + + Group_c *g = &g_c->chats[groupnumber]; + + g->status = GROUPCHAT_STATUS_VALID; + memcpy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE); + + if (!send_invite_response(g_c, groupnumber, friendnumber, data, length)) { + g->status = GROUPCHAT_STATUS_NONE; + return -6; + } + + return groupnumber; +} + +static bool send_invite_response(Group_Chats *g_c, int groupnumber, uint32_t friendnumber, const uint8_t *data, + uint16_t length) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return false; + } + + const bool member = g->status == GROUPCHAT_STATUS_CONNECTED; + + VLA(uint8_t, response, member ? INVITE_MEMBER_PACKET_SIZE : INVITE_ACCEPT_PACKET_SIZE); + response[0] = member ? INVITE_MEMBER_ID : INVITE_ACCEPT_ID; + net_pack_u16(response + 1, groupnumber); + memcpy(response + 1 + sizeof(uint16_t), data, length); + + if (member) { + net_pack_u16(response + 1 + sizeof(uint16_t) + length, g->peer_number); + } + + if (!send_conference_invite_packet(g_c->m, friendnumber, response, SIZEOF_VLA(response))) { + return false; + } + + if (!member) { + g->type = data[sizeof(uint16_t)]; + memcpy(g->id, data + sizeof(uint16_t) + 1, GROUP_ID_LENGTH); + } + + uint16_t other_groupnum; + net_unpack_u16(data, &other_groupnum); + + const int friendcon_id = getfriendcon_id(g_c->m, friendnumber); + + if (friendcon_id == -1) { + return false; + } + + const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCER, true); + + if (member) { + add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCING, false); + } + + if (connection_index != -1) { + g->connections[connection_index].group_number = other_groupnum; + g->connections[connection_index].type = GROUPCHAT_CONNECTION_ONLINE; + } + + send_peer_query(g_c, friendcon_id, other_groupnum); + + return true; +} + +/** Set handlers for custom lossy packets. */ +void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, lossy_packet_cb *function) +{ + g_c->lossy_packethandlers[byte] = function; +} + +/** Set the callback for group invites. */ +void g_callback_group_invite(Group_Chats *g_c, g_conference_invite_cb *function) +{ + g_c->invite_callback = function; +} + +/** Set the callback for group connection. */ +void g_callback_group_connected(Group_Chats *g_c, g_conference_connected_cb *function) +{ + g_c->connected_callback = function; +} + +/** Set the callback for group messages. */ +void g_callback_group_message(Group_Chats *g_c, g_conference_message_cb *function) +{ + g_c->message_callback = function; +} + +/** @brief Set callback function for peer nickname changes. + * + * It gets called every time a peer changes their nickname. + */ +void g_callback_peer_name(Group_Chats *g_c, peer_name_cb *function) +{ + g_c->peer_name_callback = function; +} + +/** @brief Set callback function for peer list changes. + * + * It gets called every time the name list changes(new peer, deleted peer) + */ +void g_callback_peer_list_changed(Group_Chats *g_c, peer_list_changed_cb *function) +{ + g_c->peer_list_changed_callback = function; +} + +/** Set callback function for title changes. */ +void g_callback_group_title(Group_Chats *g_c, title_cb *function) +{ + g_c->title_callback = function; +} + +/** @brief Set a function to be called when a new peer joins a group chat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int callback_groupchat_peer_new(const Group_Chats *g_c, uint32_t groupnumber, peer_on_join_cb *function) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + g->peer_on_join = function; + return 0; +} + +/** @brief Set a function to be called when a peer leaves a group chat. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int callback_groupchat_peer_delete(const Group_Chats *g_c, uint32_t groupnumber, peer_on_leave_cb *function) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + g->peer_on_leave = function; + return 0; +} + +/** @brief Set a function to be called when the group chat is deleted. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +int callback_groupchat_delete(const Group_Chats *g_c, uint32_t groupnumber, group_on_delete_cb *function) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + g->group_on_delete = function; + return 0; +} + +non_null(1) nullable(4) +static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint8_t message_id, const uint8_t *data, + uint16_t len); + +/** @brief send a ping message + * return true on success + */ +non_null() +static bool group_ping_send(const Group_Chats *g_c, uint32_t groupnumber) +{ + return send_message_group(g_c, groupnumber, GROUP_MESSAGE_PING_ID, nullptr, 0) > 0; +} + +/** @brief send a new_peer message + * return true on success + */ +non_null() +static bool group_new_peer_send(const Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_num, const uint8_t *real_pk, + const uint8_t *temp_pk) +{ + uint8_t packet[GROUP_MESSAGE_NEW_PEER_LENGTH]; + + peer_num = net_htons(peer_num); + memcpy(packet, &peer_num, sizeof(uint16_t)); + memcpy(packet + sizeof(uint16_t), real_pk, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE, temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + + return send_message_group(g_c, groupnumber, GROUP_MESSAGE_NEW_PEER_ID, packet, sizeof(packet)) > 0; +} + +/** @brief send a kill_peer message + * return true on success + */ +non_null() +static bool group_kill_peer_send(const Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_num) +{ + uint8_t packet[GROUP_MESSAGE_KILL_PEER_LENGTH]; + + peer_num = net_htons(peer_num); + memcpy(packet, &peer_num, sizeof(uint16_t)); + + return send_message_group(g_c, groupnumber, GROUP_MESSAGE_KILL_PEER_ID, packet, sizeof(packet)) > 0; +} + +/** @brief send a freeze_peer message + * return true on success + */ +non_null() +static bool group_freeze_peer_send(const Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_num) +{ + uint8_t packet[GROUP_MESSAGE_KILL_PEER_LENGTH]; + + peer_num = net_htons(peer_num); + memcpy(packet, &peer_num, sizeof(uint16_t)); + + return send_message_group(g_c, groupnumber, GROUP_MESSAGE_FREEZE_PEER_ID, packet, sizeof(packet)) > 0; +} + +/** @brief send a name message + * return true on success + */ +non_null() +static bool group_name_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *nick, uint16_t nick_len) +{ + if (nick_len > MAX_NAME_LENGTH) { + return false; + } + + return send_message_group(g_c, groupnumber, GROUP_MESSAGE_NAME_ID, nick, nick_len) > 0; +} + +/** @brief send message to announce leaving group + * return true on success + */ +static bool group_leave(const Group_Chats *g_c, uint32_t groupnumber, bool permanent) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return false; + } + + if (permanent) { + return group_kill_peer_send(g_c, groupnumber, g->peer_number); + } else { + return group_freeze_peer_send(g_c, groupnumber, g->peer_number); + } +} + + +/** @brief set the group's title, limited to MAX_NAME_LENGTH. + * @retval 0 on success + * @retval -1 if groupnumber is invalid. + * @retval -2 if title is too long or empty. + * @retval -3 if packet fails to send. + */ +int group_title_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *title, uint8_t title_len) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + if (title_len > MAX_NAME_LENGTH || title_len == 0) { + return -2; + } + + /* same as already set? */ + if (g_title_eq(g, title, title_len)) { + return 0; + } + + memcpy(g->title, title, title_len); + g->title_len = title_len; + + if (g->numpeers == 1) { + return 0; + } + + if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_TITLE_ID, title, title_len) > 0) { + return 0; + } + + return -3; +} + +/** @brief return the group's title size. + * @retval -1 of groupnumber is invalid. + * @retval -2 if title is too long or empty. + */ +int group_title_get_size(const Group_Chats *g_c, uint32_t groupnumber) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + if (g->title_len > MAX_NAME_LENGTH || g->title_len == 0) { + return -2; + } + + return g->title_len; +} + +/** @brief Get group title from groupnumber and put it in title. + * + * Title needs to be a valid memory location with a size of at least MAX_NAME_LENGTH (128) bytes. + * + * @return length of copied title if success. + * @retval -1 if groupnumber is invalid. + * @retval -2 if title is too long or empty. + */ +int group_title_get(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *title) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + if (g->title_len > MAX_NAME_LENGTH || g->title_len == 0) { + return -2; + } + + memcpy(title, g->title, g->title_len); + return g->title_len; +} + +non_null() +static bool get_peer_number(const Group_c *g, const uint8_t *real_pk, uint16_t *peer_number) +{ + const int peer_index = peer_in_group(g, real_pk); + + if (peer_index >= 0) { + *peer_number = g->group[peer_index].peer_number; + return true; + } + + const int frozen_index = frozen_in_group(g, real_pk); + + if (frozen_index >= 0) { + *peer_number = g->frozen[frozen_index].peer_number; + return true; + } + + return false; +} + +non_null(1, 3) nullable(5) +static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, + void *userdata) +{ + Group_Chats *g_c = m->conferences_object; + + if (length <= 1) { + return; + } + + switch (data[0]) { + case INVITE_ID: { + if (length != INVITE_PACKET_SIZE) { + return; + } + + const int groupnumber = get_group_num(g_c, data[1 + sizeof(uint16_t)], data + 1 + sizeof(uint16_t) + 1); + + const uint8_t *invite_data = data + 1; + const uint16_t invite_length = length - 1; + + if (groupnumber == -1) { + if (g_c->invite_callback != nullptr) { + g_c->invite_callback(m, friendnumber, invite_data[sizeof(uint16_t)], invite_data, invite_length, userdata); + } + + return; + } else { + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g != nullptr && g->status == GROUPCHAT_STATUS_CONNECTED) { + send_invite_response(g_c, groupnumber, friendnumber, invite_data, invite_length); + } + } + + break; + } + + case INVITE_ACCEPT_ID: + case INVITE_MEMBER_ID: { + const bool member = data[0] == INVITE_MEMBER_ID; + + if (length != (member ? INVITE_MEMBER_PACKET_SIZE : INVITE_ACCEPT_PACKET_SIZE)) { + return; + } + + uint16_t other_groupnum; + uint16_t groupnum; + net_unpack_u16(data + 1, &other_groupnum); + net_unpack_u16(data + 1 + sizeof(uint16_t), &groupnum); + + Group_c *g = get_group_c(g_c, groupnum); + + if (g == nullptr) { + return; + } + + if (data[1 + sizeof(uint16_t) * 2] != g->type) { + return; + } + + if (!group_id_eq(data + 1 + sizeof(uint16_t) * 2 + 1, g->id)) { + return; + } + + uint16_t peer_number; + + if (member) { + net_unpack_u16(data + 1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH, &peer_number); + } else { + /* TODO(irungentoo): what if two people enter the group at the + * same time and are given the same peer_number by different + * nodes? */ + peer_number = random_u16(m->rng); + + unsigned int tries = 0; + + while (get_peer_index(g, peer_number) != -1 || get_frozen_index(g, peer_number) != -1) { + peer_number = random_u16(m->rng); + ++tries; + + if (tries > 32) { + return; + } + } + } + + const int friendcon_id = getfriendcon_id(m, friendnumber); + + if (friendcon_id == -1) { + // TODO(iphydf): Log something? + return; + } + + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id); + + addpeer(g_c, groupnum, real_pk, temp_pk, peer_number, userdata, true, true); + const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g, + GROUPCHAT_CONNECTION_REASON_INTRODUCING, true); + + if (member) { + add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCER, false); + send_peer_query(g_c, friendcon_id, other_groupnum); + } + + if (connection_index != -1) { + g->connections[connection_index].group_number = other_groupnum; + g->connections[connection_index].type = GROUPCHAT_CONNECTION_ONLINE; + } + + group_new_peer_send(g_c, groupnum, peer_number, real_pk, temp_pk); + + break; + } + + default: { + return; + } + } +} + +/** @brief Find index of friend in the connections list. + * + * return index on success + * return -1 on failure. + */ +non_null() +static int friend_in_connections(const Group_c *g, int friendcon_id) +{ + for (unsigned int i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { + continue; + } + + if (g->connections[i].number == (uint32_t)friendcon_id) { + return i; + } + } + + return -1; +} + +/** return number of connections. */ +non_null() +static unsigned int count_connected(const Group_c *g) +{ + unsigned int count = 0; + + for (unsigned int i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type == GROUPCHAT_CONNECTION_ONLINE) { + ++count; + } + } + + return count; +} + +static bool send_packet_online(const Friend_Connections *fr_c, int friendcon_id, uint16_t group_num, + uint8_t type, const uint8_t *id) +{ + uint8_t packet[1 + ONLINE_PACKET_DATA_SIZE]; + group_num = net_htons(group_num); + packet[0] = PACKET_ID_ONLINE_PACKET; + memcpy(packet + 1, &group_num, sizeof(uint16_t)); + packet[1 + sizeof(uint16_t)] = type; + memcpy(packet + 1 + sizeof(uint16_t) + 1, id, GROUP_ID_LENGTH); + return write_cryptpacket(friendconn_net_crypto(fr_c), friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, + sizeof(packet), false) != -1; +} + +non_null() +static bool ping_groupchat(const Group_Chats *g_c, uint32_t groupnumber); + +non_null() +static int handle_packet_online(const Group_Chats *g_c, int friendcon_id, const uint8_t *data, uint16_t length) +{ + if (length != ONLINE_PACKET_DATA_SIZE) { + return -1; + } + + const int groupnumber = get_group_num(g_c, data[sizeof(uint16_t)], data + sizeof(uint16_t) + 1); + + if (groupnumber == -1) { + return -1; + } + + uint16_t other_groupnum; + memcpy(&other_groupnum, data, sizeof(uint16_t)); + other_groupnum = net_ntohs(other_groupnum); + + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + const int index = friend_in_connections(g, friendcon_id); + + if (index == -1) { + return -1; + } + + if (g->connections[index].type == GROUPCHAT_CONNECTION_ONLINE) { + return -1; + } + + if (count_connected(g) == 0 || (g->connections[index].reasons & GROUPCHAT_CONNECTION_REASON_INTRODUCER) != 0) { + send_peer_query(g_c, friendcon_id, other_groupnum); + } + + g->connections[index].group_number = other_groupnum; + g->connections[index].type = GROUPCHAT_CONNECTION_ONLINE; + send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->type, g->id); + + if ((g->connections[index].reasons & GROUPCHAT_CONNECTION_REASON_INTRODUCING) != 0) { + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id); + + const int peer_index = peer_in_group(g, real_pk); + + if (peer_index != -1) { + group_new_peer_send(g_c, groupnumber, g->group[peer_index].peer_number, real_pk, temp_pk); + } + + g->need_send_name = true; + } + + ping_groupchat(g_c, groupnumber); + + return 0; +} + +non_null(1, 3) nullable(5) +static int handle_packet_rejoin(Group_Chats *g_c, int friendcon_id, const uint8_t *data, uint16_t length, + void *userdata) +{ + if (length < 1 + GROUP_ID_LENGTH) { + return -1; + } + + const int32_t groupnum = get_group_num(g_c, *data, data + 1); + + Group_c *g = get_group_c(g_c, groupnum); + + if (g == nullptr) { + return -1; + } + + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id); + + uint16_t peer_number; + + if (!get_peer_number(g, real_pk, &peer_number)) { + return -1; + } + + addpeer(g_c, groupnum, real_pk, temp_pk, peer_number, userdata, true, true); + const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g, + GROUPCHAT_CONNECTION_REASON_INTRODUCING, true); + + if (connection_index != -1) { + send_packet_online(g_c->fr_c, friendcon_id, groupnum, g->type, g->id); + } + + return 0; +} + + +// we could send title with invite, but then if it changes between sending and accepting inv, joinee won't see it + +/** + * @retval true on success. + * @retval false on failure + */ +static bool send_peer_introduced(const Group_Chats *g_c, int friendcon_id, uint16_t group_num) +{ + uint8_t packet[1]; + packet[0] = PEER_INTRODUCED_ID; + return send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, sizeof(packet)); +} + + +/** + * @retval true on success. + * @retval false on failure + */ +static bool send_peer_query(const Group_Chats *g_c, int friendcon_id, uint16_t group_num) +{ + uint8_t packet[1]; + packet[0] = PEER_QUERY_ID; + return send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, sizeof(packet)); +} + +/** + * @return number of peers sent on success. + * @retval 0 on failure. + */ +non_null() +static unsigned int send_peers(const Group_Chats *g_c, const Group_c *g, int friendcon_id, uint16_t group_num) +{ + uint8_t response_packet[MAX_CRYPTO_DATA_SIZE - (1 + sizeof(uint16_t))]; + response_packet[0] = PEER_RESPONSE_ID; + uint8_t *p = response_packet + 1; + + uint16_t sent = 0; + + for (uint32_t i = 0; i <= g->numpeers; ++i) { + if (i == g->numpeers + || (p - response_packet) + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2 + 1 + g->group[i].nick_len > + sizeof(response_packet)) { + if (send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, response_packet, + p - response_packet)) { + sent = i; + } else { + return sent; + } + + if (i == g->numpeers) { + break; + } + + p = response_packet + 1; + } + + const uint16_t peer_num = net_htons(g->group[i].peer_number); + memcpy(p, &peer_num, sizeof(peer_num)); + p += sizeof(peer_num); + memcpy(p, g->group[i].real_pk, CRYPTO_PUBLIC_KEY_SIZE); + p += CRYPTO_PUBLIC_KEY_SIZE; + memcpy(p, g->group[i].temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + p += CRYPTO_PUBLIC_KEY_SIZE; + *p = g->group[i].nick_len; + p += 1; + memcpy(p, g->group[i].nick, g->group[i].nick_len); + p += g->group[i].nick_len; + } + + if (g->title_len > 0) { + VLA(uint8_t, title_packet, 1 + g->title_len); + title_packet[0] = PEER_TITLE_ID; + memcpy(title_packet + 1, g->title, g->title_len); + send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, title_packet, + SIZEOF_VLA(title_packet)); + } + + return sent; +} + +non_null(1, 3) nullable(5) +static int handle_send_peers(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length, + void *userdata) +{ + if (length == 0) { + return -1; + } + + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + const uint8_t *d = data; + + while ((unsigned int)(length - (d - data)) >= sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2 + 1) { + uint16_t peer_num; + memcpy(&peer_num, d, sizeof(peer_num)); + peer_num = net_ntohs(peer_num); + d += sizeof(uint16_t); + + if (g->status == GROUPCHAT_STATUS_VALID + && pk_equal(d, nc_get_self_public_key(g_c->m->net_crypto))) { + g->peer_number = peer_num; + g->status = GROUPCHAT_STATUS_CONNECTED; + + if (g_c->connected_callback != nullptr) { + g_c->connected_callback(g_c->m, groupnumber, userdata); + } + + g->need_send_name = true; + } + + const int peer_index = addpeer(g_c, groupnumber, d, d + CRYPTO_PUBLIC_KEY_SIZE, peer_num, userdata, false, true); + + if (peer_index == -1) { + return -1; + } + + d += CRYPTO_PUBLIC_KEY_SIZE * 2; + const uint8_t name_length = *d; + d += 1; + + if (name_length > (length - (d - data)) || name_length > MAX_NAME_LENGTH) { + return -1; + } + + if (!g->group[peer_index].nick_updated) { + setnick(g_c, groupnumber, peer_index, d, name_length, userdata, true); + } + + d += name_length; + } + + return 0; +} + +non_null(1, 3) nullable(6) +static void handle_direct_packet(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length, + int connection_index, void *userdata) +{ + if (length == 0) { + return; + } + + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return; + } + + switch (data[0]) { + case PEER_INTRODUCED_ID: { + remove_connection_reason(g_c, g, connection_index, GROUPCHAT_CONNECTION_REASON_INTRODUCING); + break; + } + + case PEER_QUERY_ID: { + if (g->connections[connection_index].type != GROUPCHAT_CONNECTION_ONLINE) { + return; + } + + send_peers(g_c, g, g->connections[connection_index].number, g->connections[connection_index].group_number); + break; + } + + + case PEER_RESPONSE_ID: { + handle_send_peers(g_c, groupnumber, data + 1, length - 1, userdata); + break; + } + + + case PEER_TITLE_ID: { + if (!g->title_fresh) { + settitle(g_c, groupnumber, -1, data + 1, length - 1, userdata); + } + + break; + } + } +} + +/** @brief Send message to all connections except receiver (if receiver isn't -1) + * + * NOTE: this function appends the group chat number to the data passed to it. + * + * @return number of messages sent. + */ +non_null() +static unsigned int send_message_all_connections(const Group_Chats *g_c, const Group_c *g, const uint8_t *data, + uint16_t length, int receiver) +{ + uint16_t sent = 0; + + for (uint16_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type != GROUPCHAT_CONNECTION_ONLINE) { + continue; + } + + if ((int)i == receiver) { + continue; + } + + if (send_packet_group_peer(g_c->fr_c, g->connections[i].number, PACKET_ID_MESSAGE_CONFERENCE, + g->connections[i].group_number, data, length)) { + ++sent; + } + } + + return sent; +} + +/** @brief Send lossy message to all connections except receiver (if receiver isn't -1) + * + * NOTE: this function appends the group chat number to the data passed to it. + * + * @return number of messages sent. + */ +non_null() +static unsigned int send_lossy_all_connections(const Group_Chats *g_c, const Group_c *g, const uint8_t *data, + uint16_t length, int receiver) +{ + unsigned int sent = 0; + unsigned int num_connected_closest = 0; + unsigned int connected_closest[DESIRED_CLOSEST] = {0}; + + for (unsigned int i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type != GROUPCHAT_CONNECTION_ONLINE) { + continue; + } + + if ((int)i == receiver) { + continue; + } + + if ((g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_CLOSEST) != 0) { + connected_closest[num_connected_closest] = i; + ++num_connected_closest; + continue; + } + + if (send_lossy_group_peer(g_c->fr_c, g->connections[i].number, PACKET_ID_LOSSY_CONFERENCE, + g->connections[i].group_number, data, length)) { + ++sent; + } + } + + if (num_connected_closest == 0) { + return sent; + } + + unsigned int to_send[2] = {0, 0}; + uint64_t comp_val_old[2] = {(uint64_t) -1, (uint64_t) -1}; + + for (unsigned int i = 0; i < num_connected_closest; ++i) { + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE] = {0}; + get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->connections[connected_closest[i]].number); + const uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk); + + for (uint8_t j = 0; j < 2; ++j) { + if (j > 0 ? (comp_val > comp_val_old[j]) : (comp_val < comp_val_old[j])) { + to_send[j] = connected_closest[i]; + comp_val_old[j] = comp_val; + } + } + } + + for (uint8_t j = 0; j < 2; ++j) { + if (j > 0 && to_send[1] == to_send[0]) { + break; + } + + if (send_lossy_group_peer(g_c->fr_c, g->connections[to_send[j]].number, PACKET_ID_LOSSY_CONFERENCE, + g->connections[to_send[j]].group_number, data, length)) { + ++sent; + } + } + + return sent; +} + +/** @brief Send data of len with message_id to groupnumber. + * + * @return number of peers it was sent to on success. + * @retval -1 if groupnumber is invalid. + * @retval -2 if message is too long. + * @retval -3 if we are not connected to the group. + * @retval -4 if message failed to send. + */ +static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint8_t message_id, const uint8_t *data, + uint16_t len) +{ + assert(len == 0 || data != nullptr); + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + if (len > MAX_GROUP_MESSAGE_DATA_LEN) { + return -2; + } + + if (g->status != GROUPCHAT_STATUS_CONNECTED || count_connected(g) == 0) { + return -3; + } + + VLA(uint8_t, packet, sizeof(uint16_t) + sizeof(uint32_t) + 1 + len); + const uint16_t peer_num = net_htons(g->peer_number); + memcpy(packet, &peer_num, sizeof(peer_num)); + + ++g->message_number; + + if (g->message_number == 0) { + ++g->message_number; + } + + const uint32_t message_num = net_htonl(g->message_number); + memcpy(packet + sizeof(uint16_t), &message_num, sizeof(message_num)); + + packet[sizeof(uint16_t) + sizeof(uint32_t)] = message_id; + + if (len != 0) { + memcpy(packet + sizeof(uint16_t) + sizeof(uint32_t) + 1, data, len); + } + + const unsigned int ret = send_message_all_connections(g_c, g, packet, SIZEOF_VLA(packet), -1); + + if (ret == 0) { + return -4; + } + + return ret; +} + +/** @brief send a group message + * @retval 0 on success + * @see send_message_group for error codes. + */ +int group_message_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *message, uint16_t length) +{ + const int ret = send_message_group(g_c, groupnumber, PACKET_ID_MESSAGE, message, length); + + if (ret > 0) { + return 0; + } + + return ret; +} + +/** @brief send a group action + * @retval 0 on success + * @see send_message_group for error codes. + */ +int group_action_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *action, uint16_t length) +{ + const int ret = send_message_group(g_c, groupnumber, PACKET_ID_ACTION, action, length); + + if (ret > 0) { + return 0; + } + + return ret; +} + +/** @brief High level function to send custom lossy packets. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +int send_group_lossy_packet(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length) +{ + // TODO(irungentoo): length check here? + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + VLA(uint8_t, packet, sizeof(uint16_t) * 2 + length); + const uint16_t peer_number = net_htons(g->peer_number); + memcpy(packet, &peer_number, sizeof(uint16_t)); + const uint16_t message_num = net_htons(g->lossy_message_number); + memcpy(packet + sizeof(uint16_t), &message_num, sizeof(uint16_t)); + memcpy(packet + sizeof(uint16_t) * 2, data, length); + + if (send_lossy_all_connections(g_c, g, packet, SIZEOF_VLA(packet), -1) == 0) { + return -1; + } + + ++g->lossy_message_number; + return 0; +} + +non_null() +static Message_Info *find_message_slot_or_reject(uint32_t message_number, uint8_t message_id, Group_Peer *peer) +{ + const bool ignore_older = message_id == GROUP_MESSAGE_NAME_ID || message_id == GROUP_MESSAGE_TITLE_ID; + + Message_Info *i; + + for (i = peer->last_message_infos; i < peer->last_message_infos + peer->num_last_message_infos; ++i) { + if (message_number - (i->message_number + 1) <= ((uint32_t)1 << 31)) { + break; + } + + if (message_number == i->message_number) { + return nullptr; + } + + if (ignore_older && message_id == i->message_id) { + return nullptr; + } + } + + return i; +} + +/** @brief Stores message info in `peer->last_message_infos`. + * + * @retval true if message should be processed. + * @retval false otherwise. + */ +non_null() +static bool check_message_info(uint32_t message_number, uint8_t message_id, Group_Peer *peer) +{ + Message_Info *const i = find_message_slot_or_reject(message_number, message_id, peer); + + if (i == nullptr) { + return false; + } + + if (i == peer->last_message_infos + MAX_LAST_MESSAGE_INFOS) { + return false; + } + + if (peer->num_last_message_infos < MAX_LAST_MESSAGE_INFOS) { + ++peer->num_last_message_infos; + } + + memmove(i + 1, i, (&peer->last_message_infos[peer->num_last_message_infos - 1] - i) * sizeof(Message_Info)); + + i->message_number = message_number; + i->message_id = message_id; + + return true; +} + +non_null(1, 3) nullable(6) +static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length, + int connection_index, void *userdata) +{ + if (length < sizeof(uint16_t) + sizeof(uint32_t) + 1) { + return; + } + + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return; + } + + uint16_t peer_number; + memcpy(&peer_number, data, sizeof(uint16_t)); + peer_number = net_ntohs(peer_number); + + uint32_t message_number; + memcpy(&message_number, data + sizeof(uint16_t), sizeof(message_number)); + message_number = net_ntohl(message_number); + + const uint8_t message_id = data[sizeof(uint16_t) + sizeof(message_number)]; + const uint8_t *msg_data = data + sizeof(uint16_t) + sizeof(message_number) + 1; + const uint16_t msg_data_len = length - (sizeof(uint16_t) + sizeof(message_number) + 1); + + const bool ignore_frozen = message_id == GROUP_MESSAGE_FREEZE_PEER_ID; + + const int index = ignore_frozen ? get_peer_index(g, peer_number) + : note_peer_active(g_c, groupnumber, peer_number, userdata); + + if (index == -1) { + if (ignore_frozen) { + return; + } + + if (g->connections[connection_index].type != GROUPCHAT_CONNECTION_ONLINE) { + return; + } + + /* If we don't know the peer this packet came from, then we query the + * list of peers from the relaying peer. + * (They wouldn't have relayed it if they didn't know the peer.) */ + send_peer_query(g_c, g->connections[connection_index].number, g->connections[connection_index].group_number); + return; + } + + if (g->num_introducer_connections > 0 && count_connected(g) > DESIRED_CLOSEST) { + for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE + || (g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_INTRODUCER) == 0 + || i == connection_index) { + continue; + } + + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->connections[i].number); + + if (pk_equal(g->group[index].real_pk, real_pk)) { + /* Received message from peer relayed via another peer, so + * the introduction was successful */ + remove_connection_reason(g_c, g, i, GROUPCHAT_CONNECTION_REASON_INTRODUCER); + } + } + } + + if (!check_message_info(message_number, message_id, &g->group[index])) { + return; + } + + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->connections[connection_index].number); + const bool direct_from_sender = pk_equal(g->group[index].real_pk, real_pk); + + switch (message_id) { + case GROUP_MESSAGE_PING_ID: { + break; + } + + case GROUP_MESSAGE_NEW_PEER_ID: { + if (msg_data_len != GROUP_MESSAGE_NEW_PEER_LENGTH) { + return; + } + + uint16_t new_peer_number; + memcpy(&new_peer_number, msg_data, sizeof(uint16_t)); + new_peer_number = net_ntohs(new_peer_number); + addpeer(g_c, groupnumber, msg_data + sizeof(uint16_t), msg_data + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE, + new_peer_number, userdata, true, true); + break; + } + + case GROUP_MESSAGE_KILL_PEER_ID: + case GROUP_MESSAGE_FREEZE_PEER_ID: { + if (msg_data_len != GROUP_MESSAGE_KILL_PEER_LENGTH) { + return; + } + + uint16_t kill_peer_number; + memcpy(&kill_peer_number, msg_data, sizeof(uint16_t)); + kill_peer_number = net_ntohs(kill_peer_number); + + if (peer_number == kill_peer_number) { + if (message_id == GROUP_MESSAGE_KILL_PEER_ID) { + delpeer(g_c, groupnumber, index, userdata); + } else { + freeze_peer(g_c, groupnumber, index, userdata); + } + } else { + return; + // TODO(irungentoo): + } + + break; + } + + case GROUP_MESSAGE_NAME_ID: { + if (!setnick(g_c, groupnumber, index, msg_data, msg_data_len, userdata, true)) { + return; + } + + break; + } + + case GROUP_MESSAGE_TITLE_ID: { + if (!settitle(g_c, groupnumber, index, msg_data, msg_data_len, userdata)) { + return; + } + + break; + } + + case PACKET_ID_MESSAGE: { + if (msg_data_len == 0) { + return; + } + + VLA(uint8_t, newmsg, msg_data_len + 1); + memcpy(newmsg, msg_data, msg_data_len); + newmsg[msg_data_len] = 0; + + // TODO(irungentoo): + if (g_c->message_callback != nullptr) { + g_c->message_callback(g_c->m, groupnumber, index, 0, newmsg, msg_data_len, userdata); + } + + break; + } + + case PACKET_ID_ACTION: { + if (msg_data_len == 0) { + return; + } + + VLA(uint8_t, newmsg, msg_data_len + 1); + memcpy(newmsg, msg_data, msg_data_len); + newmsg[msg_data_len] = 0; + + // TODO(irungentoo): + if (g_c->message_callback != nullptr) { + g_c->message_callback(g_c->m, groupnumber, index, 1, newmsg, msg_data_len, userdata); + } + + break; + } + + default: { + return; + } + } + + /* If the packet was received from the peer who sent the message, relay it + * back. When the sender only has one group connection (e.g. because there + * are only two peers in the group), this is the only way for them to + * receive their own message. */ + send_message_all_connections(g_c, g, data, length, direct_from_sender ? -1 : connection_index); +} + +static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata) +{ + Group_Chats *g_c = (Group_Chats *)object; + + if (length < 1 + sizeof(uint16_t) + 1) { + return -1; + } + + if (data[0] == PACKET_ID_ONLINE_PACKET) { + return handle_packet_online(g_c, friendcon_id, data + 1, length - 1); + } + + if (data[0] == PACKET_ID_REJOIN_CONFERENCE) { + return handle_packet_rejoin(g_c, friendcon_id, data + 1, length - 1, userdata); + } + + uint16_t groupnumber; + memcpy(&groupnumber, data + 1, sizeof(uint16_t)); + groupnumber = net_ntohs(groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + const int index = friend_in_connections(g, friendcon_id); + + if (index == -1) { + return -1; + } + + if (data[0] == PACKET_ID_DIRECT_CONFERENCE) { + handle_direct_packet(g_c, groupnumber, data + 1 + sizeof(uint16_t), + length - (1 + sizeof(uint16_t)), index, userdata); + return 0; + } + + if (data[0] == PACKET_ID_MESSAGE_CONFERENCE) { + handle_message_packet_group(g_c, groupnumber, data + 1 + sizeof(uint16_t), + length - (1 + sizeof(uint16_t)), index, userdata); + return 0; + } + + return -1; +} + +/** @brief Did we already receive the lossy packet or not. + * + * @retval -1 on failure. + * @retval 0 if packet was not received. + * @retval 1 if packet was received. + * + * TODO(irungentoo): test this + */ +non_null() +static int lossy_packet_not_received(const Group_c *g, int peer_index, uint16_t message_number) +{ + if (peer_index == -1) { + return -1; + } + + if (g->group[peer_index].bottom_lossy_number == g->group[peer_index].top_lossy_number) { + g->group[peer_index].top_lossy_number = message_number; + g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; + g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; + return 0; + } + + if ((uint16_t)(message_number - g->group[peer_index].bottom_lossy_number) < MAX_LOSSY_COUNT) { + if (g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] != 0) { + return 1; + } + + g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; + return 0; + } + + if ((uint16_t)(message_number - g->group[peer_index].bottom_lossy_number) > (1 << 15)) { + return -1; + } + + const uint16_t top_distance = message_number - g->group[peer_index].top_lossy_number; + + if (top_distance >= MAX_LOSSY_COUNT) { + crypto_memzero(g->group[peer_index].recv_lossy, sizeof(g->group[peer_index].recv_lossy)); + } else { // top_distance < MAX_LOSSY_COUNT + for (unsigned int i = g->group[peer_index].bottom_lossy_number; + i != g->group[peer_index].bottom_lossy_number + top_distance; + ++i) { + g->group[peer_index].recv_lossy[i % MAX_LOSSY_COUNT] = 0; + } + } + + g->group[peer_index].top_lossy_number = message_number; + g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; + g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; + + return 0; + +} + +/** Does this group type make use of lossy packets? */ +static bool type_uses_lossy(uint8_t type) +{ + return type == GROUPCHAT_TYPE_AV; +} + +static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata) +{ + const Group_Chats *g_c = (const Group_Chats *)object; + + if (data[0] != PACKET_ID_LOSSY_CONFERENCE) { + return -1; + } + + if (length < 1 + sizeof(uint16_t) * 3 + 1) { + return -1; + } + + uint16_t groupnumber; + uint16_t peer_number; + uint16_t message_number; + memcpy(&groupnumber, data + 1, sizeof(uint16_t)); + memcpy(&peer_number, data + 1 + sizeof(uint16_t), sizeof(uint16_t)); + memcpy(&message_number, data + 1 + sizeof(uint16_t) * 2, sizeof(uint16_t)); + groupnumber = net_ntohs(groupnumber); + peer_number = net_ntohs(peer_number); + message_number = net_ntohs(message_number); + + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + if (!type_uses_lossy(g->type)) { + return -1; + } + + const int index = friend_in_connections(g, friendcon_id); + + if (index == -1) { + return -1; + } + + if (peer_number == g->peer_number) { + return -1; + } + + const int peer_index = get_peer_index(g, peer_number); + + if (peer_index == -1) { + return -1; + } + + if (lossy_packet_not_received(g, peer_index, message_number) != 0) { + return -1; + } + + const uint8_t *lossy_data = data + 1 + sizeof(uint16_t) * 3; + uint16_t lossy_length = length - (1 + sizeof(uint16_t) * 3); + const uint8_t message_id = lossy_data[0]; + ++lossy_data; + --lossy_length; + + send_lossy_all_connections(g_c, g, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index); + + if (g_c->lossy_packethandlers[message_id] == nullptr) { + return -1; + } + + if (g_c->lossy_packethandlers[message_id](g->object, groupnumber, peer_index, g->group[peer_index].object, + lossy_data, lossy_length) == -1) { + return -1; + } + + return 0; +} + +/** @brief Set the object that is tied to the group chat. + * + * @retval 0 on success. + * @retval -1 on failure + */ +int group_set_object(const Group_Chats *g_c, uint32_t groupnumber, void *object) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + g->object = object; + return 0; +} + +/** @brief Set the object that is tied to the group peer. + * + * @retval 0 on success. + * @retval -1 on failure + */ +int group_peer_set_object(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, void *object) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return -1; + } + + if (peernumber >= g->numpeers) { + return -1; + } + + g->group[peernumber].object = object; + return 0; +} + +/** @brief Return the object tied to the group chat previously set by group_set_object. + * + * @retval NULL on failure. + * @return object on success. + */ +void *group_get_object(const Group_Chats *g_c, uint32_t groupnumber) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return nullptr; + } + + return g->object; +} + +/** @brief Return the object tied to the group chat peer previously set by group_peer_set_object. + * + * @retval NULL on failure. + * @return object on success. + */ +void *group_peer_get_object(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return nullptr; + } + + if (peernumber >= g->numpeers) { + return nullptr; + } + + return g->group[peernumber].object; +} + +/** Interval in seconds to send ping messages */ +#define GROUP_PING_INTERVAL 20 + +static bool ping_groupchat(const Group_Chats *g_c, uint32_t groupnumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return false; + } + + if (mono_time_is_timeout(g_c->mono_time, g->last_sent_ping, GROUP_PING_INTERVAL)) { + if (group_ping_send(g_c, groupnumber)) { + g->last_sent_ping = mono_time_get(g_c->mono_time); + } + } + + return true; +} + +/** Seconds of inactivity after which to freeze a peer */ +#define FREEZE_TIMEOUT (GROUP_PING_INTERVAL * 3) + +non_null(1) nullable(3) +static bool groupchat_freeze_timedout(Group_Chats *g_c, uint32_t groupnumber, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g == nullptr) { + return false; + } + + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (g->group[i].peer_number == g->peer_number) { + continue; + } + + if (mono_time_is_timeout(g_c->mono_time, g->group[i].last_active, FREEZE_TIMEOUT)) { + freeze_peer(g_c, groupnumber, i, userdata); + } + } + + if (g->numpeers <= 1) { + g->title_fresh = false; + } + + return true; +} + +/** Push non-empty slots to start. */ +non_null() +static void squash_connections(Group_c *g) +{ + uint16_t num_connected = 0; + + for (uint16_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type != GROUPCHAT_CONNECTION_NONE) { + g->connections[num_connected] = g->connections[i]; + ++num_connected; + } + } + + for (uint16_t i = num_connected; i < MAX_GROUP_CONNECTIONS; ++i) { + g->connections[i].type = GROUPCHAT_CONNECTION_NONE; + } +} + +#define MIN_EMPTY_CONNECTIONS (1 + MAX_GROUP_CONNECTIONS / 10) + +non_null() +static uint16_t empty_connection_count(const Group_c *g) +{ + uint16_t to_clear = MIN_EMPTY_CONNECTIONS; + + for (uint16_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { + --to_clear; + + if (to_clear == 0) { + break; + } + } + } + + return to_clear; +} + +/** + * @brief Remove old connections as necessary to ensure we have space for new + * connections. + * + * This invalidates connections array indices (which is + * why we do this periodically rather than on adding a connection). + */ +non_null() +static void clean_connections(Group_Chats *g_c, Group_c *g) +{ + for (uint16_t to_clear = empty_connection_count(g); to_clear > 0; --to_clear) { + // Remove a connection. Prefer non-closest connections, and given + // that prefer non-online connections, and given that prefer earlier + // slots. + uint16_t i = 0; + + while (i < MAX_GROUP_CONNECTIONS + && (g->connections[i].type != GROUPCHAT_CONNECTION_CONNECTING + || (g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_CLOSEST) != 0)) { + ++i; + } + + if (i == MAX_GROUP_CONNECTIONS) { + i = 0; + + while (i < MAX_GROUP_CONNECTIONS - to_clear + && (g->connections[i].type != GROUPCHAT_CONNECTION_ONLINE + || (g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_CLOSEST) != 0)) { + ++i; + } + } + + if (g->connections[i].type != GROUPCHAT_CONNECTION_NONE) { + remove_connection(g_c, g, i); + } + } + + squash_connections(g); +} + +/** Send current name (set in messenger) to all online groups. */ +void send_name_all_groups(const Group_Chats *g_c) +{ + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + Group_c *g = get_group_c(g_c, i); + + if (g == nullptr) { + continue; + } + + if (g->status == GROUPCHAT_STATUS_CONNECTED) { + group_name_send(g_c, i, g_c->m->name, g_c->m->name_length); + g->need_send_name = false; + } + } +} + +#define SAVED_PEER_SIZE_CONSTANT (2 * CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint16_t) + sizeof(uint64_t) + 1) + +non_null() +static uint32_t saved_peer_size(const Group_Peer *peer) +{ + return SAVED_PEER_SIZE_CONSTANT + peer->nick_len; +} + +non_null() +static uint8_t *save_peer(const Group_Peer *peer, uint8_t *data) +{ + memcpy(data, peer->real_pk, CRYPTO_PUBLIC_KEY_SIZE); + data += CRYPTO_PUBLIC_KEY_SIZE; + + memcpy(data, peer->temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + data += CRYPTO_PUBLIC_KEY_SIZE; + + host_to_lendian_bytes16(data, peer->peer_number); + data += sizeof(uint16_t); + + host_to_lendian_bytes64(data, peer->last_active); + data += sizeof(uint64_t); + + // TODO(iphydf): This looks broken: nick_len can be > 255. + *data = peer->nick_len; + ++data; + + memcpy(data, peer->nick, peer->nick_len); + data += peer->nick_len; + + return data; +} + +#define SAVED_CONF_SIZE_CONSTANT (1 + GROUP_ID_LENGTH + sizeof(uint32_t) \ + + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + 1) + +non_null() +static uint32_t saved_conf_size(const Group_c *g) +{ + uint32_t len = SAVED_CONF_SIZE_CONSTANT + g->title_len; + + for (uint32_t j = 0; j < g->numpeers + g->numfrozen; ++j) { + const Group_Peer *peer = (j < g->numpeers) ? &g->group[j] : &g->frozen[j - g->numpeers]; + + if (pk_equal(peer->real_pk, g->real_pk)) { + continue; + } + + len += saved_peer_size(peer); + } + + return len; +} + +/** + * Save a future message number. The save will remain valid until we have sent + * this many more messages. + */ +#define SAVE_OFFSET_MESSAGE_NUMBER (1 << 16) +#define SAVE_OFFSET_LOSSY_MESSAGE_NUMBER (1 << 13) + +non_null() +static uint8_t *save_conf(const Group_c *g, uint8_t *data) +{ + *data = g->type; + ++data; + + memcpy(data, g->id, GROUP_ID_LENGTH); + data += GROUP_ID_LENGTH; + + host_to_lendian_bytes32(data, g->message_number + SAVE_OFFSET_MESSAGE_NUMBER); + data += sizeof(uint32_t); + + host_to_lendian_bytes16(data, g->lossy_message_number + SAVE_OFFSET_LOSSY_MESSAGE_NUMBER); + data += sizeof(uint16_t); + + host_to_lendian_bytes16(data, g->peer_number); + data += sizeof(uint16_t); + + uint8_t *const numsaved_location = data; + data += sizeof(uint32_t); + + *data = g->title_len; + ++data; + + memcpy(data, g->title, g->title_len); + data += g->title_len; + + uint32_t numsaved = 0; + + for (uint32_t j = 0; j < g->numpeers + g->numfrozen; ++j) { + const Group_Peer *peer = (j < g->numpeers) ? &g->group[j] : &g->frozen[j - g->numpeers]; + + if (pk_equal(peer->real_pk, g->real_pk)) { + continue; + } + + data = save_peer(peer, data); + ++numsaved; + } + + host_to_lendian_bytes32(numsaved_location, numsaved); + + return data; +} + +non_null() +static uint32_t conferences_section_size(const Group_Chats *g_c) +{ + uint32_t len = 0; + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + const Group_c *g = get_group_c(g_c, i); + + if (g == nullptr || g->status != GROUPCHAT_STATUS_CONNECTED) { + continue; + } + + len += saved_conf_size(g); + } + + return len; +} + +uint32_t conferences_size(const Group_Chats *g_c) +{ + return 2 * sizeof(uint32_t) + conferences_section_size(g_c); +} + +uint8_t *conferences_save(const Group_Chats *g_c, uint8_t *data) +{ + const uint32_t len = conferences_section_size(g_c); + data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_CONFERENCES); + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + const Group_c *g = get_group_c(g_c, i); + + if (g == nullptr || g->status != GROUPCHAT_STATUS_CONNECTED) { + continue; + } + + data = save_conf(g, data); + } + + return data; +} + +/** + * @brief load_group Load a Group section from a save file + * @param g Group to load + * @param g_c Reference to all groupchats, need for utility functions + * @param data Start of the data to deserialze + * @param length Length of data + * @return 0 on error, number of consumed bytes otherwise + */ +non_null() +static uint32_t load_group(Group_c *g, const Group_Chats *g_c, const uint8_t *data, uint32_t length) +{ + const uint8_t *init_data = data; + + // Initialize to default values so we can unconditionally free in case of an error + setup_conference(g); + + g->type = *data; + ++data; + + memcpy(g->id, data, GROUP_ID_LENGTH); + data += GROUP_ID_LENGTH; + + lendian_bytes_to_host32(&g->message_number, data); + data += sizeof(uint32_t); + + lendian_bytes_to_host16(&g->lossy_message_number, data); + data += sizeof(uint16_t); + + lendian_bytes_to_host16(&g->peer_number, data); + data += sizeof(uint16_t); + + lendian_bytes_to_host32(&g->numfrozen, data); + data += sizeof(uint32_t); + + g->title_len = *data; + + if (g->title_len > MAX_NAME_LENGTH) { + return 0; + } + + ++data; + + assert((data - init_data) < UINT32_MAX); + + if (length < (uint32_t)(data - init_data) + g->title_len) { + return 0; + } + + memcpy(g->title, data, g->title_len); + data += g->title_len; + + for (uint32_t j = 0; j < g->numfrozen; ++j) { + + assert((data - init_data) < UINT32_MAX); + + if (length < (uint32_t)(data - init_data) + SAVED_PEER_SIZE_CONSTANT) { + return 0; + } + + // This is inefficient, but allows us to check data consistency before allocating memory + Group_Peer *tmp_frozen = (Group_Peer *)realloc(g->frozen, (j + 1) * sizeof(Group_Peer)); + + if (tmp_frozen == nullptr) { + // Memory allocation failure + return 0; + } + + g->frozen = tmp_frozen; + + Group_Peer *peer = &g->frozen[j]; + *peer = empty_group_peer; + + pk_copy(peer->real_pk, data); + data += CRYPTO_PUBLIC_KEY_SIZE; + pk_copy(peer->temp_pk, data); + data += CRYPTO_PUBLIC_KEY_SIZE; + + lendian_bytes_to_host16(&peer->peer_number, data); + data += sizeof(uint16_t); + + lendian_bytes_to_host64(&peer->last_active, data); + data += sizeof(uint64_t); + + peer->nick_len = *data; + + if (peer->nick_len > MAX_NAME_LENGTH) { + return 0; + } + + ++data; + assert((data - init_data) < UINT32_MAX); + + if (length < (uint32_t)(data - init_data) + peer->nick_len) { + return 0; + } + + memcpy(peer->nick, data, peer->nick_len); + data += peer->nick_len; + + // NOTE: this relies on friends being loaded before conferences. + peer->is_friend = getfriend_id(g_c->m, peer->real_pk) != -1; + } + + if (g->numfrozen > g->maxfrozen) { + g->maxfrozen = g->numfrozen; + } + + g->status = GROUPCHAT_STATUS_CONNECTED; + + pk_copy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto)); + + assert((data - init_data) < UINT32_MAX); + + return (uint32_t)(data - init_data); +} + +non_null() +static State_Load_Status load_conferences_helper(Group_Chats *g_c, const uint8_t *data, uint32_t length) +{ + const uint8_t *init_data = data; + + while (length >= (uint32_t)(data - init_data) + SAVED_CONF_SIZE_CONSTANT) { + const int groupnumber = create_group_chat(g_c); + + // Helpful for testing + assert(groupnumber != -1); + + if (groupnumber == -1) { + // If this fails there's a serious problem, don't bother with cleanup + return STATE_LOAD_STATUS_ERROR; + } + + Group_c *g = &g_c->chats[groupnumber]; + + const uint32_t consumed = load_group(g, g_c, data, length - (uint32_t)(data - init_data)); + + if (consumed == 0) { + // remove partially loaded stuff, wipe_group_chat must be able to wipe a partially loaded group + const bool ret = wipe_group_chat(g_c, groupnumber); + + // HACK: suppress unused variable warning + if (!ret) { + // wipe_group_chat(...) must be able to wipe partially allocated groups + assert(ret); + } + + return STATE_LOAD_STATUS_ERROR; + } + + data += consumed; + + const int peer_index = addpeer(g_c, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), g->peer_number, + nullptr, true, false); + + if (peer_index == -1) { + return STATE_LOAD_STATUS_ERROR; + } + + setnick(g_c, groupnumber, peer_index, g_c->m->name, g_c->m->name_length, nullptr, false); + } + + return STATE_LOAD_STATUS_CONTINUE; +} + +non_null() +static State_Load_Status load_conferences(Group_Chats *g_c, const uint8_t *data, uint32_t length) +{ + const State_Load_Status res = load_conferences_helper(g_c, data, length); + + if (res == STATE_LOAD_STATUS_CONTINUE) { + return res; + } + + // Loading failed, cleanup all Group_c + + // save locally, because wipe_group_chat(...) modifies it + const uint16_t num_groups = g_c->num_chats; + + for (uint16_t i = 0; i < num_groups; ++i) { + wipe_group_chat(g_c, i); + } + + return res; +} + +bool conferences_load_state_section(Group_Chats *g_c, const uint8_t *data, uint32_t length, uint16_t type, + State_Load_Status *status) +{ + if (type != STATE_TYPE_CONFERENCES) { + return false; + } + + *status = load_conferences(g_c, data, length); + return true; +} + + +/** Create new groupchat instance. */ +Group_Chats *new_groupchats(const Mono_Time *mono_time, Messenger *m) +{ + if (m == nullptr) { + return nullptr; + } + + Group_Chats *temp = (Group_Chats *)calloc(1, sizeof(Group_Chats)); + + if (temp == nullptr) { + return nullptr; + } + + temp->mono_time = mono_time; + temp->m = m; + temp->fr_c = m->fr_c; + m->conferences_object = temp; + m_callback_conference_invite(m, &handle_friend_invite_packet); + + set_global_status_callback(m->fr_c, &g_handle_any_status, temp); + + return temp; +} + +/** main groupchats loop. */ +void do_groupchats(Group_Chats *g_c, void *userdata) +{ + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + Group_c *g = get_group_c(g_c, i); + + if (g == nullptr) { + continue; + } + + if (g->status == GROUPCHAT_STATUS_CONNECTED) { + connect_to_closest(g_c, i, userdata); + ping_groupchat(g_c, i); + groupchat_freeze_timedout(g_c, i, userdata); + clean_connections(g_c, g); + + if (g->need_send_name) { + group_name_send(g_c, i, g_c->m->name, g_c->m->name_length); + g->need_send_name = false; + } + } + } + + // TODO(irungentoo): +} + +/** Free everything related with group chats. */ +void kill_groupchats(Group_Chats *g_c) +{ + if (g_c == nullptr) { + return; + } + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + del_groupchat(g_c, i, false); + } + + m_callback_conference_invite(g_c->m, nullptr); + set_global_status_callback(g_c->m->fr_c, nullptr, nullptr); + g_c->m->conferences_object = nullptr; + free(g_c); +} + +/** + * @brief Return the number of chats in the instance m. + * + * You should use this to determine how much memory to allocate + * for copy_chatlist. + */ +uint32_t count_chatlist(const Group_Chats *g_c) +{ + uint32_t ret = 0; + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + if (g_c->chats[i].status != GROUPCHAT_STATUS_NONE) { + ++ret; + } + } + + return ret; +} + +/** @brief Copy a list of valid chat IDs into the array out_list. + * + * If out_list is NULL, returns 0. + * Otherwise, returns the number of elements copied. + * If the array was too small, the contents + * of out_list will be truncated to list_size. + */ +uint32_t copy_chatlist(const Group_Chats *g_c, uint32_t *out_list, uint32_t list_size) +{ + if (out_list == nullptr) { + return 0; + } + + if (g_c->num_chats == 0) { + return 0; + } + + uint32_t ret = 0; + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + if (ret >= list_size) { + break; /* Abandon ship */ + } + + if (g_c->chats[i].status > GROUPCHAT_STATUS_NONE) { + out_list[ret] = i; + ++ret; + } + } + + return ret; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/group_announce.h b/local_pod_repo/toxcore/toxcore/toxcore/group_announce.h new file mode 100644 index 0000000..11bba7d --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/group_announce.h @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2020 The TokTok team. + * Copyright © 2015 Tox project. + */ + +/** + * Similar to ping.h, but designed for group chat purposes + */ +#ifndef GROUP_ANNOUNCE_H +#define GROUP_ANNOUNCE_H + +#include + +#include "DHT.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* The maximum number of announces to save for a particular group chat. */ +#define GCA_MAX_SAVED_ANNOUNCES_PER_GC 16 + +/* Maximum number of TCP relays that can be in an annoucne. */ +#define GCA_MAX_ANNOUNCED_TCP_RELAYS 1 + +/* Maximum number of announces we can send in an announce response. */ +#define GCA_MAX_SENT_ANNOUNCES 4 + +/* Maximum size of an announce. */ +#define GCA_ANNOUNCE_MAX_SIZE (ENC_PUBLIC_KEY_SIZE + 1 + 1 + (PACKED_NODE_SIZE_IP6 * 2)) + +/* Maximum size of a public announce. */ +#define GCA_PUBLIC_ANNOUNCE_MAX_SIZE (ENC_PUBLIC_KEY_SIZE + GCA_ANNOUNCE_MAX_SIZE) + +typedef struct GC_Announce GC_Announce; +typedef struct GC_Peer_Announce GC_Peer_Announce; +typedef struct GC_Announces GC_Announces; +typedef struct GC_Announces_List GC_Announces_List; +typedef struct GC_Public_Announce GC_Public_Announce; + +/* Base announce. */ +struct GC_Announce { + Node_format tcp_relays[GCA_MAX_ANNOUNCED_TCP_RELAYS]; + uint8_t tcp_relays_count; + bool ip_port_is_set; + IP_Port ip_port; + uint8_t peer_public_key[ENC_PUBLIC_KEY_SIZE]; +}; + +/* Peer announce for specific group. */ +struct GC_Peer_Announce { + GC_Announce base_announce; + uint64_t timestamp; +}; + +/* Used for announces in public groups. */ +struct GC_Public_Announce { + GC_Announce base_announce; + uint8_t chat_public_key[ENC_PUBLIC_KEY_SIZE]; +}; + +/* A linked list that holds all announces for a particular group. */ +struct GC_Announces { + uint8_t chat_id[CHAT_ID_SIZE]; + uint64_t index; + uint64_t last_announce_received_timestamp; + + GC_Peer_Announce peer_announces[GCA_MAX_SAVED_ANNOUNCES_PER_GC]; + + GC_Announces *next_announce; + GC_Announces *prev_announce; +}; + +/* A list of all announces. */ +struct GC_Announces_List { + GC_Announces *root_announces; + uint64_t last_timeout_check; +}; + + +/** @brief Returns a new group announces list. + * + * The caller is responsible for freeing the memory with `kill_gca`. + */ +GC_Announces_List *new_gca_list(void); + +/** @brief Frees all dynamically allocated memory associated with `announces_list`. */ +nullable(1) +void kill_gca(GC_Announces_List *announces_list); + +/** @brief Iterates through the announces list and removes announces that are considered stale. + * + * @param gc_announces_list The list of announces to iterate. + * + * This function should be called from the main loop, and will iterate the list a + * maxmimum of once per second. + */ +non_null() +void do_gca(const Mono_Time *mono_time, GC_Announces_List *gc_announces_list); + +/** @brief Frees all dynamically allocated memory associated with an announces list entry. + * + * @param gc_announces_list The announces list we want to search through. + * @param chat_id The chat ID that designates the entry we want to remove. + */ +non_null() +void cleanup_gca(GC_Announces_List *gc_announces_list, const uint8_t *chat_id); + +/** @brief Puts a set of announces from the announces list in supplied list. + * + * @param gc_announces_list The announces list we want to search for entries in. + * @param gc_announces An empty announces list that will be filled with matches. + * @param max_nodes The maximum number of matches that we want to add to the list. + * @param chat_id The chat ID associated with the announces that we want to add. + * @param except_public_key The public key associated with announces that we want to ignore. + * + * @return the number of added nodes on success. + * @retval -1 on failure. + */ +non_null() +int gca_get_announces(const GC_Announces_List *gc_announces_list, GC_Announce *gc_announces, uint8_t max_nodes, + const uint8_t *chat_id, const uint8_t *except_public_key); + +/** @brief Adds a public_announce to list of announces. + * + * @param gc_announces_list The announces list that we want to add an entry to. + * @param public_announce The public announce that we want to add. + * + * @return the peer announce on success. + * @retval null on failure. + */ +non_null() +GC_Peer_Announce *gca_add_announce(const Mono_Time *mono_time, GC_Announces_List *gc_announces_list, + const GC_Public_Announce *public_announce); + +/** @brief Packs an announce into a data buffer. + * + * @param data The data buffer being packed. + * @param length The size in bytes of the data buffer. Must be at least GCA_ANNOUNCE_MAX_SIZE. + * @param announce The announce being packed into the data buffer. + * + * @return the size of the packed data on success. + * @retval -1 on failure. + */ +non_null() +int gca_pack_announce(const Logger *log, uint8_t *data, uint16_t length, const GC_Announce *announce); + +/** @brief Returns the number of bytes needed for a buff in which to pack `count` announces. */ +uint16_t gca_pack_announces_list_size(uint16_t count); + +/** @brief Packs a list of announces into a data buffer. + * + * @param data The data buffer being packed. + * @param length The size in bytes of the data buffer. Use gca_pack_announces_list_size to get the + * required length. + * @param announces The announces to be packed into the data buffer. + * @param announces_count The number of announces in the announces list. + * @param processed If non-null, will contain the number of bytes packed (only on success). + * + * @return the number of packed announces on success. + * @retval -1 on failure. + */ +non_null(1, 2, 4) nullable(6) +int gca_pack_announces_list(const Logger *log, uint8_t *data, uint16_t length, const GC_Announce *announces, + uint8_t announces_count, size_t *processed); + +/** @brief Unpacks packed announces from a data buffer into a supplied list. + * + * @param data The data buffer to unpack from. + * @param length The size of the data buffer. + * @param announces The announces list that the data buffer will be unpacked to. + * @param max_count The maximum number of announces to unpack. + * + * @return the number of unpacked announces on success. + * @retval -1 on failure. + */ +non_null() +int gca_unpack_announces_list(const Logger *log, const uint8_t *data, uint16_t length, GC_Announce *announces, + uint8_t max_count); + +/** @brief Packs a public announce into a data buffer. + * + * @param data The data buffer being packed. + * @param length The size in bytes of the data buffer. Must be at least GCA_PUBLIC_ANNOUNCE_MAX_SIZE. + * @param public_announce The public announce being packed into the data buffer. + * + * @return the size of the packed data on success. + * @retval -1 on failure. + */ +non_null() +int gca_pack_public_announce(const Logger *log, uint8_t *data, uint16_t length, + const GC_Public_Announce *public_announce); + +/** @brief Unpacks a public announce from a data buffer into a supplied public announce. + * + * @param data The data buffer to unpack from. + * @param length The size of the data buffer. + * @param public_announce The public announce to unpack the data buffer into. + * + * @return the size of the unpacked data on success. + * @retval -1 on failure. + */ +non_null() +int gca_unpack_public_announce(const Logger *log, const uint8_t *data, uint16_t length, + GC_Public_Announce *public_announce); + +/** @brief Returns true if the announce is valid. + * + * An announce is considered valid if there is at least one TCP relay, or the ip_port is set. + */ +non_null() +bool gca_is_valid_announce(const GC_Announce *announce); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // GROUP_ANNOUNCE_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/group_announce.m b/local_pod_repo/toxcore/toxcore/toxcore/group_announce.m new file mode 100644 index 0000000..83dd3ac --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/group_announce.m @@ -0,0 +1,462 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2020 The TokTok team. + * Copyright © 2015 Tox project. + */ + +#include "group_announce.h" + +#include +#include + +#include "LAN_discovery.h" +#include "ccompat.h" +#include "mono_time.h" +#include "util.h" + +/** + * Removes `announces` from `gc_announces_list`. + */ +non_null() +static void remove_announces(GC_Announces_List *gc_announces_list, GC_Announces *announces) +{ + if (announces == nullptr || gc_announces_list == nullptr) { + return; + } + + if (announces->prev_announce != nullptr) { + announces->prev_announce->next_announce = announces->next_announce; + } else { + gc_announces_list->root_announces = announces->next_announce; + } + + if (announces->next_announce != nullptr) { + announces->next_announce->prev_announce = announces->prev_announce; + } + + free(announces); +} + +/** + * Returns the announce designated by `chat_id`. + * Returns null if no announce is found. + */ +non_null() +static GC_Announces *get_announces_by_chat_id(const GC_Announces_List *gc_announces_list, const uint8_t *chat_id) +{ + GC_Announces *announces = gc_announces_list->root_announces; + + while (announces != nullptr) { + if (memcmp(announces->chat_id, chat_id, CHAT_ID_SIZE) == 0) { + return announces; + } + + announces = announces->next_announce; + } + + return nullptr; +} + +int gca_get_announces(const GC_Announces_List *gc_announces_list, GC_Announce *gc_announces, uint8_t max_nodes, + const uint8_t *chat_id, const uint8_t *except_public_key) +{ + if (gc_announces == nullptr || gc_announces_list == nullptr || chat_id == nullptr || max_nodes == 0 + || except_public_key == nullptr) { + return -1; + } + + const GC_Announces *announces = get_announces_by_chat_id(gc_announces_list, chat_id); + + if (announces == nullptr) { + return 0; + } + + uint16_t added_count = 0; + + for (size_t i = 0; i < announces->index && i < GCA_MAX_SAVED_ANNOUNCES_PER_GC && added_count < max_nodes; ++i) { + const size_t index = i % GCA_MAX_SAVED_ANNOUNCES_PER_GC; + + if (memcmp(except_public_key, &announces->peer_announces[index].base_announce.peer_public_key, + ENC_PUBLIC_KEY_SIZE) == 0) { + continue; + } + + bool already_added = false; + + for (size_t j = 0; j < added_count; ++j) { + if (memcmp(&gc_announces[j].peer_public_key, &announces->peer_announces[index].base_announce.peer_public_key, + ENC_PUBLIC_KEY_SIZE) == 0) { + already_added = true; + break; + } + } + + if (!already_added) { + gc_announces[added_count] = announces->peer_announces[index].base_announce; + ++added_count; + } + } + + return added_count; +} + +uint16_t gca_pack_announces_list_size(uint16_t count) +{ + return count * GCA_ANNOUNCE_MAX_SIZE; +} + +int gca_pack_announce(const Logger *log, uint8_t *data, uint16_t length, const GC_Announce *announce) +{ + if (length < GCA_ANNOUNCE_MAX_SIZE) { + LOGGER_ERROR(log, "Invalid announce length: %u", length); + return -1; + } + + if (data == nullptr) { + LOGGER_ERROR(log, "data is null"); + return -1; + } + + if (announce == nullptr) { + LOGGER_ERROR(log, "announce is null"); + return -1; + } + + uint16_t offset = 0; + memcpy(data + offset, announce->peer_public_key, ENC_PUBLIC_KEY_SIZE); + offset += ENC_PUBLIC_KEY_SIZE; + + data[offset] = announce->ip_port_is_set ? 1 : 0; + ++offset; + + data[offset] = announce->tcp_relays_count; + ++offset; + + if (!announce->ip_port_is_set && announce->tcp_relays_count == 0) { + LOGGER_ERROR(log, "Failed to pack announce: no valid ip_port or tcp relay"); + return -1; + } + + if (announce->ip_port_is_set) { + const int ip_port_length = pack_ip_port(log, data + offset, length - offset, &announce->ip_port); + + if (ip_port_length == -1) { + LOGGER_ERROR(log, "Failed to pack ip_port"); + return -1; + } + + offset += ip_port_length; + } + + const int nodes_length = pack_nodes(log, data + offset, length - offset, announce->tcp_relays, + announce->tcp_relays_count); + + if (nodes_length == -1) { + LOGGER_ERROR(log, "Failed to pack TCP nodes"); + return -1; + } + + return nodes_length + offset; +} + +/** + * Unpacks `announce` into `data` buffer of size `length`. + * + * Returns the size of the unpacked data on success. + * Returns -1 on failure. + */ +non_null() +static int gca_unpack_announce(const Logger *log, const uint8_t *data, uint16_t length, GC_Announce *announce) +{ + if (length < ENC_PUBLIC_KEY_SIZE + 2) { + LOGGER_ERROR(log, "Invalid announce length: %u", length); + return -1; + } + + if (data == nullptr) { + LOGGER_ERROR(log, "data is null"); + return -1; + } + + if (announce == nullptr) { + LOGGER_ERROR(log, "announce is null"); + return -1; + } + + uint16_t offset = 0; + memcpy(announce->peer_public_key, data + offset, ENC_PUBLIC_KEY_SIZE); + offset += ENC_PUBLIC_KEY_SIZE; + + announce->ip_port_is_set = data[offset] == 1; + ++offset; + + announce->tcp_relays_count = data[offset]; + ++offset; + + if (announce->tcp_relays_count > GCA_MAX_ANNOUNCED_TCP_RELAYS) { + return -1; + } + + if (announce->ip_port_is_set) { + if (length - offset == 0) { + return -1; + } + + const int ip_port_length = unpack_ip_port(&announce->ip_port, data + offset, length - offset, false); + + if (ip_port_length == -1) { + LOGGER_ERROR(log, "Failed to unpack ip_port"); + return -1; + } + + offset += ip_port_length; + } + + uint16_t nodes_length; + const int nodes_count = unpack_nodes(announce->tcp_relays, announce->tcp_relays_count, &nodes_length, + data + offset, length - offset, true); + + if (nodes_count != announce->tcp_relays_count) { + LOGGER_ERROR(log, "Failed to unpack TCP nodes"); + return -1; + } + + return offset + nodes_length; +} + +int gca_pack_public_announce(const Logger *log, uint8_t *data, uint16_t length, + const GC_Public_Announce *public_announce) +{ + if (public_announce == nullptr || data == nullptr || length < CHAT_ID_SIZE) { + return -1; + } + + memcpy(data, public_announce->chat_public_key, CHAT_ID_SIZE); + + const int packed_size = gca_pack_announce(log, data + CHAT_ID_SIZE, length - CHAT_ID_SIZE, + &public_announce->base_announce); + + if (packed_size < 0) { + LOGGER_ERROR(log, "Failed to pack public group announce"); + return -1; + } + + return packed_size + CHAT_ID_SIZE; +} + +int gca_unpack_public_announce(const Logger *log, const uint8_t *data, uint16_t length, + GC_Public_Announce *public_announce) +{ + if (length < CHAT_ID_SIZE) { + LOGGER_ERROR(log, "invalid public announce length: %u", length); + return -1; + } + + if (data == nullptr) { + LOGGER_ERROR(log, "data is null"); + return -1; + } + + if (public_announce == nullptr) { + LOGGER_ERROR(log, "public_announce is null"); + return -1; + } + + memcpy(public_announce->chat_public_key, data, CHAT_ID_SIZE); + + const int base_announce_size = gca_unpack_announce(log, data + ENC_PUBLIC_KEY_SIZE, length - ENC_PUBLIC_KEY_SIZE, + &public_announce->base_announce); + + if (base_announce_size == -1) { + LOGGER_ERROR(log, "Failed to unpack group announce"); + return -1; + } + + return base_announce_size + CHAT_ID_SIZE; +} + +int gca_pack_announces_list(const Logger *log, uint8_t *data, uint16_t length, const GC_Announce *announces, + uint8_t announces_count, size_t *processed) +{ + if (data == nullptr) { + LOGGER_ERROR(log, "data is null"); + return -1; + } + + if (announces == nullptr) { + LOGGER_ERROR(log, "announces is null"); + return -1; + } + + uint16_t offset = 0; + + for (size_t i = 0; i < announces_count; ++i) { + const int packed_length = gca_pack_announce(log, data + offset, length - offset, &announces[i]); + + if (packed_length < 0) { + LOGGER_ERROR(log, "Failed to pack group announce"); + return -1; + } + + offset += packed_length; + } + + if (processed != nullptr) { + *processed = offset; + } + + return announces_count; +} + +int gca_unpack_announces_list(const Logger *log, const uint8_t *data, uint16_t length, GC_Announce *announces, + uint8_t max_count) +{ + if (data == nullptr) { + LOGGER_ERROR(log, "data is null"); + return -1; + } + + if (announces == nullptr) { + LOGGER_ERROR(log, "announces is null"); + return -1; + } + + uint16_t offset = 0; + int announces_count = 0; + + for (size_t i = 0; i < max_count && length > offset; ++i) { + const int unpacked_length = gca_unpack_announce(log, data + offset, length - offset, &announces[i]); + + if (unpacked_length == -1) { + LOGGER_WARNING(log, "Failed to unpack group announce: %d %d", length, offset); + return -1; + } + + offset += unpacked_length; + ++announces_count; + } + + return announces_count; +} + +GC_Peer_Announce *gca_add_announce(const Mono_Time *mono_time, GC_Announces_List *gc_announces_list, + const GC_Public_Announce *public_announce) +{ + if (gc_announces_list == nullptr || public_announce == nullptr) { + return nullptr; + } + + GC_Announces *announces = get_announces_by_chat_id(gc_announces_list, public_announce->chat_public_key); + + // No entry for this chat_id exists so we create one + if (announces == nullptr) { + announces = (GC_Announces *)calloc(1, sizeof(GC_Announces)); + + if (announces == nullptr) { + return nullptr; + } + + announces->index = 0; + announces->prev_announce = nullptr; + + if (gc_announces_list->root_announces != nullptr) { + gc_announces_list->root_announces->prev_announce = announces; + } + + announces->next_announce = gc_announces_list->root_announces; + gc_announces_list->root_announces = announces; + memcpy(announces->chat_id, public_announce->chat_public_key, CHAT_ID_SIZE); + } + + const uint64_t cur_time = mono_time_get(mono_time); + + announces->last_announce_received_timestamp = cur_time; + + const uint64_t index = announces->index % GCA_MAX_SAVED_ANNOUNCES_PER_GC; + + GC_Peer_Announce *gc_peer_announce = &announces->peer_announces[index]; + + gc_peer_announce->base_announce = public_announce->base_announce; + + gc_peer_announce->timestamp = cur_time; + + ++announces->index; + + return gc_peer_announce; +} + +bool gca_is_valid_announce(const GC_Announce *announce) +{ + if (announce == nullptr) { + return false; + } + + return announce->tcp_relays_count > 0 || announce->ip_port_is_set; +} + +GC_Announces_List *new_gca_list(void) +{ + GC_Announces_List *announces_list = (GC_Announces_List *)calloc(1, sizeof(GC_Announces_List)); + return announces_list; +} + +void kill_gca(GC_Announces_List *announces_list) +{ + if (announces_list == nullptr) { + return; + } + + GC_Announces *root = announces_list->root_announces; + + while (root != nullptr) { + GC_Announces *next = root->next_announce; + free(root); + root = next; + } + + free(announces_list); +} + +/* How long we save a peer's announce before we consider it stale and remove it. */ +#define GCA_ANNOUNCE_SAVE_TIMEOUT 30 + +/* How often we run do_gca() */ +#define GCA_DO_GCA_TIMEOUT 1 + +void do_gca(const Mono_Time *mono_time, GC_Announces_List *gc_announces_list) +{ + if (gc_announces_list == nullptr) { + return; + } + + if (!mono_time_is_timeout(mono_time, gc_announces_list->last_timeout_check, GCA_DO_GCA_TIMEOUT)) { + return; + } + + gc_announces_list->last_timeout_check = mono_time_get(mono_time); + + GC_Announces *announces = gc_announces_list->root_announces; + + while (announces != nullptr) { + if (mono_time_is_timeout(mono_time, announces->last_announce_received_timestamp, GCA_ANNOUNCE_SAVE_TIMEOUT)) { + GC_Announces *to_delete = announces; + announces = announces->next_announce; + remove_announces(gc_announces_list, to_delete); + continue; + } + + announces = announces->next_announce; + } +} + +void cleanup_gca(GC_Announces_List *gc_announces_list, const uint8_t *chat_id) +{ + if (gc_announces_list == nullptr || chat_id == nullptr) { + return; + } + + GC_Announces *announces = get_announces_by_chat_id(gc_announces_list, chat_id); + + if (announces != nullptr) { + remove_announces(gc_announces_list, announces); + } +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/group_moderation.h b/local_pod_repo/toxcore/toxcore/toxcore/group_moderation.h new file mode 100644 index 0000000..36b44a4 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/group_moderation.h @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2020 The TokTok team. + * Copyright © 2015 Tox project. + */ + +/** + * An implementation of massive text only group chats. + */ + +#ifndef C_TOXCORE_TOXCORE_GROUP_MODERATION_H +#define C_TOXCORE_TOXCORE_GROUP_MODERATION_H + +#include +#include + +#include "DHT.h" +#include "logger.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MOD_MODERATION_HASH_SIZE CRYPTO_SHA256_SIZE +#define MOD_LIST_ENTRY_SIZE SIG_PUBLIC_KEY_SIZE +#define MOD_SANCTION_HASH_SIZE CRYPTO_SHA256_SIZE + +#define TIME_STAMP_SIZE sizeof(uint64_t) + +/* The packed size of a Mod_Sanction_Creds */ +#define MOD_SANCTIONS_CREDS_SIZE (sizeof(uint32_t) + MOD_SANCTION_HASH_SIZE + sizeof(uint16_t) +\ + SIG_PUBLIC_KEY_SIZE + SIGNATURE_SIZE) + +/* The packed size of a Mod_Sanction */ +#define MOD_SANCTION_PACKED_SIZE (SIG_PUBLIC_KEY_SIZE + TIME_STAMP_SIZE + 1 + ENC_PUBLIC_KEY_SIZE + SIGNATURE_SIZE) + +/* The max size of a groupchat packet with 100 bytes reserved for header data */ +#define MAX_PACKET_SIZE_NO_HEADERS 49900 + +/* These values must take into account the maximum allowed packet size and headers. */ +#define MOD_MAX_NUM_MODERATORS (((MAX_PACKET_SIZE_NO_HEADERS) / (MOD_LIST_ENTRY_SIZE))) +#define MOD_MAX_NUM_SANCTIONS (((MAX_PACKET_SIZE_NO_HEADERS - (MOD_SANCTIONS_CREDS_SIZE)) / (MOD_SANCTION_PACKED_SIZE))) + +typedef enum Mod_Sanction_Type { + SA_OBSERVER = 0x00, + SA_INVALID = 0x01, +} Mod_Sanction_Type; + +typedef struct Mod_Sanction_Creds { + uint32_t version; + uint8_t hash[MOD_SANCTION_HASH_SIZE]; // hash of all sanctions list signatures + version + uint16_t checksum; // a sum of the hash + uint8_t sig_pk[SIG_PUBLIC_KEY_SIZE]; // Last mod to have modified the sanctions list + uint8_t sig[SIGNATURE_SIZE]; // signature of hash, signed by sig_pk +} Mod_Sanction_Creds; + +/** Holds data pertaining to a peer who has been sanctioned. */ +typedef struct Mod_Sanction { + uint8_t setter_public_sig_key[SIG_PUBLIC_KEY_SIZE]; + + // TODO(Jfreegman): This timestamp can potentially be used to track a user across + // different group chats if they're a moderator and set many sanctions across the + // different groups. This should be addressed in the future. + uint64_t time_set; + + uint8_t type; + uint8_t target_public_enc_key[ENC_PUBLIC_KEY_SIZE]; + + /* Signature of all above packed data signed by the owner of public_sig_key */ + uint8_t signature[SIGNATURE_SIZE]; +} Mod_Sanction; + +typedef struct Moderation { + const Logger *log; + + Mod_Sanction *sanctions; + uint16_t num_sanctions; + + Mod_Sanction_Creds sanctions_creds; + + uint8_t **mod_list; // array of public signature keys of all the mods + uint16_t num_mods; + + // copies from parent/sibling chat/shared state objects + uint8_t founder_public_sig_key[SIG_PUBLIC_KEY_SIZE]; + uint8_t self_public_sig_key[SIG_PUBLIC_KEY_SIZE]; + uint8_t self_secret_sig_key[SIG_SECRET_KEY_SIZE]; + uint32_t shared_state_version; +} Moderation; + +/** @brief Returns the size in bytes of the packed moderation list. */ +non_null() +uint16_t mod_list_packed_size(const Moderation *moderation); + +/** @brief Unpacks data into the moderator list. + * + * @param data should contain num_mods entries of size MOD_LIST_ENTRY_SIZE. + * + * Returns length of unpacked data on success. + * Returns -1 on failure. + */ +non_null() +int mod_list_unpack(Moderation *moderation, const uint8_t *data, uint16_t length, uint16_t num_mods); + +/** @brief Packs moderator list into data. + * @param data must have room for the number of bytes returned by `mod_list_packed_size`. + */ +non_null() +void mod_list_pack(const Moderation *moderation, uint8_t *data); + +/** @brief Creates a new moderator list hash and puts it in `hash`. + * + * @param hash must have room for at least MOD_MODERATION_HASH_SIZE bytes. + * + * If num_mods is 0 the hash is zeroed. + * + * Returns true on sucess. + */ +non_null() +bool mod_list_make_hash(const Moderation *moderation, uint8_t *hash); + +/** @brief Puts a sha256 hash of `packed_mod_list` of `length` bytes in `hash`. + * + * @param hash must have room for at least MOD_MODERATION_HASH_SIZE bytes. + */ +non_null() +void mod_list_get_data_hash(uint8_t *hash, const uint8_t *packed_mod_list, uint16_t length); + +/** @brief Removes moderator at index-th position in the moderator list. + * + * Returns true on success. + */ +non_null() +bool mod_list_remove_index(Moderation *moderation, uint16_t index); + +/** @brief Removes public_sig_key from the moderator list. + * + * Returns true on success. + */ +non_null() +bool mod_list_remove_entry(Moderation *moderation, const uint8_t *public_sig_key); + +/** @brief Adds a mod to the moderator list. + * + * @param mod_data must be MOD_LIST_ENTRY_SIZE bytes. + * + * Returns true on success. + */ +non_null() +bool mod_list_add_entry(Moderation *moderation, const uint8_t *mod_data); + +/** @return true if the public signature key belongs to a moderator or the founder */ +non_null() +bool mod_list_verify_sig_pk(const Moderation *moderation, const uint8_t *sig_pk); + +/** @brief Frees all memory associated with the moderator list and sets num_mods to 0. */ +nullable(1) +void mod_list_cleanup(Moderation *moderation); + +/** @brief Returns the size in bytes of num_sanctions packed sanctions. */ +uint16_t sanctions_list_packed_size(uint16_t num_sanctions); + +/** @brief Packs sanctions into data. Additionally packs the sanctions credentials into creds. + * + * @param data The byte array being packed. Must have room for the number of bytes returned + * by `sanctions_list_packed_size`. + * @param length The size of the byte array. + * @param sanctions The sanctions list. + * @param num_sanctions The number of sanctions in the sanctions list. This value must be the same + * value used when calling `sanctions_list_packed_size`. + * @param creds The credentials object to fill. + * + * @retval The length of packed data on success. + * @retval -1 on failure. + */ +non_null(1) nullable(3, 5) +int sanctions_list_pack(uint8_t *data, uint16_t length, const Mod_Sanction *sanctions, uint16_t num_sanctions, + const Mod_Sanction_Creds *creds); + +/** @brief Unpacks sanctions and new sanctions credentials. + * + * @param sanctions The sanctions array the sanctions data is unpacked into. + * @param creds The creds object the creds data is unpacked into. + * @param max_sanctions The maximum number of sanctions that the sanctions array can hold. + * @param data The packed data array. + * @param length The size of the packed data. + * @param processed_data_len If non-null, will contain the number of processed bytes on success. + * + * @retval The number of unpacked entries on success. + * @retval -1 on failure. + */ +non_null(1, 2, 4) nullable(6) +int sanctions_list_unpack(Mod_Sanction *sanctions, Mod_Sanction_Creds *creds, uint16_t max_sanctions, + const uint8_t *data, uint16_t length, uint16_t *processed_data_len); + +/** @brief Packs sanction list credentials into data. + * + * @param data must have room for MOD_SANCTIONS_CREDS_SIZE bytes. + * + * Returns length of packed data. + */ +non_null() +uint16_t sanctions_creds_pack(const Mod_Sanction_Creds *creds, uint8_t *data); + +/** @brief Unpacks sanctions credentials into creds from data. + * + * @param data must have room for MOD_SANCTIONS_CREDS_SIZE bytes. + * + * Returns the length of the data processed. + */ +non_null() +uint16_t sanctions_creds_unpack(Mod_Sanction_Creds *creds, const uint8_t *data); + +/** @brief Updates sanction list credentials. + * + * Increment version, replace sig_pk with your own, update hash to reflect new + * sanction list, and sign new hash signature. + * + * Returns true on success. + */ +non_null() +bool sanctions_list_make_creds(Moderation *moderation); + +/** @brief Validates all sanctions list entries as well as the list itself. + * + * Returns true if all entries are valid. + * Returns false if one or more entries are invalid. + */ +non_null() +bool sanctions_list_check_integrity(const Moderation *moderation, const Mod_Sanction_Creds *creds, + const Mod_Sanction *sanctions, uint16_t num_sanctions); + +/** @brief Adds an entry to the sanctions list. + * + * The entry is first validated and the resulting new sanction list is + * compared against the new credentials. + * + * Entries must be unique. + * + * Returns true on success. + */ +non_null(1, 2) nullable(3) +bool sanctions_list_add_entry(Moderation *moderation, const Mod_Sanction *sanction, const Mod_Sanction_Creds *creds); + +/** @brief Creates a new sanction entry for `public_key` where type is one of Mod_Sanction_Type. + * + * New entry is signed and placed in the sanctions list. + * + * Returns true on success. + */ +non_null() +bool sanctions_list_make_entry(Moderation *moderation, const uint8_t *public_key, Mod_Sanction *sanction, + uint8_t type); + +/** @return true if public key is in the observer list. */ +non_null() +bool sanctions_list_is_observer(const Moderation *moderation, const uint8_t *public_key); + +/** @return true if sanction already exists in the sanctions list. */ +non_null() +bool sanctions_list_entry_exists(const Moderation *moderation, const Mod_Sanction *sanction); + +/** @brief Removes observer entry for public key from sanction list. + * + * If creds is NULL we make new credentials (this should only be done by a moderator or founder) + * + * Returns false on failure or if entry was not found. + */ +non_null(1, 2) nullable(3) +bool sanctions_list_remove_observer(Moderation *moderation, const uint8_t *public_key, + const Mod_Sanction_Creds *creds); + +/** @brief Replaces all sanctions list signatures made by public_sig_key with the caller's. + * + * This is called whenever the founder demotes a moderator. + * + * Returns the number of entries re-signed. + */ +non_null() +uint16_t sanctions_list_replace_sig(Moderation *moderation, const uint8_t *public_sig_key); + +non_null() +void sanctions_list_cleanup(Moderation *moderation); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // C_TOXCORE_TOXCORE_GROUP_MODERATION_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/group_moderation.m b/local_pod_repo/toxcore/toxcore/toxcore/group_moderation.m new file mode 100644 index 0000000..dea38a6 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/group_moderation.m @@ -0,0 +1,864 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2020 The TokTok team. + * Copyright © 2015 Tox project. + */ + +/** + * An implementation of massive text only group chats. + */ + +#include "group_moderation.h" + +#include + +#include +#include +#include + +#include "ccompat.h" +#include "crypto_core.h" +#include "mono_time.h" +#include "network.h" +#include "util.h" + +static_assert(MOD_SANCTIONS_CREDS_SIZE <= MAX_PACKET_SIZE_NO_HEADERS, + "MOD_SANCTIONS_CREDS_SIZE must be <= the maximum allowed payload size"); +static_assert(MOD_MAX_NUM_SANCTIONS * MOD_SANCTION_PACKED_SIZE + MOD_SANCTIONS_CREDS_SIZE <= MAX_PACKET_SIZE_NO_HEADERS, + "MOD_MAX_NUM_SANCTIONS must be able to fit inside the maximum allowed payload size"); +static_assert(MOD_MAX_NUM_MODERATORS * MOD_LIST_ENTRY_SIZE <= MAX_PACKET_SIZE_NO_HEADERS, + "MOD_MAX_NUM_MODERATORS must be able to fit insize the maximum allowed payload size"); + +uint16_t mod_list_packed_size(const Moderation *moderation) +{ + return moderation->num_mods * MOD_LIST_ENTRY_SIZE; +} + +int mod_list_unpack(Moderation *moderation, const uint8_t *data, uint16_t length, uint16_t num_mods) +{ + if (length < num_mods * MOD_LIST_ENTRY_SIZE) { + return -1; + } + + mod_list_cleanup(moderation); + + if (num_mods == 0) { + return 0; + } + + uint8_t **tmp_list = (uint8_t **)calloc(num_mods, sizeof(uint8_t *)); + + if (tmp_list == nullptr) { + return -1; + } + + uint16_t unpacked_len = 0; + + for (uint16_t i = 0; i < num_mods; ++i) { + tmp_list[i] = (uint8_t *)malloc(sizeof(uint8_t) * MOD_LIST_ENTRY_SIZE); + + if (tmp_list[i] == nullptr) { + free_uint8_t_pointer_array(tmp_list, i); + return -1; + } + + memcpy(tmp_list[i], &data[i * MOD_LIST_ENTRY_SIZE], MOD_LIST_ENTRY_SIZE); + unpacked_len += MOD_LIST_ENTRY_SIZE; + } + + moderation->mod_list = tmp_list; + moderation->num_mods = num_mods; + + return unpacked_len; +} + +void mod_list_pack(const Moderation *moderation, uint8_t *data) +{ + for (uint16_t i = 0; i < moderation->num_mods; ++i) { + memcpy(&data[i * MOD_LIST_ENTRY_SIZE], moderation->mod_list[i], MOD_LIST_ENTRY_SIZE); + } +} + +void mod_list_get_data_hash(uint8_t *hash, const uint8_t *packed_mod_list, uint16_t length) +{ + crypto_sha256(hash, packed_mod_list, length); +} + +bool mod_list_make_hash(const Moderation *moderation, uint8_t *hash) +{ + if (moderation->num_mods == 0) { + memset(hash, 0, MOD_MODERATION_HASH_SIZE); + return true; + } + + const size_t data_buf_size = mod_list_packed_size(moderation); + + assert(data_buf_size > 0); + + uint8_t *data = (uint8_t *)malloc(data_buf_size); + + if (data == nullptr) { + return false; + } + + mod_list_pack(moderation, data); + + mod_list_get_data_hash(hash, data, data_buf_size); + + free(data); + + return true; +} + +/** + * Returns moderator list index for public_sig_key. + * Returns -1 if key is not in the list. + */ +non_null() +static int mod_list_index_of_sig_pk(const Moderation *moderation, const uint8_t *public_sig_key) +{ + for (uint16_t i = 0; i < moderation->num_mods; ++i) { + if (memcmp(moderation->mod_list[i], public_sig_key, SIG_PUBLIC_KEY_SIZE) == 0) { + return i; + } + } + + return -1; +} + +bool mod_list_verify_sig_pk(const Moderation *moderation, const uint8_t *sig_pk) +{ + if (memcmp(moderation->founder_public_sig_key, sig_pk, SIG_PUBLIC_KEY_SIZE) == 0) { + return true; + } + + for (uint16_t i = 0; i < moderation->num_mods; ++i) { + if (memcmp(moderation->mod_list[i], sig_pk, SIG_PUBLIC_KEY_SIZE) == 0) { + return true; + } + } + + return false; +} + +bool mod_list_remove_index(Moderation *moderation, uint16_t index) +{ + if (index >= moderation->num_mods) { + return false; + } + + if ((moderation->num_mods - 1) == 0) { + mod_list_cleanup(moderation); + return true; + } + + --moderation->num_mods; + + if (index != moderation->num_mods) { + memcpy(moderation->mod_list[index], moderation->mod_list[moderation->num_mods], + MOD_LIST_ENTRY_SIZE); + } + + free(moderation->mod_list[moderation->num_mods]); + moderation->mod_list[moderation->num_mods] = nullptr; + + uint8_t **tmp_list = (uint8_t **)realloc(moderation->mod_list, moderation->num_mods * sizeof(uint8_t *)); + + if (tmp_list == nullptr) { + return false; + } + + moderation->mod_list = tmp_list; + + return true; +} + +bool mod_list_remove_entry(Moderation *moderation, const uint8_t *public_sig_key) +{ + if (moderation->num_mods == 0) { + return false; + } + + const int idx = mod_list_index_of_sig_pk(moderation, public_sig_key); + + if (idx == -1) { + return false; + } + + assert(idx <= UINT16_MAX); + + return mod_list_remove_index(moderation, (uint16_t)idx); +} + +bool mod_list_add_entry(Moderation *moderation, const uint8_t *mod_data) +{ + if (moderation->num_mods >= MOD_MAX_NUM_MODERATORS) { + return false; + } + + uint8_t **tmp_list = (uint8_t **)realloc(moderation->mod_list, (moderation->num_mods + 1) * sizeof(uint8_t *)); + + if (tmp_list == nullptr) { + return false; + } + + moderation->mod_list = tmp_list; + + tmp_list[moderation->num_mods] = (uint8_t *)malloc(sizeof(uint8_t) * MOD_LIST_ENTRY_SIZE); + + if (tmp_list[moderation->num_mods] == nullptr) { + return false; + } + + memcpy(tmp_list[moderation->num_mods], mod_data, MOD_LIST_ENTRY_SIZE); + ++moderation->num_mods; + + return true; +} + +void mod_list_cleanup(Moderation *moderation) +{ + free_uint8_t_pointer_array(moderation->mod_list, moderation->num_mods); + moderation->num_mods = 0; + moderation->mod_list = nullptr; +} + +uint16_t sanctions_creds_pack(const Mod_Sanction_Creds *creds, uint8_t *data) +{ + uint16_t packed_len = 0; + + net_pack_u32(data + packed_len, creds->version); + packed_len += sizeof(uint32_t); + memcpy(data + packed_len, creds->hash, MOD_SANCTION_HASH_SIZE); + packed_len += MOD_SANCTION_HASH_SIZE; + net_pack_u16(data + packed_len, creds->checksum); + packed_len += sizeof(uint16_t); + memcpy(data + packed_len, creds->sig_pk, SIG_PUBLIC_KEY_SIZE); + packed_len += SIG_PUBLIC_KEY_SIZE; + memcpy(data + packed_len, creds->sig, SIGNATURE_SIZE); + packed_len += SIGNATURE_SIZE; + + return packed_len; +} + +uint16_t sanctions_list_packed_size(uint16_t num_sanctions) +{ + return MOD_SANCTION_PACKED_SIZE * num_sanctions; +} + +int sanctions_list_pack(uint8_t *data, uint16_t length, const Mod_Sanction *sanctions, uint16_t num_sanctions, + const Mod_Sanction_Creds *creds) +{ + assert(sanctions != nullptr || num_sanctions == 0); + assert(sanctions != nullptr || creds != nullptr); + + uint16_t packed_len = 0; + + for (uint16_t i = 0; i < num_sanctions; ++i) { + if (packed_len + sizeof(uint8_t) + SIG_PUBLIC_KEY_SIZE + TIME_STAMP_SIZE > length) { + return -1; + } + + memcpy(data + packed_len, &sanctions[i].type, sizeof(uint8_t)); + packed_len += sizeof(uint8_t); + memcpy(data + packed_len, sanctions[i].setter_public_sig_key, SIG_PUBLIC_KEY_SIZE); + packed_len += SIG_PUBLIC_KEY_SIZE; + net_pack_u64(data + packed_len, sanctions[i].time_set); + packed_len += TIME_STAMP_SIZE; + + const uint8_t sanctions_type = sanctions[i].type; + + if (sanctions_type == SA_OBSERVER) { + if (packed_len + ENC_PUBLIC_KEY_SIZE > length) { + return -1; + } + + memcpy(data + packed_len, sanctions[i].target_public_enc_key, ENC_PUBLIC_KEY_SIZE); + packed_len += ENC_PUBLIC_KEY_SIZE; + } else { + return -1; + } + + if (packed_len + SIGNATURE_SIZE > length) { + return -1; + } + + /* Signature must be packed last */ + memcpy(data + packed_len, sanctions[i].signature, SIGNATURE_SIZE); + packed_len += SIGNATURE_SIZE; + } + + if (creds == nullptr) { + return packed_len; + } + + if (length < packed_len || length - packed_len < MOD_SANCTIONS_CREDS_SIZE) { + return -1; + } + + const uint16_t cred_len = sanctions_creds_pack(creds, data + packed_len); + + if (cred_len != MOD_SANCTIONS_CREDS_SIZE) { + return -1; + } + + return (int)(packed_len + cred_len); +} + +uint16_t sanctions_creds_unpack(Mod_Sanction_Creds *creds, const uint8_t *data) +{ + uint16_t len_processed = 0; + + net_unpack_u32(data + len_processed, &creds->version); + len_processed += sizeof(uint32_t); + memcpy(creds->hash, data + len_processed, MOD_SANCTION_HASH_SIZE); + len_processed += MOD_SANCTION_HASH_SIZE; + net_unpack_u16(data + len_processed, &creds->checksum); + len_processed += sizeof(uint16_t); + memcpy(creds->sig_pk, data + len_processed, SIG_PUBLIC_KEY_SIZE); + len_processed += SIG_PUBLIC_KEY_SIZE; + memcpy(creds->sig, data + len_processed, SIGNATURE_SIZE); + len_processed += SIGNATURE_SIZE; + + return len_processed; +} + +int sanctions_list_unpack(Mod_Sanction *sanctions, Mod_Sanction_Creds *creds, uint16_t max_sanctions, + const uint8_t *data, uint16_t length, uint16_t *processed_data_len) +{ + uint16_t num = 0; + uint16_t len_processed = 0; + + while (num < max_sanctions && num < MOD_MAX_NUM_SANCTIONS && len_processed < length) { + if (len_processed + sizeof(uint8_t) + SIG_PUBLIC_KEY_SIZE + TIME_STAMP_SIZE > length) { + return -1; + } + + memcpy(&sanctions[num].type, data + len_processed, sizeof(uint8_t)); + len_processed += sizeof(uint8_t); + memcpy(sanctions[num].setter_public_sig_key, data + len_processed, SIG_PUBLIC_KEY_SIZE); + len_processed += SIG_PUBLIC_KEY_SIZE; + net_unpack_u64(data + len_processed, &sanctions[num].time_set); + len_processed += TIME_STAMP_SIZE; + + if (sanctions[num].type == SA_OBSERVER) { + if (len_processed + ENC_PUBLIC_KEY_SIZE > length) { + return -1; + } + + memcpy(sanctions[num].target_public_enc_key, data + len_processed, ENC_PUBLIC_KEY_SIZE); + len_processed += ENC_PUBLIC_KEY_SIZE; + } else { + return -1; + } + + if (len_processed + SIGNATURE_SIZE > length) { + return -1; + } + + memcpy(sanctions[num].signature, data + len_processed, SIGNATURE_SIZE); + len_processed += SIGNATURE_SIZE; + + ++num; + } + + if (length <= len_processed || length - len_processed < MOD_SANCTIONS_CREDS_SIZE) { + if (length != len_processed) { + return -1; + } + + if (processed_data_len != nullptr) { + *processed_data_len = len_processed; + } + + return num; + } + + const uint16_t creds_len = sanctions_creds_unpack(creds, data + len_processed); + + if (creds_len != MOD_SANCTIONS_CREDS_SIZE) { + return -1; + } + + if (processed_data_len != nullptr) { + *processed_data_len = len_processed + creds_len; + } + + return num; +} + + +/** @brief Creates a new sanction list hash and puts it in hash. + * + * The hash is derived from the signature of all entries plus the version number. + * hash must have room for at least MOD_SANCTION_HASH_SIZE bytes. + * + * If num_sanctions is 0 the hash is zeroed. + * + * Return true on success. + */ +non_null(4) nullable(1) +static bool sanctions_list_make_hash(const Mod_Sanction *sanctions, uint32_t new_version, uint16_t num_sanctions, + uint8_t *hash) +{ + if (num_sanctions == 0 || sanctions == nullptr) { + memset(hash, 0, MOD_SANCTION_HASH_SIZE); + return true; + } + + const size_t sig_data_size = num_sanctions * SIGNATURE_SIZE; + const size_t data_buf_size = sig_data_size + sizeof(uint32_t); + + // check for integer overflower + if (data_buf_size < num_sanctions) { + return false; + } + + uint8_t *data = (uint8_t *)malloc(data_buf_size); + + if (data == nullptr) { + return false; + } + + for (uint16_t i = 0; i < num_sanctions; ++i) { + memcpy(&data[i * SIGNATURE_SIZE], sanctions[i].signature, SIGNATURE_SIZE); + } + + memcpy(&data[sig_data_size], &new_version, sizeof(uint32_t)); + crypto_sha256(hash, data, data_buf_size); + + free(data); + + return true; +} + +/** @brief Verifies that sanction contains valid info and was assigned by a current mod or group founder. + * + * Returns true on success. + */ +non_null() +static bool sanctions_list_validate_entry(const Moderation *moderation, const Mod_Sanction *sanction) +{ + if (!mod_list_verify_sig_pk(moderation, sanction->setter_public_sig_key)) { + return false; + } + + if (sanction->type >= SA_INVALID) { + return false; + } + + if (sanction->time_set == 0) { + return false; + } + + uint8_t packed_data[MOD_SANCTION_PACKED_SIZE]; + const int packed_len = sanctions_list_pack(packed_data, sizeof(packed_data), sanction, 1, nullptr); + + if (packed_len <= (int) SIGNATURE_SIZE) { + return false; + } + + return crypto_signature_verify(sanction->signature, packed_data, packed_len - SIGNATURE_SIZE, + sanction->setter_public_sig_key); +} + +non_null() +static uint16_t sanctions_creds_get_checksum(const Mod_Sanction_Creds *creds) +{ + return data_checksum(creds->hash, sizeof(creds->hash)); +} + +non_null() +static void sanctions_creds_set_checksum(Mod_Sanction_Creds *creds) +{ + creds->checksum = sanctions_creds_get_checksum(creds); +} + +bool sanctions_list_make_creds(Moderation *moderation) +{ + const Mod_Sanction_Creds old_creds = moderation->sanctions_creds; + + ++moderation->sanctions_creds.version; + + memcpy(moderation->sanctions_creds.sig_pk, moderation->self_public_sig_key, SIG_PUBLIC_KEY_SIZE); + + uint8_t hash[MOD_SANCTION_HASH_SIZE]; + + if (!sanctions_list_make_hash(moderation->sanctions, moderation->sanctions_creds.version, + moderation->num_sanctions, hash)) { + moderation->sanctions_creds = old_creds; + return false; + } + + memcpy(moderation->sanctions_creds.hash, hash, MOD_SANCTION_HASH_SIZE); + + sanctions_creds_set_checksum(&moderation->sanctions_creds); + + if (!crypto_signature_create(moderation->sanctions_creds.sig, moderation->sanctions_creds.hash, + MOD_SANCTION_HASH_SIZE, moderation->self_secret_sig_key)) { + moderation->sanctions_creds = old_creds; + return false; + } + + return true; +} + +/** @brief Validates sanction list credentials. + * + * Verifies that: + * - the public signature key belongs to a mod or the founder + * - the signature for the hash was made by the owner of the public signature key. + * - the received hash matches our own hash of the new sanctions list + * - the received checksum matches the received hash + * - the new version is >= our current version + * + * Returns true on success. + */ +non_null(1, 3) nullable(2) +static bool sanctions_creds_validate(const Moderation *moderation, const Mod_Sanction *sanctions, + const Mod_Sanction_Creds *creds, uint16_t num_sanctions) +{ + if (!mod_list_verify_sig_pk(moderation, creds->sig_pk)) { + LOGGER_WARNING(moderation->log, "Invalid credentials signature pk"); + return false; + } + + uint8_t hash[MOD_SANCTION_HASH_SIZE]; + + if (!sanctions_list_make_hash(sanctions, creds->version, num_sanctions, hash)) { + return false; + } + + if (memcmp(hash, creds->hash, MOD_SANCTION_HASH_SIZE) != 0) { + LOGGER_WARNING(moderation->log, "Invalid credentials hash"); + return false; + } + + if (creds->checksum != sanctions_creds_get_checksum(creds)) { + LOGGER_WARNING(moderation->log, "Invalid credentials checksum"); + return false; + } + + if (moderation->shared_state_version > 0) { + if ((creds->version < moderation->sanctions_creds.version) + && !(creds->version == 0 && moderation->sanctions_creds.version == UINT32_MAX)) { + LOGGER_WARNING(moderation->log, "Invalid version"); + return false; + } + } + + if (!crypto_signature_verify(creds->sig, hash, MOD_SANCTION_HASH_SIZE, creds->sig_pk)) { + LOGGER_WARNING(moderation->log, "Invalid signature"); + return false; + } + + return true; +} + +bool sanctions_list_check_integrity(const Moderation *moderation, const Mod_Sanction_Creds *creds, + const Mod_Sanction *sanctions, uint16_t num_sanctions) +{ + for (uint16_t i = 0; i < num_sanctions; ++i) { + if (!sanctions_list_validate_entry(moderation, &sanctions[i])) { + LOGGER_WARNING(moderation->log, "Invalid entry"); + return false; + } + } + + return sanctions_creds_validate(moderation, sanctions, creds, num_sanctions); +} + +/** @brief Validates a sanctions list if credentials are supplied. If successful, + * or if no credentials are supplid, assigns new sanctions list and credentials + * to moderation object. + * + * @param moderation The moderation object being operated on. + * @param new_sanctions The sanctions list to validate and assign to moderation object. + * @param new_creds The new sanctions credentials to be assigned to moderation object. + * @param num_sanctions The number of sanctions in the sanctions list. + * + * @retval false if sanctions credentials validation fails. + */ +non_null(1, 2) nullable(3) +static bool sanctions_apply_new(Moderation *moderation, Mod_Sanction *new_sanctions, + const Mod_Sanction_Creds *new_creds, + uint16_t num_sanctions) +{ + if (new_creds != nullptr) { + if (!sanctions_creds_validate(moderation, new_sanctions, new_creds, num_sanctions)) { + LOGGER_WARNING(moderation->log, "Failed to validate credentials"); + return false; + } + + moderation->sanctions_creds = *new_creds; + } + + sanctions_list_cleanup(moderation); + moderation->sanctions = new_sanctions; + moderation->num_sanctions = num_sanctions; + + return true; +} + +/** @brief Returns a copy of the sanctions list. The caller is responsible for freeing the + * memory returned by this function. + */ +non_null() +static Mod_Sanction *sanctions_list_copy(const Mod_Sanction *sanctions, uint16_t num_sanctions) +{ + Mod_Sanction *copy = (Mod_Sanction *)calloc(num_sanctions, sizeof(Mod_Sanction)); + + if (copy == nullptr) { + return nullptr; + } + + memcpy(copy, sanctions, num_sanctions * sizeof(Mod_Sanction)); + + return copy; +} + +/** @brief Removes index-th sanction list entry. + * + * New credentials will be validated if creds is non-null. + * + * Returns true on success. + */ +non_null(1) nullable(3) +static bool sanctions_list_remove_index(Moderation *moderation, uint16_t index, const Mod_Sanction_Creds *creds) +{ + if (index >= moderation->num_sanctions) { + return false; + } + + const uint16_t new_num = moderation->num_sanctions - 1; + + if (new_num == 0) { + if (creds != nullptr) { + if (!sanctions_creds_validate(moderation, nullptr, creds, 0)) { + return false; + } + + moderation->sanctions_creds = *creds; + } + + sanctions_list_cleanup(moderation); + + return true; + } + + /* Operate on a copy of the list in case something goes wrong. */ + Mod_Sanction *sanctions_copy = sanctions_list_copy(moderation->sanctions, moderation->num_sanctions); + + if (sanctions_copy == nullptr) { + return false; + } + + if (index != new_num) { + sanctions_copy[index] = sanctions_copy[new_num]; + } + + Mod_Sanction *new_list = (Mod_Sanction *)realloc(sanctions_copy, new_num * sizeof(Mod_Sanction)); + + if (new_list == nullptr) { + free(sanctions_copy); + return false; + } + + if (!sanctions_apply_new(moderation, new_list, creds, new_num)) { + free(new_list); + return false; + } + + return true; +} + +bool sanctions_list_remove_observer(Moderation *moderation, const uint8_t *public_key, + const Mod_Sanction_Creds *creds) +{ + for (uint16_t i = 0; i < moderation->num_sanctions; ++i) { + const Mod_Sanction *curr_sanction = &moderation->sanctions[i]; + + if (curr_sanction->type != SA_OBSERVER) { + continue; + } + + if (memcmp(public_key, curr_sanction->target_public_enc_key, ENC_PUBLIC_KEY_SIZE) == 0) { + if (!sanctions_list_remove_index(moderation, i, creds)) { + return false; + } + + if (creds == nullptr) { + return sanctions_list_make_creds(moderation); + } + + return true; + } + } + + return false; +} + +bool sanctions_list_is_observer(const Moderation *moderation, const uint8_t *public_key) +{ + for (uint16_t i = 0; i < moderation->num_sanctions; ++i) { + const Mod_Sanction *curr_sanction = &moderation->sanctions[i]; + + if (curr_sanction->type != SA_OBSERVER) { + continue; + } + + if (memcmp(curr_sanction->target_public_enc_key, public_key, ENC_PUBLIC_KEY_SIZE) == 0) { + return true; + } + } + + return false; +} + +bool sanctions_list_entry_exists(const Moderation *moderation, const Mod_Sanction *sanction) +{ + if (sanction->type == SA_OBSERVER) { + return sanctions_list_is_observer(moderation, sanction->target_public_enc_key); + } + + return false; +} + +bool sanctions_list_add_entry(Moderation *moderation, const Mod_Sanction *sanction, const Mod_Sanction_Creds *creds) +{ + if (moderation->num_sanctions >= MOD_MAX_NUM_SANCTIONS) { + LOGGER_WARNING(moderation->log, "num_sanctions %d exceeds maximum", moderation->num_sanctions); + return false; + } + + if (!sanctions_list_validate_entry(moderation, sanction)) { + LOGGER_ERROR(moderation->log, "Failed to validate sanction"); + return false; + } + + if (sanctions_list_entry_exists(moderation, sanction)) { + LOGGER_WARNING(moderation->log, "Attempted to add duplicate sanction"); + return false; + } + + /* Operate on a copy of the list in case something goes wrong. */ + Mod_Sanction *sanctions_copy = nullptr; + + if (moderation->num_sanctions > 0) { + sanctions_copy = sanctions_list_copy(moderation->sanctions, moderation->num_sanctions); + + if (sanctions_copy == nullptr) { + return false; + } + } + + const uint16_t index = moderation->num_sanctions; + Mod_Sanction *new_list = (Mod_Sanction *)realloc(sanctions_copy, (index + 1) * sizeof(Mod_Sanction)); + + if (new_list == nullptr) { + free(sanctions_copy); + return false; + } + + new_list[index] = *sanction; + + if (!sanctions_apply_new(moderation, new_list, creds, index + 1)) { + free(new_list); + return false; + } + + return true; +} + +/** @brief Signs packed sanction data. + * + * This function must be called by the owner of the entry's public_sig_key. + * + * Returns true on success. + */ +non_null() +static bool sanctions_list_sign_entry(const Moderation *moderation, Mod_Sanction *sanction) +{ + uint8_t packed_data[MOD_SANCTION_PACKED_SIZE]; + const int packed_len = sanctions_list_pack(packed_data, sizeof(packed_data), sanction, 1, nullptr); + + if (packed_len <= (int) SIGNATURE_SIZE) { + LOGGER_ERROR(moderation->log, "Failed to pack sanctions list: %d", packed_len); + return false; + } + + return crypto_signature_create(sanction->signature, packed_data, packed_len - SIGNATURE_SIZE, + moderation->self_secret_sig_key); +} + +bool sanctions_list_make_entry(Moderation *moderation, const uint8_t *public_key, Mod_Sanction *sanction, + uint8_t type) +{ + *sanction = (Mod_Sanction) { + 0 + }; + + if (type == SA_OBSERVER) { + memcpy(sanction->target_public_enc_key, public_key, ENC_PUBLIC_KEY_SIZE); + } else { + LOGGER_ERROR(moderation->log, "Tried to create sanction with invalid type: %u", type); + return false; + } + + memcpy(sanction->setter_public_sig_key, moderation->self_public_sig_key, SIG_PUBLIC_KEY_SIZE); + + sanction->time_set = (uint64_t)time(nullptr); + sanction->type = type; + + if (!sanctions_list_sign_entry(moderation, sanction)) { + LOGGER_ERROR(moderation->log, "Failed to sign sanction"); + return false; + } + + if (!sanctions_list_add_entry(moderation, sanction, nullptr)) { + return false; + } + + if (!sanctions_list_make_creds(moderation)) { + LOGGER_ERROR(moderation->log, "Failed to make credentials for new sanction"); + return false; + } + + return true; +} +uint16_t sanctions_list_replace_sig(Moderation *moderation, const uint8_t *public_sig_key) +{ + uint16_t count = 0; + + for (uint16_t i = 0; i < moderation->num_sanctions; ++i) { + if (memcmp(moderation->sanctions[i].setter_public_sig_key, public_sig_key, SIG_PUBLIC_KEY_SIZE) != 0) { + continue; + } + + memcpy(moderation->sanctions[i].setter_public_sig_key, moderation->self_public_sig_key, SIG_PUBLIC_KEY_SIZE); + + if (!sanctions_list_sign_entry(moderation, &moderation->sanctions[i])) { + LOGGER_ERROR(moderation->log, "Failed to sign sanction"); + continue; + } + + ++count; + } + + if (count > 0) { + if (!sanctions_list_make_creds(moderation)) { + return 0; + } + } + + return count; +} + +void sanctions_list_cleanup(Moderation *moderation) +{ + if (moderation->sanctions != nullptr) { + free(moderation->sanctions); + } + + moderation->sanctions = nullptr; + moderation->num_sanctions = 0; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/list.h b/local_pod_repo/toxcore/toxcore/toxcore/list.h new file mode 100644 index 0000000..a7c0e56 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/list.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * Simple struct with functions to create a list which associates ids with data + * -Allows for finding ids associated with data such as IPs or public keys in a short time + * -Should only be used if there are relatively few add/remove calls to the list + */ +#ifndef C_TOXCORE_TOXCORE_LIST_H +#define C_TOXCORE_TOXCORE_LIST_H + +#include +#include + +#include "attributes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BS_List { + uint32_t n; // number of elements + uint32_t capacity; // number of elements memory is allocated for + uint32_t element_size; // size of the elements + uint8_t *data; // array of elements + int *ids; // array of element ids +} BS_List; + +/** @brief Initialize a list. + * + * @param element_size is the size of the elements in the list. + * @param initial_capacity is the number of elements the memory will be initially allocated for. + * + * @retval 1 success + * @retval 0 failure + */ +non_null() +int bs_list_init(BS_List *list, uint32_t element_size, uint32_t initial_capacity); + +/** Free a list initiated with list_init */ +nullable(1) +void bs_list_free(BS_List *list); + +/** @brief Retrieve the id of an element in the list + * + * @retval >=0 id associated with data + * @retval -1 failure + */ +non_null() +int bs_list_find(const BS_List *list, const uint8_t *data); + +/** @brief Add an element with associated id to the list + * + * @retval true success + * @retval false failure (data already in list) + */ +non_null() +bool bs_list_add(BS_List *list, const uint8_t *data, int id); + +/** @brief Remove element from the list + * + * @retval true success + * @retval false failure (element not found or id does not match) + */ +non_null() +bool bs_list_remove(BS_List *list, const uint8_t *data, int id); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/list.m b/local_pod_repo/toxcore/toxcore/toxcore/list.m new file mode 100644 index 0000000..e367487 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/list.m @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * Simple struct with functions to create a list which associates ids with data + * - Allows for finding ids associated with data such as IPs or public keys in a short time + * - Should only be used if there are relatively few add/remove calls to the list + */ +#include "list.h" + +#include +#include +#include + +#include "ccompat.h" + +/** + * Basically, the elements in the list are placed in order so that they can be searched for easily + * - each element is seen as a big-endian integer when ordering them + * - the ids array is maintained so that each id always matches + * - the search algorithm cuts down the time to find the id associated with a piece of data + * at the cost of slow add/remove functions for large lists + * - Starts at `1/2` of the array, compares the element in the array with the data, + * then moves `+/- 1/4` of the array depending on whether the value is greater or lower, + * then `+- 1/8`, etc, until the value is matched or its position where it should be in the array is found + * - some considerations since the array size is never perfect + */ + +static int32_t +list_index(uint32_t i) +{ + return ~i; +} + +/** @brief Find data in list + * + * @retval >=0 index of data in array + * @retval <0 no match, returns index (return value is `list_index(index)`) where + * the data should be inserted + */ +non_null() +static int find(const BS_List *list, const uint8_t *data) +{ + // should work well, but could be improved + if (list->n == 0) { + return list_index(0); + } + + uint32_t i = list->n / 2; // current position in the array + uint32_t delta = i / 2; // how much we move in the array + + if (delta == 0) { + delta = 1; + } + + int d = -1; // used to determine if closest match is found + // closest match is found if we move back to where we have already been + + while (true) { + const int r = memcmp(data, list->data + list->element_size * i, list->element_size); + + if (r == 0) { + return i; + } + + if (r > 0) { + // data is greater + // move down + i += delta; + + if (d == 0 || i == list->n) { + // reached bottom of list, or closest match + return list_index(i); + } + + delta = delta / 2; + + if (delta == 0) { + delta = 1; + d = 1; + } + } else { + // data is smaller + if (d == 1 || i == 0) { + // reached top or list or closest match + return list_index(i); + } + + // move up + i -= delta; + + delta = delta / 2; + + if (delta == 0) { + delta = 1; + d = 0; + } + } + } +} + +/** + * Resizes the list. + * + * @return true on success. + */ +non_null() +static bool resize(BS_List *list, uint32_t new_size) +{ + if (new_size == 0) { + bs_list_free(list); + return true; + } + + uint8_t *data = (uint8_t *)realloc(list->data, list->element_size * new_size); + + if (data == nullptr) { + return false; + } + + list->data = data; + + int *ids = (int *)realloc(list->ids, sizeof(int) * new_size); + + if (ids == nullptr) { + return false; + } + + list->ids = ids; + + return true; +} + + +int bs_list_init(BS_List *list, uint32_t element_size, uint32_t initial_capacity) +{ + // set initial values + list->n = 0; + list->element_size = element_size; + list->capacity = 0; + list->data = nullptr; + list->ids = nullptr; + + if (initial_capacity != 0) { + if (!resize(list, initial_capacity)) { + return 0; + } + } + + list->capacity = initial_capacity; + + return 1; +} + +void bs_list_free(BS_List *list) +{ + if (list == nullptr) { + return; + } + + // free both arrays + free(list->data); + list->data = nullptr; + + free(list->ids); + list->ids = nullptr; +} + +int bs_list_find(const BS_List *list, const uint8_t *data) +{ + const int r = find(list, data); + + // return only -1 and positive values + if (r < 0) { + return -1; + } + + return list->ids[r]; +} + +bool bs_list_add(BS_List *list, const uint8_t *data, int id) +{ + // find where the new element should be inserted + // see: return value of find() + int i = find(list, data); + + if (i >= 0) { + // already in list + return false; + } + + i = ~i; + + // increase the size of the arrays if needed + if (list->n == list->capacity) { + // 1.5 * n + 1 + const uint32_t new_capacity = list->n + list->n / 2 + 1; + + if (!resize(list, new_capacity)) { + return false; + } + + list->capacity = new_capacity; + } + + // insert data to element array + memmove(list->data + (i + 1) * list->element_size, list->data + i * list->element_size, + (list->n - i) * list->element_size); + memcpy(list->data + i * list->element_size, data, list->element_size); + + // insert id to id array + memmove(&list->ids[i + 1], &list->ids[i], (list->n - i) * sizeof(int)); + list->ids[i] = id; + + // increase n + ++list->n; + + return true; +} + +bool bs_list_remove(BS_List *list, const uint8_t *data, int id) +{ + const int i = find(list, data); + + if (i < 0) { + return false; + } + + if (list->ids[i] != id) { + // this should never happen + return false; + } + + // decrease the size of the arrays if needed + if (list->n < list->capacity / 2) { + const uint32_t new_capacity = list->capacity / 2; + + if (resize(list, new_capacity)) { + list->capacity = new_capacity; + } + } + + --list->n; + + memmove(list->data + i * list->element_size, list->data + (i + 1) * list->element_size, + (list->n - i) * list->element_size); + memmove(&list->ids[i], &list->ids[i + 1], (list->n - i) * sizeof(int)); + + return true; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/logger.h b/local_pod_repo/toxcore/toxcore/toxcore/logger.h new file mode 100644 index 0000000..ee5838a --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/logger.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Logger abstraction backed by callbacks for writing. + */ +#ifndef C_TOXCORE_TOXCORE_LOGGER_H +#define C_TOXCORE_TOXCORE_LOGGER_H + +#include + +#include "attributes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef MIN_LOGGER_LEVEL +#define MIN_LOGGER_LEVEL LOGGER_LEVEL_INFO +#endif + +// NOTE: Don't forget to update build system files after modifying the enum. +typedef enum Logger_Level { + LOGGER_LEVEL_TRACE, + LOGGER_LEVEL_DEBUG, + LOGGER_LEVEL_INFO, + LOGGER_LEVEL_WARNING, + LOGGER_LEVEL_ERROR, +} Logger_Level; + +typedef struct Logger Logger; + +typedef void logger_cb(void *context, Logger_Level level, const char *file, int line, + const char *func, const char *message, void *userdata); + +/** + * Creates a new logger with logging disabled (callback is NULL) by default. + */ +Logger *logger_new(void); + +/** + * Frees all resources associated with the logger. + */ +nullable(1) +void logger_kill(Logger *log); + +/** + * Sets the logger callback. Disables logging if set to NULL. + * The context parameter is passed to the callback as first argument. + */ +non_null(1) nullable(2, 3, 4) +void logger_callback_log(Logger *log, logger_cb *function, void *context, void *userdata); + +/** @brief Main write function. If logging is disabled, this does nothing. + * + * If the logger is NULL and `NDEBUG` is not defined, this writes to stderr. + * This behaviour should not be used in production code, but can be useful for + * temporarily debugging a function that does not have a logger available. It's + * essentially `fprintf(stderr, ...)`, but with source location. + * + * If `NDEBUG` is defined, the NULL logger does nothing. + */ +non_null(3, 5, 6) nullable(1) GNU_PRINTF(6, 7) +void logger_write( + const Logger *log, Logger_Level level, const char *file, int line, const char *func, + const char *format, ...); + + +#define LOGGER_WRITE(log, level, ...) \ + do { \ + if (level >= MIN_LOGGER_LEVEL) { \ + logger_write(log, level, __FILE__, __LINE__, __func__, __VA_ARGS__); \ + } \ + } while (0) + +/* To log with an logger */ +#define LOGGER_TRACE(log, ...) LOGGER_WRITE(log, LOGGER_LEVEL_TRACE, __VA_ARGS__) +#define LOGGER_DEBUG(log, ...) LOGGER_WRITE(log, LOGGER_LEVEL_DEBUG, __VA_ARGS__) +#define LOGGER_INFO(log, ...) LOGGER_WRITE(log, LOGGER_LEVEL_INFO, __VA_ARGS__) +#define LOGGER_WARNING(log, ...) LOGGER_WRITE(log, LOGGER_LEVEL_WARNING, __VA_ARGS__) +#define LOGGER_ERROR(log, ...) LOGGER_WRITE(log, LOGGER_LEVEL_ERROR, __VA_ARGS__) + +#define LOGGER_FATAL(log, ...) \ + do { \ + LOGGER_ERROR(log, __VA_ARGS__); \ + abort(); \ + } while (0) + +#define LOGGER_ASSERT(log, cond, ...) \ + do { \ + if (!(cond)) { \ + LOGGER_ERROR(log, "Assertion failed"); \ + LOGGER_FATAL(log, __VA_ARGS__); \ + } \ + } while (0) + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // C_TOXCORE_TOXCORE_LOGGER_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/logger.m b/local_pod_repo/toxcore/toxcore/toxcore/logger.m new file mode 100644 index 0000000..d281a66 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/logger.m @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2015 Tox project. + */ + +/** + * Text logging abstraction. + */ +#include "logger.h" + +#include +#include +#include +#include +#include + +#include "ccompat.h" + +struct Logger { + logger_cb *callback; + void *context; + void *userdata; +}; + +#ifndef NDEBUG +static const char *logger_level_name(Logger_Level level) +{ + switch (level) { + case LOGGER_LEVEL_TRACE: + return "TRACE"; + + case LOGGER_LEVEL_DEBUG: + return "DEBUG"; + + case LOGGER_LEVEL_INFO: + return "INFO"; + + case LOGGER_LEVEL_WARNING: + return "WARNING"; + + case LOGGER_LEVEL_ERROR: + return "ERROR"; + } + + return ""; +} +#endif + +non_null(1, 3, 5, 6) nullable(7) +static void logger_stderr_handler(void *context, Logger_Level level, const char *file, int line, const char *func, + const char *message, void *userdata) +{ +#ifndef NDEBUG + // GL stands for "global logger". + fprintf(stderr, "[GL] %s %s:%d(%s): %s\n", logger_level_name(level), file, line, func, message); + fprintf(stderr, "Default stderr logger triggered; aborting program\n"); + abort(); +#endif +} + +static const Logger logger_stderr = { + logger_stderr_handler, + nullptr, + nullptr, +}; + +/* + * Public Functions + */ + +Logger *logger_new(void) +{ + return (Logger *)calloc(1, sizeof(Logger)); +} + +void logger_kill(Logger *log) +{ + free(log); +} + +void logger_callback_log(Logger *log, logger_cb *function, void *context, void *userdata) +{ + log->callback = function; + log->context = context; + log->userdata = userdata; +} + +void logger_write(const Logger *log, Logger_Level level, const char *file, int line, const char *func, + const char *format, ...) +{ + if (log == nullptr) { + log = &logger_stderr; + } + + if (log->callback == nullptr) { + return; + } + + // Only pass the file name, not the entire file path, for privacy reasons. + // The full path may contain PII of the person compiling toxcore (their + // username and directory layout). + const char *filename = strrchr(file, '/'); + file = filename != nullptr ? filename + 1 : file; +#if defined(_WIN32) || defined(__CYGWIN__) + // On Windows, the path separator *may* be a backslash, so we look for that + // one too. + const char *windows_filename = strrchr(file, '\\'); + file = windows_filename != nullptr ? windows_filename + 1 : file; +#endif + + // Format message + char msg[1024]; + va_list args; + va_start(args, format); + vsnprintf(msg, sizeof(msg), format, args); + va_end(args); + + log->callback(log->context, level, file, line, func, msg, log->userdata); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/mono_time.h b/local_pod_repo/toxcore/toxcore/toxcore/mono_time.h new file mode 100644 index 0000000..5a36724 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/mono_time.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2020 The TokTok team. + * Copyright © 2014 Tox project. + */ +#ifndef C_TOXCORE_TOXCORE_MONO_TIME_H +#define C_TOXCORE_TOXCORE_MONO_TIME_H + +#include +#include + +#include "attributes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The timer portion of the toxcore event loop. + * + * We update the time exactly once per tox_iterate call. Programs built on lower + * level APIs such as the DHT bootstrap node must update the time manually in + * each iteration. + * + * Time is kept per Tox instance, not globally, even though "time" as a concept + * is global. This is because by definition `mono_time` represents the time at + * the start of an iteration, and also by definition the time when all network + * events for the current iteration occurred. This affects mainly two situations: + * + * 1. Two timers started in the same iteration: e.g. two timers set to expire in + * 10 seconds will both expire at the same time, i.e. about 10 seconds later. + * If the time were global, `mono_time` would be a random number that is + * either the time at the start of an iteration, or 1 second later (since the + * timer resolution is 1 second). This can happen when one update happens at + * e.g. 10:00:00.995 and a few milliseconds later a concurrently running + * instance updates the time at 10:00:01.005, making one timer expire a + * second after the other. + * 2. One timer based on an event: if we want to encode a behaviour of a timer + * expiring e.g. 10 seconds after a network event occurred, we simply start a + * timer in the event handler. If a concurrent instance updates the time + * underneath us, it may instead expire 9 seconds after the event. + * + * Both these situations cause incorrect behaviour randomly. In practice, + * toxcore is somewhat robust against strange timer behaviour, but the + * implementation should at least theoretically match the specification. + */ +typedef struct Mono_Time Mono_Time; + +typedef uint64_t mono_time_current_time_cb(void *user_data); + +nullable(1, 2) +Mono_Time *mono_time_new(mono_time_current_time_cb *current_time_callback, void *user_data); + +nullable(1) +void mono_time_free(Mono_Time *mono_time); + +/** + * Update mono_time; subsequent calls to mono_time_get or mono_time_is_timeout + * will use the time at the call to mono_time_update. + */ +non_null() +void mono_time_update(Mono_Time *mono_time); + +/** + * Return unix time since epoch in seconds. + */ +non_null() +uint64_t mono_time_get(const Mono_Time *mono_time); + +/** + * Return true iff timestamp is at least timeout seconds in the past. + */ +non_null() +bool mono_time_is_timeout(const Mono_Time *mono_time, uint64_t timestamp, uint64_t timeout); + +/** + * Return current monotonic time in milliseconds (ms). The starting point is + * unspecified. + */ +non_null() +uint64_t current_time_monotonic(Mono_Time *mono_time); + +/** + * Override implementation of `current_time_monotonic()` (for tests). + * + * The caller is obligated to ensure that `current_time_monotonic()` continues + * to increase monotonically. + */ +non_null(1) nullable(2, 3) +void mono_time_set_current_time_callback(Mono_Time *mono_time, + mono_time_current_time_cb *current_time_callback, void *user_data); + +#ifdef __cplusplus +} +#endif + +#endif // C_TOXCORE_TOXCORE_MONO_TIME_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/mono_time.m b/local_pod_repo/toxcore/toxcore/toxcore/mono_time.m new file mode 100644 index 0000000..df64f22 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/mono_time.m @@ -0,0 +1,265 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2020 The TokTok team. + * Copyright © 2014 Tox project. + */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#if !defined(OS_WIN32) && (defined(_WIN32) || defined(__WIN32__) || defined(WIN32)) +#define OS_WIN32 +#endif + +#include "mono_time.h" + +#ifdef OS_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#ifdef __APPLE__ +#include +#include +#endif + +#ifndef OS_WIN32 +#include +#endif + +#include +#include +#include +#include + +#include "ccompat.h" + +/** don't call into system billions of times for no reason */ +struct Mono_Time { + uint64_t cur_time; + uint64_t base_time; +#ifdef OS_WIN32 + /* protect `last_clock_update` and `last_clock_mono` from concurrent access */ + pthread_mutex_t last_clock_lock; + uint32_t last_clock_mono; + bool last_clock_update; +#endif + +#ifndef ESP_PLATFORM + /* protect `time` from concurrent access */ + pthread_rwlock_t *time_update_lock; +#endif + + mono_time_current_time_cb *current_time_callback; + void *user_data; +}; + +#ifdef OS_WIN32 +non_null() +static uint64_t current_time_monotonic_default(void *user_data) +{ + Mono_Time *const mono_time = (Mono_Time *)user_data; + + /* Must hold mono_time->last_clock_lock here */ + + /* GetTickCount provides only a 32 bit counter, but we can't use + * GetTickCount64 for backwards compatibility, so we handle wraparound + * ourselves. + */ + const uint32_t ticks = GetTickCount(); + + /* the higher 32 bits count the number of wrap arounds */ + uint64_t old_ovf = mono_time->cur_time & ~((uint64_t)UINT32_MAX); + + /* Check if time has decreased because of 32 bit wrap from GetTickCount() */ + if (ticks < mono_time->last_clock_mono) { + /* account for overflow */ + old_ovf += UINT32_MAX + UINT64_C(1); + } + + if (mono_time->last_clock_update) { + mono_time->last_clock_mono = ticks; + mono_time->last_clock_update = false; + } + + /* splice the low and high bits back together */ + return old_ovf + ticks; +} +#else // !OS_WIN32 +static uint64_t timespec_to_u64(struct timespec clock_mono) +{ + return 1000ULL * clock_mono.tv_sec + (clock_mono.tv_nsec / 1000000ULL); +} +#ifdef __APPLE__ +non_null() +static uint64_t current_time_monotonic_default(void *user_data) +{ + struct timespec clock_mono; + clock_serv_t muhclock; + mach_timespec_t machtime; + + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &muhclock); + clock_get_time(muhclock, &machtime); + mach_port_deallocate(mach_task_self(), muhclock); + + clock_mono.tv_sec = machtime.tv_sec; + clock_mono.tv_nsec = machtime.tv_nsec; + return timespec_to_u64(clock_mono); +} +#else // !__APPLE__ +non_null() +static uint64_t current_time_monotonic_default(void *user_data) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + // This assert should always fail. If it does, the fuzzing harness didn't + // override the mono time callback. + assert(user_data == nullptr); +#endif + struct timespec clock_mono; + clock_gettime(CLOCK_MONOTONIC, &clock_mono); + return timespec_to_u64(clock_mono); +} +#endif // !__APPLE__ +#endif // !OS_WIN32 + + +Mono_Time *mono_time_new(mono_time_current_time_cb *current_time_callback, void *user_data) +{ + Mono_Time *mono_time = (Mono_Time *)calloc(1, sizeof(Mono_Time)); + + if (mono_time == nullptr) { + return nullptr; + } + +#ifndef ESP_PLATFORM + mono_time->time_update_lock = (pthread_rwlock_t *)calloc(1, sizeof(pthread_rwlock_t)); + + if (mono_time->time_update_lock == nullptr) { + free(mono_time); + return nullptr; + } + + if (pthread_rwlock_init(mono_time->time_update_lock, nullptr) < 0) { + free(mono_time->time_update_lock); + free(mono_time); + return nullptr; + } +#endif + + mono_time_set_current_time_callback(mono_time, current_time_callback, user_data); + +#ifdef OS_WIN32 + + mono_time->last_clock_mono = 0; + mono_time->last_clock_update = false; + + if (pthread_mutex_init(&mono_time->last_clock_lock, nullptr) < 0) { + free(mono_time->time_update_lock); + free(mono_time); + return nullptr; + } + +#endif + + mono_time->cur_time = 0; +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + // Maximum reproducibility. Never return time = 0. + mono_time->base_time = 1; +#else + mono_time->base_time = (uint64_t)time(nullptr) - (current_time_monotonic(mono_time) / 1000ULL); +#endif + + mono_time_update(mono_time); + + return mono_time; +} + +void mono_time_free(Mono_Time *mono_time) +{ + if (mono_time == nullptr) { + return; + } +#ifdef OS_WIN32 + pthread_mutex_destroy(&mono_time->last_clock_lock); +#endif +#ifndef ESP_PLATFORM + pthread_rwlock_destroy(mono_time->time_update_lock); + free(mono_time->time_update_lock); +#endif + free(mono_time); +} + +void mono_time_update(Mono_Time *mono_time) +{ + uint64_t cur_time = 0; +#ifdef OS_WIN32 + /* we actually want to update the overflow state of mono_time here */ + pthread_mutex_lock(&mono_time->last_clock_lock); + mono_time->last_clock_update = true; +#endif + cur_time = mono_time->current_time_callback(mono_time->user_data) / 1000ULL; + cur_time += mono_time->base_time; +#ifdef OS_WIN32 + pthread_mutex_unlock(&mono_time->last_clock_lock); +#endif + +#ifndef ESP_PLATFORM + pthread_rwlock_wrlock(mono_time->time_update_lock); +#endif + mono_time->cur_time = cur_time; +#ifndef ESP_PLATFORM + pthread_rwlock_unlock(mono_time->time_update_lock); +#endif +} + +uint64_t mono_time_get(const Mono_Time *mono_time) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + // Fuzzing is only single thread for now, no locking needed */ + return mono_time->cur_time; +#else +#ifndef ESP_PLATFORM + pthread_rwlock_rdlock(mono_time->time_update_lock); +#endif + const uint64_t cur_time = mono_time->cur_time; +#ifndef ESP_PLATFORM + pthread_rwlock_unlock(mono_time->time_update_lock); +#endif + return cur_time; +#endif +} + +bool mono_time_is_timeout(const Mono_Time *mono_time, uint64_t timestamp, uint64_t timeout) +{ + return timestamp + timeout <= mono_time_get(mono_time); +} + +void mono_time_set_current_time_callback(Mono_Time *mono_time, + mono_time_current_time_cb *current_time_callback, void *user_data) +{ + if (current_time_callback == nullptr) { + mono_time->current_time_callback = current_time_monotonic_default; + mono_time->user_data = mono_time; + } else { + mono_time->current_time_callback = current_time_callback; + mono_time->user_data = user_data; + } +} + +/** + * Return current monotonic time in milliseconds (ms). The starting point is + * unspecified. + */ +uint64_t current_time_monotonic(Mono_Time *mono_time) +{ + /* For WIN32 we don't want to change overflow state of mono_time here */ +#ifdef OS_WIN32 + /* We don't want to update the overflow state of mono_time here, + * but must protect against other threads */ + pthread_mutex_lock(&mono_time->last_clock_lock); +#endif + const uint64_t cur_time = mono_time->current_time_callback(mono_time->user_data); +#ifdef OS_WIN32 + pthread_mutex_unlock(&mono_time->last_clock_lock); +#endif + return cur_time; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/net_crypto.h b/local_pod_repo/toxcore/toxcore/toxcore/net_crypto.h new file mode 100644 index 0000000..8fbc7bd --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/net_crypto.h @@ -0,0 +1,414 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Functions for the core network crypto. + */ +#ifndef C_TOXCORE_TOXCORE_NET_CRYPTO_H +#define C_TOXCORE_TOXCORE_NET_CRYPTO_H + +#include + +#include "DHT.h" +#include "LAN_discovery.h" +#include "TCP_connection.h" +#include "logger.h" + +/*** Crypto payloads. */ + +/*** Ranges. */ + +/** Packets in this range are reserved for net_crypto events_alloc use. */ +#define PACKET_ID_RANGE_RESERVED_START 0 +#define PACKET_ID_RANGE_RESERVED_END 15 +/** Packets in this range are reserved for Messenger use. */ +#define PACKET_ID_RANGE_LOSSLESS_START 16 +#define PACKET_ID_RANGE_LOSSLESS_NORMAL_START 16 +#define PACKET_ID_RANGE_LOSSLESS_NORMAL_END 159 +/** Packets in this range can be used for anything. */ +#define PACKET_ID_RANGE_LOSSLESS_CUSTOM_START 160 +#define PACKET_ID_RANGE_LOSSLESS_CUSTOM_END 191 +#define PACKET_ID_RANGE_LOSSLESS_END 191 +/** Packets in this range are reserved for AV use. */ +#define PACKET_ID_RANGE_LOSSY_START 192 +#define PACKET_ID_RANGE_LOSSY_AV_START 192 +#define PACKET_ID_RANGE_LOSSY_AV_SIZE 8 +#define PACKET_ID_RANGE_LOSSY_AV_END 199 +/** Packets in this range can be used for anything. */ +#define PACKET_ID_RANGE_LOSSY_CUSTOM_START 200 +#define PACKET_ID_RANGE_LOSSY_CUSTOM_END 254 +#define PACKET_ID_RANGE_LOSSY_END 254 + +/*** Messages. */ + +#define PACKET_ID_PADDING 0 // Denotes padding +#define PACKET_ID_REQUEST 1 // Used to request unreceived packets +#define PACKET_ID_KILL 2 // Used to kill connection + +#define PACKET_ID_ONLINE 24 +#define PACKET_ID_OFFLINE 25 +#define PACKET_ID_NICKNAME 48 +#define PACKET_ID_STATUSMESSAGE 49 +#define PACKET_ID_USERSTATUS 50 +#define PACKET_ID_TYPING 51 +#define PACKET_ID_MESSAGE 64 +#define PACKET_ID_ACTION 65 // PACKET_ID_MESSAGE + MESSAGE_ACTION +#define PACKET_ID_HIGH_LEVEL_ACK 66 // MSG V3 +#define PACKET_ID_MSI 69 // Used by AV to setup calls and etc +#define PACKET_ID_FILE_SENDREQUEST 80 +#define PACKET_ID_FILE_CONTROL 81 +#define PACKET_ID_FILE_DATA 82 +#define PACKET_ID_INVITE_CONFERENCE 96 +#define PACKET_ID_ONLINE_PACKET 97 +#define PACKET_ID_DIRECT_CONFERENCE 98 +#define PACKET_ID_MESSAGE_CONFERENCE 99 +#define PACKET_ID_REJOIN_CONFERENCE 100 +#define PACKET_ID_LOSSY_CONFERENCE 199 + +/** Maximum size of receiving and sending packet buffers. */ +#define CRYPTO_PACKET_BUFFER_SIZE 32768 // Must be a power of 2 + +/** Minimum packet rate per second. */ +#define CRYPTO_PACKET_MIN_RATE 4.0 + +/** Minimum packet queue max length. */ +#define CRYPTO_MIN_QUEUE_LENGTH 64 + +/** Maximum total size of packets that net_crypto sends. */ +#define MAX_CRYPTO_PACKET_SIZE (uint16_t)1400 + +#define CRYPTO_DATA_PACKET_MIN_SIZE (uint16_t)(1 + sizeof(uint16_t) + (sizeof(uint32_t) + sizeof(uint32_t)) + CRYPTO_MAC_SIZE) + +/** Max size of data in packets */ +#define MAX_CRYPTO_DATA_SIZE (uint16_t)(MAX_CRYPTO_PACKET_SIZE - CRYPTO_DATA_PACKET_MIN_SIZE) + +/** Interval in ms between sending cookie request/handshake packets. */ +#define CRYPTO_SEND_PACKET_INTERVAL 1000 + +/** + * The maximum number of times we try to send the cookie request and handshake + * before giving up. + */ +#define MAX_NUM_SENDPACKET_TRIES 8 + +/** The timeout of no received UDP packets before the direct UDP connection is considered dead. */ +#define UDP_DIRECT_TIMEOUT 8 + +#define MAX_TCP_CONNECTIONS 64 +#define MAX_TCP_RELAYS_PEER 4 + +/** All packets will be padded a number of bytes based on this number. */ +#define CRYPTO_MAX_PADDING 8 + +/** + * Base current transfer speed on last CONGESTION_QUEUE_ARRAY_SIZE number of points taken + * at the dT defined in net_crypto.c + */ +#define CONGESTION_QUEUE_ARRAY_SIZE 12 +#define CONGESTION_LAST_SENT_ARRAY_SIZE (CONGESTION_QUEUE_ARRAY_SIZE * 2) + +/** Default connection ping in ms. */ +#define DEFAULT_PING_CONNECTION 1000 +#define DEFAULT_TCP_PING_CONNECTION 500 + +typedef struct Net_Crypto Net_Crypto; + +non_null() const uint8_t *nc_get_self_public_key(const Net_Crypto *c); +non_null() const uint8_t *nc_get_self_secret_key(const Net_Crypto *c); +non_null() TCP_Connections *nc_get_tcp_c(const Net_Crypto *c); +non_null() DHT *nc_get_dht(const Net_Crypto *c); + +typedef struct New_Connection { + IP_Port source; + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The real public key of the peer. */ + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer. */ + uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ + uint8_t peersessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The public key of the peer. */ + uint8_t *cookie; + uint8_t cookie_length; +} New_Connection; + +typedef int connection_status_cb(void *object, int id, bool status, void *userdata); +typedef int connection_data_cb(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); +typedef int connection_lossy_data_cb(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); +typedef void dht_pk_cb(void *data, int32_t number, const uint8_t *dht_public_key, void *userdata); +typedef int new_connection_cb(void *object, const New_Connection *n_c); + +/** @brief Set function to be called when someone requests a new connection to us. + * + * The set function should return -1 on failure and 0 on success. + * + * n_c is only valid for the duration of the function call. + */ +non_null() +void new_connection_handler(Net_Crypto *c, new_connection_cb *new_connection_callback, void *object); + +/** @brief Accept a crypto connection. + * + * return -1 on failure. + * return connection id on success. + */ +non_null() +int accept_crypto_connection(Net_Crypto *c, const New_Connection *n_c); + +/** @brief Create a crypto connection. + * If one to that real public key already exists, return it. + * + * return -1 on failure. + * return connection id on success. + */ +non_null() +int new_crypto_connection(Net_Crypto *c, const uint8_t *real_public_key, const uint8_t *dht_public_key); + +/** @brief Set the direct ip of the crypto connection. + * + * Connected is 0 if we are not sure we are connected to that person, 1 if we are sure. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int set_direct_ip_port(Net_Crypto *c, int crypt_connection_id, const IP_Port *ip_port, bool connected); + +/** @brief Set function to be called when connection with crypt_connection_id goes connects/disconnects. + * + * The set function should return -1 on failure and 0 on success. + * Note that if this function is set, the connection will clear itself on disconnect. + * Object and id will be passed to this function untouched. + * status is 1 if the connection is going online, 0 if it is going offline. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int connection_status_handler(const Net_Crypto *c, int crypt_connection_id, + connection_status_cb *connection_status_callback, void *object, int id); + +/** @brief Set function to be called when connection with crypt_connection_id receives a lossless data packet of length. + * + * The set function should return -1 on failure and 0 on success. + * Object and id will be passed to this function untouched. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int connection_data_handler(const Net_Crypto *c, int crypt_connection_id, + connection_data_cb *connection_data_callback, void *object, int id); + + +/** @brief Set function to be called when connection with crypt_connection_id receives a lossy data packet of length. + * + * The set function should return -1 on failure and 0 on success. + * Object and id will be passed to this function untouched. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int connection_lossy_data_handler(const Net_Crypto *c, int crypt_connection_id, + connection_lossy_data_cb *connection_lossy_data_callback, void *object, int id); + +/** @brief Set the function for this friend that will be callbacked with object and number if + * the friend sends us a different dht public key than we have associated to him. + * + * If this function is called, the connection should be recreated with the new public key. + * + * object and number will be passed as argument to this function. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int nc_dht_pk_callback(const Net_Crypto *c, int crypt_connection_id, + dht_pk_cb *function, void *object, uint32_t number); + +/** + * @return the number of packet slots left in the sendbuffer. + * @retval 0 if failure. + */ +non_null() +uint32_t crypto_num_free_sendqueue_slots(const Net_Crypto *c, int crypt_connection_id); + +/** + * @retval 1 if max speed was reached for this connection (no more data can be physically through the pipe). + * @retval 0 if it wasn't reached. + */ +non_null() +bool max_speed_reached(Net_Crypto *c, int crypt_connection_id); + +/** @brief Sends a lossless cryptopacket. + * + * return -1 if data could not be put in packet queue. + * return positive packet number if data was put into the queue. + * + * The first byte of data must be in the PACKET_ID_RANGE_LOSSLESS. + * + * congestion_control: should congestion control apply to this packet? + */ +non_null() +int64_t write_cryptpacket(Net_Crypto *c, int crypt_connection_id, + const uint8_t *data, uint16_t length, bool congestion_control); + +/** @brief Check if packet_number was received by the other side. + * + * packet_number must be a valid packet number of a packet sent on this connection. + * + * return -1 on failure. + * return 0 on success. + * + * Note: The condition `buffer_end - buffer_start < packet_number - buffer_start` is + * a trick which handles situations `buffer_end >= buffer_start` and + * `buffer_end < buffer_start` (when buffer_end overflowed) both correctly. + * + * It CANNOT be simplified to `packet_number < buffer_start`, as it will fail + * when `buffer_end < buffer_start`. + */ +non_null() +int cryptpacket_received(const Net_Crypto *c, int crypt_connection_id, uint32_t packet_number); + +/** @brief Sends a lossy cryptopacket. + * + * return -1 on failure. + * return 0 on success. + * + * The first byte of data must be in the PACKET_ID_RANGE_LOSSY. + */ +non_null() +int send_lossy_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length); + +/** @brief Add a tcp relay, associating it to a crypt_connection_id. + * + * return 0 if it was added. + * return -1 if it wasn't. + */ +non_null() +int add_tcp_relay_peer(Net_Crypto *c, int crypt_connection_id, const IP_Port *ip_port, + const uint8_t *public_key); + +/** @brief Add a tcp relay to the array. + * + * return 0 if it was added. + * return -1 if it wasn't. + */ +non_null() +int add_tcp_relay(Net_Crypto *c, const IP_Port *ip_port, const uint8_t *public_key); + +/** @brief Return a random TCP connection number for use in send_tcp_onion_request. + * + * TODO(irungentoo): This number is just the index of an array that the elements can + * change without warning. + * + * return TCP connection number on success. + * return -1 on failure. + */ +non_null() +int get_random_tcp_con_number(Net_Crypto *c); + +/** @brief Put IP_Port of a random onion TCP connection in ip_port. + * + * return true on success. + * return false on failure. + */ +non_null() +bool get_random_tcp_conn_ip_port(Net_Crypto *c, IP_Port *ip_port); + +/** @brief Send an onion packet via the TCP relay corresponding to tcp_connections_number. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +int send_tcp_onion_request(Net_Crypto *c, unsigned int tcp_connections_number, + const uint8_t *data, uint16_t length); + +/** + * Send a forward request to the TCP relay with IP_Port tcp_forwarder, + * requesting to forward data via a chain of dht nodes starting with dht_node. + * A chain_length of 0 means that dht_node is the final destination of data. + * + * return 0 on success. + * return -1 on failure. + */ +non_null() +int send_tcp_forward_request(const Logger *logger, Net_Crypto *c, const IP_Port *tcp_forwarder, const IP_Port *dht_node, + const uint8_t *chain_keys, uint16_t chain_length, + const uint8_t *data, uint16_t data_length); + +/** @brief Copy a maximum of num random TCP relays we are connected to to tcp_relays. + * + * NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6. + * + * return number of relays copied to tcp_relays on success. + * return 0 on failure. + */ +non_null() +unsigned int copy_connected_tcp_relays(Net_Crypto *c, Node_format *tcp_relays, uint16_t num); + +/** + * Copy a maximum of `max_num` TCP relays we are connected to starting at the index in the TCP relay array + * for `tcp_c` designated by `idx`. If idx is greater than the array length a modulo operation is performed. + * + * Returns the number of relays successfully copied. + */ +non_null() +uint32_t copy_connected_tcp_relays_index(Net_Crypto *c, Node_format *tcp_relays, uint16_t num, uint32_t idx); + +/** @brief Kill a crypto connection. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int crypto_kill(Net_Crypto *c, int crypt_connection_id); + +/** + * @retval true if connection is valid, false otherwise + * + * sets direct_connected to 1 if connection connects directly to other, 0 if it isn't. + * sets online_tcp_relays to the number of connected tcp relays this connection has. + */ +non_null(1, 3) nullable(4) +bool crypto_connection_status( + const Net_Crypto *c, int crypt_connection_id, bool *direct_connected, uint32_t *online_tcp_relays); + +/** @brief Generate our public and private keys. + * Only call this function the first time the program starts. + */ +non_null() +void new_keys(Net_Crypto *c); + +/** @brief Save the public and private keys to the keys array. + * Length must be CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE. + * + * TODO(irungentoo): Save only secret key. + */ +non_null() +void save_keys(const Net_Crypto *c, uint8_t *keys); + +/** @brief Load the secret key. + * Length must be CRYPTO_SECRET_KEY_SIZE. + */ +non_null() +void load_secret_key(Net_Crypto *c, const uint8_t *sk); + +/** @brief Create new instance of Net_Crypto. + * Sets all the global connection variables to their default values. + */ +non_null() +Net_Crypto *new_net_crypto(const Logger *log, const Random *rng, const Network *ns, Mono_Time *mono_time, DHT *dht, const TCP_Proxy_Info *proxy_info); + +/** return the optimal interval in ms for running do_net_crypto. */ +non_null() +uint32_t crypto_run_interval(const Net_Crypto *c); + +/** Main loop. */ +non_null(1) nullable(2) +void do_net_crypto(Net_Crypto *c, void *userdata); + +nullable(1) +void kill_net_crypto(Net_Crypto *c); + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/net_crypto.m b/local_pod_repo/toxcore/toxcore/toxcore/net_crypto.m new file mode 100644 index 0000000..c34a457 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/net_crypto.m @@ -0,0 +1,3227 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Functions for the core network crypto. + * + * NOTE: This code has to be perfect. We don't mess around with encryption. + */ +#include "net_crypto.h" + +#include +#include +#include + +#include "ccompat.h" +#include "list.h" +#include "mono_time.h" +#include "util.h" + +typedef struct Packet_Data { + uint64_t sent_time; + uint16_t length; + uint8_t data[MAX_CRYPTO_DATA_SIZE]; +} Packet_Data; + +typedef struct Packets_Array { + Packet_Data *buffer[CRYPTO_PACKET_BUFFER_SIZE]; + uint32_t buffer_start; + uint32_t buffer_end; /* packet numbers in array: `{buffer_start, buffer_end)` */ +} Packets_Array; + +typedef enum Crypto_Conn_State { + /* the connection slot is free. This value is 0 so it is valid after + * `crypto_memzero(...)` of the parent struct + */ + CRYPTO_CONN_FREE = 0, + CRYPTO_CONN_NO_CONNECTION, /* the connection is allocated, but not yet used */ + CRYPTO_CONN_COOKIE_REQUESTING, /* we are sending cookie request packets */ + CRYPTO_CONN_HANDSHAKE_SENT, /* we are sending handshake packets */ + /* we are sending handshake packets. + * we have received one from the other, but no data */ + CRYPTO_CONN_NOT_CONFIRMED, + CRYPTO_CONN_ESTABLISHED, /* the connection is established */ +} Crypto_Conn_State; + +typedef struct Crypto_Connection { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The real public key of the peer. */ + uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ + uint8_t sent_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of sent packets. */ + uint8_t sessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* Our public key for this session. */ + uint8_t sessionsecret_key[CRYPTO_SECRET_KEY_SIZE]; /* Our private key for this session. */ + uint8_t peersessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The public key of the peer. */ + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; /* The precomputed shared key from encrypt_precompute. */ + Crypto_Conn_State status; /* See Crypto_Conn_State documentation */ + uint64_t cookie_request_number; /* number used in the cookie request packets for this connection */ + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer */ + + uint8_t *temp_packet; /* Where the cookie request/handshake packet is stored while it is being sent. */ + uint16_t temp_packet_length; + uint64_t temp_packet_sent_time; /* The time at which the last temp_packet was sent in ms. */ + uint32_t temp_packet_num_sent; + + IP_Port ip_portv4; /* The ip and port to contact this guy directly.*/ + IP_Port ip_portv6; + uint64_t direct_lastrecv_timev4; /* The Time at which we last received a direct packet in ms. */ + uint64_t direct_lastrecv_timev6; + + uint64_t last_tcp_sent; /* Time the last TCP packet was sent. */ + + Packets_Array send_array; + Packets_Array recv_array; + + connection_status_cb *connection_status_callback; + void *connection_status_callback_object; + int connection_status_callback_id; + + connection_data_cb *connection_data_callback; + void *connection_data_callback_object; + int connection_data_callback_id; + + connection_lossy_data_cb *connection_lossy_data_callback; + void *connection_lossy_data_callback_object; + int connection_lossy_data_callback_id; + + uint64_t last_request_packet_sent; + uint64_t direct_send_attempt_time; + + uint32_t packet_counter; + double packet_recv_rate; + uint64_t packet_counter_set; + + double packet_send_rate; + uint32_t packets_left; + uint64_t last_packets_left_set; + double last_packets_left_rem; + + double packet_send_rate_requested; + uint32_t packets_left_requested; + uint64_t last_packets_left_requested_set; + double last_packets_left_requested_rem; + + uint32_t last_sendqueue_size[CONGESTION_QUEUE_ARRAY_SIZE]; + uint32_t last_sendqueue_counter; + long signed int last_num_packets_sent[CONGESTION_LAST_SENT_ARRAY_SIZE]; + long signed int last_num_packets_resent[CONGESTION_LAST_SENT_ARRAY_SIZE]; + uint32_t packets_sent; + uint32_t packets_resent; + uint64_t last_congestion_event; + uint64_t rtt_time; + + /* TCP_connection connection_number */ + unsigned int connection_number_tcp; + + bool maximum_speed_reached; + + /* Must be a pointer, because the struct is moved in memory */ + pthread_mutex_t *mutex; + + dht_pk_cb *dht_pk_callback; + void *dht_pk_callback_object; + uint32_t dht_pk_callback_number; +} Crypto_Connection; + +static const Crypto_Connection empty_crypto_connection = {{0}}; + +struct Net_Crypto { + const Logger *log; + const Random *rng; + Mono_Time *mono_time; + const Network *ns; + + DHT *dht; + TCP_Connections *tcp_c; + + Crypto_Connection *crypto_connections; + pthread_mutex_t tcp_mutex; + + pthread_mutex_t connections_mutex; + unsigned int connection_use_counter; + + uint32_t crypto_connections_length; /* Length of connections array. */ + + /* Our public and secret keys. */ + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + /* The secret key used for cookies */ + uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; + + new_connection_cb *new_connection_callback; + void *new_connection_callback_object; + + /* The current optimal sleep time */ + uint32_t current_sleep_time; + + BS_List ip_port_list; +}; + +const uint8_t *nc_get_self_public_key(const Net_Crypto *c) +{ + return c->self_public_key; +} + +const uint8_t *nc_get_self_secret_key(const Net_Crypto *c) +{ + return c->self_secret_key; +} + +TCP_Connections *nc_get_tcp_c(const Net_Crypto *c) +{ + return c->tcp_c; +} + +DHT *nc_get_dht(const Net_Crypto *c) +{ + return c->dht; +} + +non_null() +static bool crypt_connection_id_is_valid(const Net_Crypto *c, int crypt_connection_id) +{ + if ((uint32_t)crypt_connection_id >= c->crypto_connections_length) { + return false; + } + + if (c->crypto_connections == nullptr) { + return false; + } + + const Crypto_Conn_State status = c->crypto_connections[crypt_connection_id].status; + + return status != CRYPTO_CONN_NO_CONNECTION && status != CRYPTO_CONN_FREE; +} + +/** cookie timeout in seconds */ +#define COOKIE_TIMEOUT 15 +#define COOKIE_DATA_LENGTH (uint16_t)(CRYPTO_PUBLIC_KEY_SIZE * 2) +#define COOKIE_CONTENTS_LENGTH (uint16_t)(sizeof(uint64_t) + COOKIE_DATA_LENGTH) +#define COOKIE_LENGTH (uint16_t)(CRYPTO_NONCE_SIZE + COOKIE_CONTENTS_LENGTH + CRYPTO_MAC_SIZE) + +#define COOKIE_REQUEST_PLAIN_LENGTH (uint16_t)(COOKIE_DATA_LENGTH + sizeof(uint64_t)) +#define COOKIE_REQUEST_LENGTH (uint16_t)(1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + COOKIE_REQUEST_PLAIN_LENGTH + CRYPTO_MAC_SIZE) +#define COOKIE_RESPONSE_LENGTH (uint16_t)(1 + CRYPTO_NONCE_SIZE + COOKIE_LENGTH + sizeof(uint64_t) + CRYPTO_MAC_SIZE) + +/** @brief Create a cookie request packet and put it in packet. + * + * dht_public_key is the dht public key of the other + * + * packet must be of size COOKIE_REQUEST_LENGTH or bigger. + * + * @retval -1 on failure. + * @retval COOKIE_REQUEST_LENGTH on success. + */ +non_null() +static int create_cookie_request(const Net_Crypto *c, uint8_t *packet, const uint8_t *dht_public_key, + uint64_t number, uint8_t *shared_key) +{ + uint8_t plain[COOKIE_REQUEST_PLAIN_LENGTH]; + uint8_t padding[CRYPTO_PUBLIC_KEY_SIZE] = {0}; + + memcpy(plain, c->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(plain + CRYPTO_PUBLIC_KEY_SIZE, padding, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(plain + (CRYPTO_PUBLIC_KEY_SIZE * 2), &number, sizeof(uint64_t)); + + dht_get_shared_key_sent(c->dht, shared_key, dht_public_key); + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_nonce(c->rng, nonce); + packet[0] = NET_PACKET_COOKIE_REQUEST; + memcpy(packet + 1, dht_get_self_public_key(c->dht), CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); + const int len = encrypt_data_symmetric(shared_key, nonce, plain, sizeof(plain), + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); + + if (len != COOKIE_REQUEST_PLAIN_LENGTH + CRYPTO_MAC_SIZE) { + return -1; + } + + return 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + len; +} + +/** @brief Create cookie of length COOKIE_LENGTH from bytes of length COOKIE_DATA_LENGTH using encryption_key + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int create_cookie(const Random *rng, const Mono_Time *mono_time, uint8_t *cookie, const uint8_t *bytes, + const uint8_t *encryption_key) +{ + uint8_t contents[COOKIE_CONTENTS_LENGTH]; + const uint64_t temp_time = mono_time_get(mono_time); + memcpy(contents, &temp_time, sizeof(temp_time)); + memcpy(contents + sizeof(temp_time), bytes, COOKIE_DATA_LENGTH); + random_nonce(rng, cookie); + const int len = encrypt_data_symmetric(encryption_key, cookie, contents, sizeof(contents), cookie + CRYPTO_NONCE_SIZE); + + if (len != COOKIE_LENGTH - CRYPTO_NONCE_SIZE) { + return -1; + } + + return 0; +} + +/** @brief Open cookie of length COOKIE_LENGTH to bytes of length COOKIE_DATA_LENGTH using encryption_key + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int open_cookie(const Mono_Time *mono_time, uint8_t *bytes, const uint8_t *cookie, + const uint8_t *encryption_key) +{ + uint8_t contents[COOKIE_CONTENTS_LENGTH]; + const int len = decrypt_data_symmetric(encryption_key, cookie, cookie + CRYPTO_NONCE_SIZE, + COOKIE_LENGTH - CRYPTO_NONCE_SIZE, contents); + + if (len != sizeof(contents)) { + return -1; + } + + uint64_t cookie_time; + memcpy(&cookie_time, contents, sizeof(cookie_time)); + const uint64_t temp_time = mono_time_get(mono_time); + + if (cookie_time + COOKIE_TIMEOUT < temp_time || temp_time < cookie_time) { + return -1; + } + + memcpy(bytes, contents + sizeof(cookie_time), COOKIE_DATA_LENGTH); + return 0; +} + + +/** @brief Create a cookie response packet and put it in packet. + * @param request_plain must be COOKIE_REQUEST_PLAIN_LENGTH bytes. + * @param packet must be of size COOKIE_RESPONSE_LENGTH or bigger. + * + * @retval -1 on failure. + * @retval COOKIE_RESPONSE_LENGTH on success. + */ +non_null() +static int create_cookie_response(const Net_Crypto *c, uint8_t *packet, const uint8_t *request_plain, + const uint8_t *shared_key, const uint8_t *dht_public_key) +{ + uint8_t cookie_plain[COOKIE_DATA_LENGTH]; + memcpy(cookie_plain, request_plain, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + uint8_t plain[COOKIE_LENGTH + sizeof(uint64_t)]; + + if (create_cookie(c->rng, c->mono_time, plain, cookie_plain, c->secret_symmetric_key) != 0) { + return -1; + } + + memcpy(plain + COOKIE_LENGTH, request_plain + COOKIE_DATA_LENGTH, sizeof(uint64_t)); + packet[0] = NET_PACKET_COOKIE_RESPONSE; + random_nonce(c->rng, packet + 1); + const int len = encrypt_data_symmetric(shared_key, packet + 1, plain, sizeof(plain), packet + 1 + CRYPTO_NONCE_SIZE); + + if (len != COOKIE_RESPONSE_LENGTH - (1 + CRYPTO_NONCE_SIZE)) { + return -1; + } + + return COOKIE_RESPONSE_LENGTH; +} + +/** @brief Handle the cookie request packet of length length. + * Put what was in the request in request_plain (must be of size COOKIE_REQUEST_PLAIN_LENGTH) + * Put the key used to decrypt the request into shared_key (of size CRYPTO_SHARED_KEY_SIZE) for use in the response. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int handle_cookie_request(const Net_Crypto *c, uint8_t *request_plain, uint8_t *shared_key, + uint8_t *dht_public_key, const uint8_t *packet, uint16_t length) +{ + if (length != COOKIE_REQUEST_LENGTH) { + return -1; + } + + memcpy(dht_public_key, packet + 1, CRYPTO_PUBLIC_KEY_SIZE); + dht_get_shared_key_sent(c->dht, shared_key, dht_public_key); + const int len = decrypt_data_symmetric(shared_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, COOKIE_REQUEST_PLAIN_LENGTH + CRYPTO_MAC_SIZE, + request_plain); + + if (len != COOKIE_REQUEST_PLAIN_LENGTH) { + return -1; + } + + return 0; +} + +/** Handle the cookie request packet (for raw UDP) */ +non_null(1, 2, 3) nullable(5) +static int udp_handle_cookie_request(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + const Net_Crypto *c = (const Net_Crypto *)object; + uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + + if (handle_cookie_request(c, request_plain, shared_key, dht_public_key, packet, length) != 0) { + return 1; + } + + uint8_t data[COOKIE_RESPONSE_LENGTH]; + + if (create_cookie_response(c, data, request_plain, shared_key, dht_public_key) != sizeof(data)) { + return 1; + } + + if ((uint32_t)sendpacket(dht_get_net(c->dht), source, data, sizeof(data)) != sizeof(data)) { + return 1; + } + + return 0; +} + +/** Handle the cookie request packet (for TCP) */ +non_null() +static int tcp_handle_cookie_request(const Net_Crypto *c, int connections_number, const uint8_t *packet, + uint16_t length) +{ + uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + + if (handle_cookie_request(c, request_plain, shared_key, dht_public_key, packet, length) != 0) { + return -1; + } + + uint8_t data[COOKIE_RESPONSE_LENGTH]; + + if (create_cookie_response(c, data, request_plain, shared_key, dht_public_key) != sizeof(data)) { + return -1; + } + + const int ret = send_packet_tcp_connection(c->tcp_c, connections_number, data, sizeof(data)); + return ret; +} + +/** Handle the cookie request packet (for TCP oob packets) */ +non_null() +static int tcp_oob_handle_cookie_request(const Net_Crypto *c, unsigned int tcp_connections_number, + const uint8_t *dht_public_key, const uint8_t *packet, uint16_t length) +{ + uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t dht_public_key_temp[CRYPTO_PUBLIC_KEY_SIZE]; + + if (handle_cookie_request(c, request_plain, shared_key, dht_public_key_temp, packet, length) != 0) { + return -1; + } + + if (!pk_equal(dht_public_key, dht_public_key_temp)) { + return -1; + } + + uint8_t data[COOKIE_RESPONSE_LENGTH]; + + if (create_cookie_response(c, data, request_plain, shared_key, dht_public_key) != sizeof(data)) { + return -1; + } + + const int ret = tcp_send_oob_packet(c->tcp_c, tcp_connections_number, dht_public_key, data, sizeof(data)); + return ret; +} + +/** @brief Handle a cookie response packet of length encrypted with shared_key. + * put the cookie in the response in cookie + * + * @param cookie must be of length COOKIE_LENGTH. + * + * @retval -1 on failure. + * @retval COOKIE_LENGTH on success. + */ +non_null() +static int handle_cookie_response(uint8_t *cookie, uint64_t *number, + const uint8_t *packet, uint16_t length, + const uint8_t *shared_key) +{ + if (length != COOKIE_RESPONSE_LENGTH) { + return -1; + } + + uint8_t plain[COOKIE_LENGTH + sizeof(uint64_t)]; + const int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE, + length - (1 + CRYPTO_NONCE_SIZE), plain); + + if (len != sizeof(plain)) { + return -1; + } + + memcpy(cookie, plain, COOKIE_LENGTH); + memcpy(number, plain + COOKIE_LENGTH, sizeof(uint64_t)); + return COOKIE_LENGTH; +} + +#define HANDSHAKE_PACKET_LENGTH (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH + CRYPTO_MAC_SIZE) + +/** @brief Create a handshake packet and put it in packet. + * @param cookie must be COOKIE_LENGTH bytes. + * @param packet must be of size HANDSHAKE_PACKET_LENGTH or bigger. + * + * @retval -1 on failure. + * @retval HANDSHAKE_PACKET_LENGTH on success. + */ +non_null() +static int create_crypto_handshake(const Net_Crypto *c, uint8_t *packet, const uint8_t *cookie, const uint8_t *nonce, + const uint8_t *session_pk, const uint8_t *peer_real_pk, const uint8_t *peer_dht_pubkey) +{ + uint8_t plain[CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH]; + memcpy(plain, nonce, CRYPTO_NONCE_SIZE); + memcpy(plain + CRYPTO_NONCE_SIZE, session_pk, CRYPTO_PUBLIC_KEY_SIZE); + crypto_sha512(plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, cookie, COOKIE_LENGTH); + uint8_t cookie_plain[COOKIE_DATA_LENGTH]; + memcpy(cookie_plain, peer_real_pk, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, peer_dht_pubkey, CRYPTO_PUBLIC_KEY_SIZE); + + if (create_cookie(c->rng, c->mono_time, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE, + cookie_plain, c->secret_symmetric_key) != 0) { + return -1; + } + + random_nonce(c->rng, packet + 1 + COOKIE_LENGTH); + const int len = encrypt_data(peer_real_pk, c->self_secret_key, packet + 1 + COOKIE_LENGTH, plain, sizeof(plain), + packet + 1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE); + + if (len != HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE)) { + return -1; + } + + packet[0] = NET_PACKET_CRYPTO_HS; + memcpy(packet + 1, cookie, COOKIE_LENGTH); + + return HANDSHAKE_PACKET_LENGTH; +} + +/** @brief Handle a crypto handshake packet of length. + * put the nonce contained in the packet in nonce, + * the session public key in session_pk + * the real public key of the peer in peer_real_pk + * the dht public key of the peer in dht_public_key and + * the cookie inside the encrypted part of the packet in cookie. + * + * if expected_real_pk isn't NULL it denotes the real public key + * the packet should be from. + * + * nonce must be at least CRYPTO_NONCE_SIZE + * session_pk must be at least CRYPTO_PUBLIC_KEY_SIZE + * peer_real_pk must be at least CRYPTO_PUBLIC_KEY_SIZE + * cookie must be at least COOKIE_LENGTH + * + * @retval false on failure. + * @retval true on success. + */ +non_null(1, 2, 3, 4, 5, 6, 7) nullable(9) +static bool handle_crypto_handshake(const Net_Crypto *c, uint8_t *nonce, uint8_t *session_pk, uint8_t *peer_real_pk, + uint8_t *dht_public_key, uint8_t *cookie, const uint8_t *packet, uint16_t length, const uint8_t *expected_real_pk) +{ + if (length != HANDSHAKE_PACKET_LENGTH) { + return false; + } + + uint8_t cookie_plain[COOKIE_DATA_LENGTH]; + + if (open_cookie(c->mono_time, cookie_plain, packet + 1, c->secret_symmetric_key) != 0) { + return false; + } + + if (expected_real_pk != nullptr && !pk_equal(cookie_plain, expected_real_pk)) { + return false; + } + + uint8_t cookie_hash[CRYPTO_SHA512_SIZE]; + crypto_sha512(cookie_hash, packet + 1, COOKIE_LENGTH); + + uint8_t plain[CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH]; + const int len = decrypt_data(cookie_plain, c->self_secret_key, packet + 1 + COOKIE_LENGTH, + packet + 1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE, + HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE), plain); + + if (len != sizeof(plain)) { + return false; + } + + if (!crypto_sha512_eq(cookie_hash, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE)) { + return false; + } + + memcpy(nonce, plain, CRYPTO_NONCE_SIZE); + memcpy(session_pk, plain + CRYPTO_NONCE_SIZE, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(cookie, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE, COOKIE_LENGTH); + memcpy(peer_real_pk, cookie_plain, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(dht_public_key, cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); + return true; +} + + +non_null() +static Crypto_Connection *get_crypto_connection(const Net_Crypto *c, int crypt_connection_id) +{ + if (!crypt_connection_id_is_valid(c, crypt_connection_id)) { + return nullptr; + } + + return &c->crypto_connections[crypt_connection_id]; +} + + +/** @brief Associate an ip_port to a connection. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int add_ip_port_connection(Net_Crypto *c, int crypt_connection_id, const IP_Port *ip_port) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + if (net_family_is_ipv4(ip_port->ip.family)) { + if (!ipport_equal(ip_port, &conn->ip_portv4) && !ip_is_lan(&conn->ip_portv4.ip)) { + if (!bs_list_add(&c->ip_port_list, (const uint8_t *)ip_port, crypt_connection_id)) { + return -1; + } + + bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv4, crypt_connection_id); + conn->ip_portv4 = *ip_port; + return 0; + } + } else if (net_family_is_ipv6(ip_port->ip.family)) { + if (!ipport_equal(ip_port, &conn->ip_portv6)) { + if (!bs_list_add(&c->ip_port_list, (const uint8_t *)ip_port, crypt_connection_id)) { + return -1; + } + + bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv6, crypt_connection_id); + conn->ip_portv6 = *ip_port; + return 0; + } + } + + return -1; +} + +/** @brief Return the IP_Port that should be used to send packets to the other peer. + * + * @retval IP_Port with family 0 on failure. + * @return IP_Port on success. + */ +non_null() +static IP_Port return_ip_port_connection(const Net_Crypto *c, int crypt_connection_id) +{ + const IP_Port empty = {{{0}}}; + + const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return empty; + } + + const uint64_t current_time = mono_time_get(c->mono_time); + bool v6 = false; + bool v4 = false; + + if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev4) > current_time) { + v4 = true; + } + + if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev6) > current_time) { + v6 = true; + } + + /* Prefer IP_Ports which haven't timed out to those which have. + * To break ties, prefer ipv4 lan, then ipv6, then non-lan ipv4. + */ + if (v4 && ip_is_lan(&conn->ip_portv4.ip)) { + return conn->ip_portv4; + } + + if (v6 && net_family_is_ipv6(conn->ip_portv6.ip.family)) { + return conn->ip_portv6; + } + + if (v4 && net_family_is_ipv4(conn->ip_portv4.ip.family)) { + return conn->ip_portv4; + } + + if (ip_is_lan(&conn->ip_portv4.ip)) { + return conn->ip_portv4; + } + + if (net_family_is_ipv6(conn->ip_portv6.ip.family)) { + return conn->ip_portv6; + } + + if (net_family_is_ipv4(conn->ip_portv4.ip.family)) { + return conn->ip_portv4; + } + + return empty; +} + +/** @brief Sends a packet to the peer using the fastest route. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int send_packet_to(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length) +{ +// TODO(irungentoo): TCP, etc... + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + bool direct_send_attempt = false; + + pthread_mutex_lock(conn->mutex); + IP_Port ip_port = return_ip_port_connection(c, crypt_connection_id); + + // TODO(irungentoo): on bad networks, direct connections might not last indefinitely. + if (!net_family_is_unspec(ip_port.ip.family)) { + bool direct_connected = false; + + // FIXME(sudden6): handle return value + crypto_connection_status(c, crypt_connection_id, &direct_connected, nullptr); + + if (direct_connected) { + if ((uint32_t)sendpacket(dht_get_net(c->dht), &ip_port, data, length) == length) { + pthread_mutex_unlock(conn->mutex); + return 0; + } + + pthread_mutex_unlock(conn->mutex); + LOGGER_WARNING(c->log, "sending packet of length %d failed", length); + return -1; + } + + // TODO(irungentoo): a better way of sending packets directly to confirm the others ip. + const uint64_t current_time = mono_time_get(c->mono_time); + + if ((((UDP_DIRECT_TIMEOUT / 2) + conn->direct_send_attempt_time) < current_time && length < 96) + || data[0] == NET_PACKET_COOKIE_REQUEST || data[0] == NET_PACKET_CRYPTO_HS) { + if ((uint32_t)sendpacket(dht_get_net(c->dht), &ip_port, data, length) == length) { + direct_send_attempt = true; + conn->direct_send_attempt_time = mono_time_get(c->mono_time); + } + } + } + + pthread_mutex_unlock(conn->mutex); + pthread_mutex_lock(&c->tcp_mutex); + const int ret = send_packet_tcp_connection(c->tcp_c, conn->connection_number_tcp, data, length); + pthread_mutex_unlock(&c->tcp_mutex); + + pthread_mutex_lock(conn->mutex); + + if (ret == 0) { + conn->last_tcp_sent = current_time_monotonic(c->mono_time); + } + + pthread_mutex_unlock(conn->mutex); + + if (direct_send_attempt) { + return 0; + } + + return ret; +} + +/*** START: Array Related functions */ + + +/** @brief Return number of packets in array + * Note that holes are counted too. + */ +non_null() +static uint32_t num_packets_array(const Packets_Array *array) +{ + return array->buffer_end - array->buffer_start; +} + +/** @brief Add data with packet number to array. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int add_data_to_buffer(Packets_Array *array, uint32_t number, const Packet_Data *data) +{ + if (number - array->buffer_start >= CRYPTO_PACKET_BUFFER_SIZE) { + return -1; + } + + const uint32_t num = number % CRYPTO_PACKET_BUFFER_SIZE; + + if (array->buffer[num] != nullptr) { + return -1; + } + + Packet_Data *new_d = (Packet_Data *)calloc(1, sizeof(Packet_Data)); + + if (new_d == nullptr) { + return -1; + } + + *new_d = *data; + array->buffer[num] = new_d; + + if (number - array->buffer_start >= num_packets_array(array)) { + array->buffer_end = number + 1; + } + + return 0; +} + +/** @brief Get pointer of data with packet number. + * + * @retval -1 on failure. + * @retval 0 if data at number is empty. + * @retval 1 if data pointer was put in data. + */ +non_null() +static int get_data_pointer(const Packets_Array *array, Packet_Data **data, uint32_t number) +{ + const uint32_t num_spots = num_packets_array(array); + + if (array->buffer_end - number > num_spots || number - array->buffer_start >= num_spots) { + return -1; + } + + const uint32_t num = number % CRYPTO_PACKET_BUFFER_SIZE; + + if (array->buffer[num] == nullptr) { + return 0; + } + + *data = array->buffer[num]; + return 1; +} + +/** @brief Add data to end of array. + * + * @retval -1 on failure. + * @return packet number on success. + */ +non_null() +static int64_t add_data_end_of_buffer(const Logger *logger, Packets_Array *array, const Packet_Data *data) +{ + const uint32_t num_spots = num_packets_array(array); + + if (num_spots >= CRYPTO_PACKET_BUFFER_SIZE) { + LOGGER_WARNING(logger, "crypto packet buffer size exceeded; rejecting packet of length %d", data->length); + return -1; + } + + Packet_Data *new_d = (Packet_Data *)calloc(1, sizeof(Packet_Data)); + + if (new_d == nullptr) { + LOGGER_ERROR(logger, "packet data allocation failed"); + return -1; + } + + *new_d = *data; + const uint32_t id = array->buffer_end; + array->buffer[id % CRYPTO_PACKET_BUFFER_SIZE] = new_d; + ++array->buffer_end; + return id; +} + +/** @brief Read data from beginning of array. + * + * @retval -1 on failure. + * @return packet number on success. + */ +non_null() +static int64_t read_data_beg_buffer(Packets_Array *array, Packet_Data *data) +{ + if (array->buffer_end == array->buffer_start) { + return -1; + } + + const uint32_t num = array->buffer_start % CRYPTO_PACKET_BUFFER_SIZE; + + if (array->buffer[num] == nullptr) { + return -1; + } + + *data = *array->buffer[num]; + const uint32_t id = array->buffer_start; + ++array->buffer_start; + free(array->buffer[num]); + array->buffer[num] = nullptr; + return id; +} + +/** @brief Delete all packets in array before number (but not number) + * + * @retval -1 on failure. + * @retval 0 on success + */ +non_null() +static int clear_buffer_until(Packets_Array *array, uint32_t number) +{ + const uint32_t num_spots = num_packets_array(array); + + if (array->buffer_end - number >= num_spots || number - array->buffer_start > num_spots) { + return -1; + } + + uint32_t i; + + for (i = array->buffer_start; i != number; ++i) { + const uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; + + if (array->buffer[num] != nullptr) { + free(array->buffer[num]); + array->buffer[num] = nullptr; + } + } + + array->buffer_start = i; + return 0; +} + +non_null() +static int clear_buffer(Packets_Array *array) +{ + uint32_t i; + + for (i = array->buffer_start; i != array->buffer_end; ++i) { + const uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; + + if (array->buffer[num] != nullptr) { + free(array->buffer[num]); + array->buffer[num] = nullptr; + } + } + + array->buffer_start = i; + return 0; +} + +/** @brief Set array buffer end to number. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int set_buffer_end(Packets_Array *array, uint32_t number) +{ + if (number - array->buffer_start > CRYPTO_PACKET_BUFFER_SIZE) { + return -1; + } + + if (number - array->buffer_end > CRYPTO_PACKET_BUFFER_SIZE) { + return -1; + } + + array->buffer_end = number; + return 0; +} + +/** + * @brief Create a packet request packet from recv_array and send_buffer_end into + * data of length. + * + * @retval -1 on failure. + * @return length of packet on success. + */ +non_null() +static int generate_request_packet(uint8_t *data, uint16_t length, const Packets_Array *recv_array) +{ + if (length == 0) { + return -1; + } + + data[0] = PACKET_ID_REQUEST; + + uint16_t cur_len = 1; + + if (recv_array->buffer_start == recv_array->buffer_end) { + return cur_len; + } + + if (length <= cur_len) { + return cur_len; + } + + uint32_t n = 1; + + for (uint32_t i = recv_array->buffer_start; i != recv_array->buffer_end; ++i) { + const uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; + + if (recv_array->buffer[num] == nullptr) { + data[cur_len] = n; + n = 0; + ++cur_len; + + if (length <= cur_len) { + return cur_len; + } + } else if (n == 255) { + data[cur_len] = 0; + n = 0; + ++cur_len; + + if (length <= cur_len) { + return cur_len; + } + } + + ++n; + } + + return cur_len; +} + +/** @brief Handle a request data packet. + * Remove all the packets the other received from the array. + * + * @retval -1 on failure. + * @return number of requested packets on success. + */ +non_null() +static int handle_request_packet(Mono_Time *mono_time, Packets_Array *send_array, + const uint8_t *data, uint16_t length, + uint64_t *latest_send_time, uint64_t rtt_time) +{ + if (length == 0) { + return -1; + } + + if (data[0] != PACKET_ID_REQUEST) { + return -1; + } + + if (length == 1) { + return 0; + } + + ++data; + --length; + + uint32_t n = 1; + uint32_t requested = 0; + + const uint64_t temp_time = current_time_monotonic(mono_time); + uint64_t l_sent_time = 0; + + for (uint32_t i = send_array->buffer_start; i != send_array->buffer_end; ++i) { + if (length == 0) { + break; + } + + const uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; + + if (n == data[0]) { + if (send_array->buffer[num] != nullptr) { + const uint64_t sent_time = send_array->buffer[num]->sent_time; + + if ((sent_time + rtt_time) < temp_time) { + send_array->buffer[num]->sent_time = 0; + } + } + + ++data; + --length; + n = 0; + ++requested; + } else { + if (send_array->buffer[num] != nullptr) { + l_sent_time = max_u64(l_sent_time, send_array->buffer[num]->sent_time); + + free(send_array->buffer[num]); + send_array->buffer[num] = nullptr; + } + } + + if (n == 255) { + n = 1; + + if (data[0] != 0) { + return -1; + } + + ++data; + --length; + } else { + ++n; + } + } + + *latest_send_time = max_u64(*latest_send_time, l_sent_time); + + return requested; +} + +/** END: Array Related functions */ + +#define MAX_DATA_DATA_PACKET_SIZE (MAX_CRYPTO_PACKET_SIZE - (1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE)) + +/** @brief Creates and sends a data packet to the peer using the fastest route. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int send_data_packet(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length) +{ + const uint16_t max_length = MAX_CRYPTO_PACKET_SIZE - (1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE); + + if (length == 0 || length > max_length) { + LOGGER_ERROR(c->log, "zero-length or too large data packet: %d (max: %d)", length, max_length); + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + LOGGER_ERROR(c->log, "connection id %d not found", crypt_connection_id); + return -1; + } + + pthread_mutex_lock(conn->mutex); + const uint16_t packet_size = 1 + sizeof(uint16_t) + length + CRYPTO_MAC_SIZE; + VLA(uint8_t, packet, packet_size); + packet[0] = NET_PACKET_CRYPTO_DATA; + memcpy(packet + 1, conn->sent_nonce + (CRYPTO_NONCE_SIZE - sizeof(uint16_t)), sizeof(uint16_t)); + const int len = encrypt_data_symmetric(conn->shared_key, conn->sent_nonce, data, length, packet + 1 + sizeof(uint16_t)); + + if (len + 1 + sizeof(uint16_t) != packet_size) { + LOGGER_ERROR(c->log, "encryption failed: %d", len); + pthread_mutex_unlock(conn->mutex); + return -1; + } + + increment_nonce(conn->sent_nonce); + pthread_mutex_unlock(conn->mutex); + + return send_packet_to(c, crypt_connection_id, packet, SIZEOF_VLA(packet)); +} + +/** @brief Creates and sends a data packet with buffer_start and num to the peer using the fastest route. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int send_data_packet_helper(Net_Crypto *c, int crypt_connection_id, uint32_t buffer_start, uint32_t num, + const uint8_t *data, uint16_t length) +{ + if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { + LOGGER_ERROR(c->log, "zero-length or too large data packet: %d (max: %d)", length, MAX_CRYPTO_PACKET_SIZE); + return -1; + } + + num = net_htonl(num); + buffer_start = net_htonl(buffer_start); + const uint16_t padding_length = (MAX_CRYPTO_DATA_SIZE - length) % CRYPTO_MAX_PADDING; + VLA(uint8_t, packet, sizeof(uint32_t) + sizeof(uint32_t) + padding_length + length); + memcpy(packet, &buffer_start, sizeof(uint32_t)); + memcpy(packet + sizeof(uint32_t), &num, sizeof(uint32_t)); + memset(packet + (sizeof(uint32_t) * 2), PACKET_ID_PADDING, padding_length); + memcpy(packet + (sizeof(uint32_t) * 2) + padding_length, data, length); + + return send_data_packet(c, crypt_connection_id, packet, SIZEOF_VLA(packet)); +} + +non_null() +static int reset_max_speed_reached(Net_Crypto *c, int crypt_connection_id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + /* If last packet send failed, try to send packet again. + * If sending it fails we won't be able to send the new packet. */ + if (conn->maximum_speed_reached) { + Packet_Data *dt = nullptr; + const uint32_t packet_num = conn->send_array.buffer_end - 1; + const int ret = get_data_pointer(&conn->send_array, &dt, packet_num); + + if (ret == 1 && dt->sent_time == 0) { + if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, + dt->data, dt->length) != 0) { + return -1; + } + + dt->sent_time = current_time_monotonic(c->mono_time); + } + + conn->maximum_speed_reached = false; + } + + return 0; +} + +/** + * @retval -1 if data could not be put in packet queue. + * @return positive packet number if data was put into the queue. + */ +non_null() +static int64_t send_lossless_packet(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length, + bool congestion_control) +{ + if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { + LOGGER_ERROR(c->log, "rejecting too large (or empty) packet of size %d on crypt connection %d", length, + crypt_connection_id); + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + /* If last packet send failed, try to send packet again. + * If sending it fails we won't be able to send the new packet. */ + reset_max_speed_reached(c, crypt_connection_id); + + if (conn->maximum_speed_reached && congestion_control) { + LOGGER_INFO(c->log, "congestion control: maximum speed reached on crypt connection %d", crypt_connection_id); + return -1; + } + + Packet_Data dt; + dt.sent_time = 0; + dt.length = length; + memcpy(dt.data, data, length); + pthread_mutex_lock(conn->mutex); + const int64_t packet_num = add_data_end_of_buffer(c->log, &conn->send_array, &dt); + pthread_mutex_unlock(conn->mutex); + + if (packet_num == -1) { + return -1; + } + + if (!congestion_control && conn->maximum_speed_reached) { + return packet_num; + } + + if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, data, length) == 0) { + Packet_Data *dt1 = nullptr; + + if (get_data_pointer(&conn->send_array, &dt1, packet_num) == 1) { + dt1->sent_time = current_time_monotonic(c->mono_time); + } + } else { + conn->maximum_speed_reached = true; + LOGGER_DEBUG(c->log, "send_data_packet failed (packet_num = %ld)", (long)packet_num); + } + + return packet_num; +} + +/** + * @brief Get the lowest 2 bytes from the nonce and convert + * them to host byte format before returning them. + */ +non_null() +static uint16_t get_nonce_uint16(const uint8_t *nonce) +{ + uint16_t num; + memcpy(&num, nonce + (CRYPTO_NONCE_SIZE - sizeof(uint16_t)), sizeof(uint16_t)); + return net_ntohs(num); +} + +#define DATA_NUM_THRESHOLD 21845 + +/** @brief Handle a data packet. + * Decrypt packet of length and put it into data. + * data must be at least MAX_DATA_DATA_PACKET_SIZE big. + * + * @retval -1 on failure. + * @return length of data on success. + */ +non_null() +static int handle_data_packet(const Net_Crypto *c, int crypt_connection_id, uint8_t *data, const uint8_t *packet, + uint16_t length) +{ + const uint16_t crypto_packet_overhead = 1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE; + + if (length <= crypto_packet_overhead || length > MAX_CRYPTO_PACKET_SIZE) { + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + memcpy(nonce, conn->recv_nonce, CRYPTO_NONCE_SIZE); + const uint16_t num_cur_nonce = get_nonce_uint16(nonce); + uint16_t num; + net_unpack_u16(packet + 1, &num); + const uint16_t diff = num - num_cur_nonce; + increment_nonce_number(nonce, diff); + const int len = decrypt_data_symmetric(conn->shared_key, nonce, packet + 1 + sizeof(uint16_t), + length - (1 + sizeof(uint16_t)), data); + + if ((unsigned int)len != length - crypto_packet_overhead) { + return -1; + } + + if (diff > DATA_NUM_THRESHOLD * 2) { + increment_nonce_number(conn->recv_nonce, DATA_NUM_THRESHOLD); + } + + return len; +} + +/** @brief Send a request packet. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int send_request_packet(Net_Crypto *c, int crypt_connection_id) +{ + const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + uint8_t data[MAX_CRYPTO_DATA_SIZE]; + const int len = generate_request_packet(data, sizeof(data), &conn->recv_array); + + if (len == -1) { + return -1; + } + + return send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, conn->send_array.buffer_end, data, + len); +} + +/** @brief Send up to max num previously requested data packets. + * + * @retval -1 on failure. + * @return number of packets sent on success. + */ +non_null() +static int send_requested_packets(Net_Crypto *c, int crypt_connection_id, uint32_t max_num) +{ + if (max_num == 0) { + return -1; + } + + const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + const uint64_t temp_time = current_time_monotonic(c->mono_time); + const uint32_t array_size = num_packets_array(&conn->send_array); + uint32_t num_sent = 0; + + for (uint32_t i = 0; i < array_size; ++i) { + Packet_Data *dt; + const uint32_t packet_num = i + conn->send_array.buffer_start; + const int ret = get_data_pointer(&conn->send_array, &dt, packet_num); + + if (ret == -1) { + return -1; + } + + if (ret == 0) { + continue; + } + + if (dt->sent_time != 0) { + continue; + } + + if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, dt->data, + dt->length) == 0) { + dt->sent_time = temp_time; + ++num_sent; + } + + if (num_sent >= max_num) { + break; + } + } + + return num_sent; +} + + +/** @brief Add a new temp packet to send repeatedly. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int new_temp_packet(const Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length) +{ + if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + uint8_t *temp_packet = (uint8_t *)malloc(length); + + if (temp_packet == nullptr) { + return -1; + } + + if (conn->temp_packet != nullptr) { + free(conn->temp_packet); + } + + memcpy(temp_packet, packet, length); + conn->temp_packet = temp_packet; + conn->temp_packet_length = length; + conn->temp_packet_sent_time = 0; + conn->temp_packet_num_sent = 0; + return 0; +} + +/** @brief Clear the temp packet. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int clear_temp_packet(const Net_Crypto *c, int crypt_connection_id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + if (conn->temp_packet != nullptr) { + free(conn->temp_packet); + } + + conn->temp_packet = nullptr; + conn->temp_packet_length = 0; + conn->temp_packet_sent_time = 0; + conn->temp_packet_num_sent = 0; + return 0; +} + + +/** @brief Send the temp packet. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int send_temp_packet(Net_Crypto *c, int crypt_connection_id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + if (conn->temp_packet == nullptr) { + return -1; + } + + if (send_packet_to(c, crypt_connection_id, conn->temp_packet, conn->temp_packet_length) != 0) { + return -1; + } + + conn->temp_packet_sent_time = current_time_monotonic(c->mono_time); + ++conn->temp_packet_num_sent; + return 0; +} + +/** @brief Create a handshake packet and set it as a temp packet. + * @param cookie must be COOKIE_LENGTH. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int create_send_handshake(Net_Crypto *c, int crypt_connection_id, const uint8_t *cookie, + const uint8_t *dht_public_key) +{ + const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + uint8_t handshake_packet[HANDSHAKE_PACKET_LENGTH]; + + if (create_crypto_handshake(c, handshake_packet, cookie, conn->sent_nonce, conn->sessionpublic_key, + conn->public_key, dht_public_key) != sizeof(handshake_packet)) { + return -1; + } + + if (new_temp_packet(c, crypt_connection_id, handshake_packet, sizeof(handshake_packet)) != 0) { + return -1; + } + + send_temp_packet(c, crypt_connection_id); + return 0; +} + +/** @brief Send a kill packet. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int send_kill_packet(Net_Crypto *c, int crypt_connection_id) +{ + const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + uint8_t kill_packet = PACKET_ID_KILL; + return send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, conn->send_array.buffer_end, + &kill_packet, sizeof(kill_packet)); +} + +non_null(1) nullable(3) +static void connection_kill(Net_Crypto *c, int crypt_connection_id, void *userdata) +{ + const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return; + } + + if (conn->connection_status_callback != nullptr) { + conn->connection_status_callback(conn->connection_status_callback_object, conn->connection_status_callback_id, + false, userdata); + } + + while (true) { /* TODO(irungentoo): is this really the best way to do this? */ + pthread_mutex_lock(&c->connections_mutex); + + if (c->connection_use_counter == 0) { + break; + } + + pthread_mutex_unlock(&c->connections_mutex); + } + + crypto_kill(c, crypt_connection_id); + pthread_mutex_unlock(&c->connections_mutex); +} + +/** @brief Handle a received data packet. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null(1, 3) nullable(6) +static int handle_data_packet_core(Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length, + bool udp, void *userdata) +{ + if (length > MAX_CRYPTO_PACKET_SIZE || length <= CRYPTO_DATA_PACKET_MIN_SIZE) { + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + uint8_t data[MAX_DATA_DATA_PACKET_SIZE]; + const int len = handle_data_packet(c, crypt_connection_id, data, packet, length); + + if (len <= (int)(sizeof(uint32_t) * 2)) { + return -1; + } + + uint32_t buffer_start; + uint32_t num; + memcpy(&buffer_start, data, sizeof(uint32_t)); + memcpy(&num, data + sizeof(uint32_t), sizeof(uint32_t)); + buffer_start = net_ntohl(buffer_start); + num = net_ntohl(num); + + uint64_t rtt_calc_time = 0; + + if (buffer_start != conn->send_array.buffer_start) { + Packet_Data *packet_time; + + if (get_data_pointer(&conn->send_array, &packet_time, conn->send_array.buffer_start) == 1) { + rtt_calc_time = packet_time->sent_time; + } + + if (clear_buffer_until(&conn->send_array, buffer_start) != 0) { + return -1; + } + } + + const uint8_t *real_data = data + (sizeof(uint32_t) * 2); + uint16_t real_length = len - (sizeof(uint32_t) * 2); + + while (real_data[0] == PACKET_ID_PADDING) { /* Remove Padding */ + ++real_data; + --real_length; + + if (real_length == 0) { + return -1; + } + } + + if (real_data[0] == PACKET_ID_KILL) { + connection_kill(c, crypt_connection_id, userdata); + return 0; + } + + if (conn->status == CRYPTO_CONN_NOT_CONFIRMED) { + clear_temp_packet(c, crypt_connection_id); + conn->status = CRYPTO_CONN_ESTABLISHED; + + if (conn->connection_status_callback != nullptr) { + conn->connection_status_callback(conn->connection_status_callback_object, conn->connection_status_callback_id, + true, userdata); + } + } + + if (real_data[0] == PACKET_ID_REQUEST) { + uint64_t rtt_time; + + if (udp) { + rtt_time = conn->rtt_time; + } else { + rtt_time = DEFAULT_TCP_PING_CONNECTION; + } + + const int requested = handle_request_packet(c->mono_time, &conn->send_array, + real_data, real_length, + &rtt_calc_time, rtt_time); + + if (requested == -1) { + return -1; + } + + set_buffer_end(&conn->recv_array, num); + } else if (real_data[0] >= PACKET_ID_RANGE_LOSSLESS_START && real_data[0] <= PACKET_ID_RANGE_LOSSLESS_END) { + Packet_Data dt = {0}; + dt.length = real_length; + memcpy(dt.data, real_data, real_length); + + if (add_data_to_buffer(&conn->recv_array, num, &dt) != 0) { + return -1; + } + + while (true) { + pthread_mutex_lock(conn->mutex); + const int ret = read_data_beg_buffer(&conn->recv_array, &dt); + pthread_mutex_unlock(conn->mutex); + + if (ret == -1) { + break; + } + + if (conn->connection_data_callback != nullptr) { + conn->connection_data_callback(conn->connection_data_callback_object, conn->connection_data_callback_id, dt.data, + dt.length, userdata); + } + + /* conn might get killed in callback. */ + conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + } + + /* Packet counter. */ + ++conn->packet_counter; + } else if (real_data[0] >= PACKET_ID_RANGE_LOSSY_START && real_data[0] <= PACKET_ID_RANGE_LOSSY_END) { + + set_buffer_end(&conn->recv_array, num); + + if (conn->connection_lossy_data_callback != nullptr) { + conn->connection_lossy_data_callback(conn->connection_lossy_data_callback_object, + conn->connection_lossy_data_callback_id, real_data, real_length, userdata); + } + } else { + return -1; + } + + if (rtt_calc_time != 0) { + uint64_t rtt_time = current_time_monotonic(c->mono_time) - rtt_calc_time; + + if (rtt_time < conn->rtt_time) { + conn->rtt_time = rtt_time; + } + } + + return 0; +} + +non_null() +static int handle_packet_cookie_response(Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING) { + return -1; + } + + uint8_t cookie[COOKIE_LENGTH]; + uint64_t number; + + if (handle_cookie_response(cookie, &number, packet, length, conn->shared_key) != sizeof(cookie)) { + return -1; + } + + if (number != conn->cookie_request_number) { + return -1; + } + + if (create_send_handshake(c, crypt_connection_id, cookie, conn->dht_public_key) != 0) { + return -1; + } + + conn->status = CRYPTO_CONN_HANDSHAKE_SENT; + return 0; +} + +non_null(1, 3) nullable(5) +static int handle_packet_crypto_hs(Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING + && conn->status != CRYPTO_CONN_HANDSHAKE_SENT + && conn->status != CRYPTO_CONN_NOT_CONFIRMED) { + return -1; + } + + uint8_t peer_real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t cookie[COOKIE_LENGTH]; + + if (!handle_crypto_handshake(c, conn->recv_nonce, conn->peersessionpublic_key, peer_real_pk, dht_public_key, cookie, + packet, length, conn->public_key)) { + return -1; + } + + if (pk_equal(dht_public_key, conn->dht_public_key)) { + encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); + + if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING) { + if (create_send_handshake(c, crypt_connection_id, cookie, dht_public_key) != 0) { + return -1; + } + } + + conn->status = CRYPTO_CONN_NOT_CONFIRMED; + } else { + if (conn->dht_pk_callback != nullptr) { + conn->dht_pk_callback(conn->dht_pk_callback_object, conn->dht_pk_callback_number, dht_public_key, userdata); + } + } + + return 0; +} + +non_null(1, 3) nullable(6) +static int handle_packet_crypto_data(Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length, + bool udp, void *userdata) +{ + const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + if (conn->status != CRYPTO_CONN_NOT_CONFIRMED && conn->status != CRYPTO_CONN_ESTABLISHED) { + return -1; + } + + return handle_data_packet_core(c, crypt_connection_id, packet, length, udp, userdata); +} + +/** @brief Handle a packet that was received for the connection. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null(1, 3) nullable(6) +static int handle_packet_connection(Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length, + bool udp, void *userdata) +{ + if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { + return -1; + } + + switch (packet[0]) { + case NET_PACKET_COOKIE_RESPONSE: + return handle_packet_cookie_response(c, crypt_connection_id, packet, length); + + case NET_PACKET_CRYPTO_HS: + return handle_packet_crypto_hs(c, crypt_connection_id, packet, length, userdata); + + case NET_PACKET_CRYPTO_DATA: + return handle_packet_crypto_data(c, crypt_connection_id, packet, length, udp, userdata); + + default: + return -1; + } +} + +/** @brief Set the size of the friend list to numfriends. + * + * @retval -1 if realloc fails. + * @retval 0 if it succeeds. + */ +non_null() +static int realloc_cryptoconnection(Net_Crypto *c, uint32_t num) +{ + if (num == 0) { + free(c->crypto_connections); + c->crypto_connections = nullptr; + return 0; + } + + Crypto_Connection *newcrypto_connections = (Crypto_Connection *)realloc(c->crypto_connections, + num * sizeof(Crypto_Connection)); + + if (newcrypto_connections == nullptr) { + return -1; + } + + c->crypto_connections = newcrypto_connections; + return 0; +} + + +/** @brief Create a new empty crypto connection. + * + * @retval -1 on failure. + * @return connection id on success. + */ +non_null() +static int create_crypto_connection(Net_Crypto *c) +{ + while (true) { /* TODO(irungentoo): is this really the best way to do this? */ + pthread_mutex_lock(&c->connections_mutex); + + if (c->connection_use_counter == 0) { + break; + } + + pthread_mutex_unlock(&c->connections_mutex); + } + + int id = -1; + + for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { + if (c->crypto_connections[i].status == CRYPTO_CONN_FREE) { + id = i; + break; + } + } + + if (id == -1) { + if (realloc_cryptoconnection(c, c->crypto_connections_length + 1) == 0) { + id = c->crypto_connections_length; + ++c->crypto_connections_length; + c->crypto_connections[id] = empty_crypto_connection; + } + } + + if (id != -1) { + // Memsetting float/double to 0 is non-portable, so we explicitly set them to 0 + c->crypto_connections[id].packet_recv_rate = 0; + c->crypto_connections[id].packet_send_rate = 0; + c->crypto_connections[id].last_packets_left_rem = 0; + c->crypto_connections[id].packet_send_rate_requested = 0; + c->crypto_connections[id].last_packets_left_requested_rem = 0; + c->crypto_connections[id].mutex = (pthread_mutex_t *)calloc(1, sizeof(pthread_mutex_t)); + + if (c->crypto_connections[id].mutex == nullptr) { + pthread_mutex_unlock(&c->connections_mutex); + return -1; + } + + if (pthread_mutex_init(c->crypto_connections[id].mutex, nullptr) != 0) { + free(c->crypto_connections[id].mutex); + pthread_mutex_unlock(&c->connections_mutex); + return -1; + } + + c->crypto_connections[id].status = CRYPTO_CONN_NO_CONNECTION; + } + + pthread_mutex_unlock(&c->connections_mutex); + return id; +} + +/** @brief Wipe a crypto connection. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null() +static int wipe_crypto_connection(Net_Crypto *c, int crypt_connection_id) +{ + if ((uint32_t)crypt_connection_id >= c->crypto_connections_length) { + return -1; + } + + if (c->crypto_connections == nullptr) { + return -1; + } + + const Crypto_Conn_State status = c->crypto_connections[crypt_connection_id].status; + + if (status == CRYPTO_CONN_FREE) { + return -1; + } + + uint32_t i; + + pthread_mutex_destroy(c->crypto_connections[crypt_connection_id].mutex); + free(c->crypto_connections[crypt_connection_id].mutex); + crypto_memzero(&c->crypto_connections[crypt_connection_id], sizeof(Crypto_Connection)); + + /* check if we can resize the connections array */ + for (i = c->crypto_connections_length; i != 0; --i) { + if (c->crypto_connections[i - 1].status != CRYPTO_CONN_FREE) { + break; + } + } + + if (c->crypto_connections_length != i) { + c->crypto_connections_length = i; + realloc_cryptoconnection(c, c->crypto_connections_length); + } + + return 0; +} + +/** @brief Get crypto connection id from public key of peer. + * + * @retval -1 if there are no connections like we are looking for. + * @return id if it found it. + */ +non_null() +static int getcryptconnection_id(const Net_Crypto *c, const uint8_t *public_key) +{ + for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { + if (!crypt_connection_id_is_valid(c, i)) { + continue; + } + + if (pk_equal(public_key, c->crypto_connections[i].public_key)) { + return i; + } + } + + return -1; +} + +/** @brief Add a source to the crypto connection. + * This is to be used only when we have received a packet from that source. + * + * @retval -1 on failure. + * @retval 0 if source was a direct UDP connection. + * @return positive number on success. + */ +non_null() +static int crypto_connection_add_source(Net_Crypto *c, int crypt_connection_id, const IP_Port *source) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + if (net_family_is_ipv4(source->ip.family) || net_family_is_ipv6(source->ip.family)) { + if (add_ip_port_connection(c, crypt_connection_id, source) != 0) { + return -1; + } + + if (net_family_is_ipv4(source->ip.family)) { + conn->direct_lastrecv_timev4 = mono_time_get(c->mono_time); + } else { + conn->direct_lastrecv_timev6 = mono_time_get(c->mono_time); + } + + return 0; + } + + unsigned int tcp_connections_number; + + if (ip_port_to_tcp_connections_number(source, &tcp_connections_number)) { + if (add_tcp_number_relay_connection(c->tcp_c, conn->connection_number_tcp, tcp_connections_number) == 0) { + return 1; + } + } + + return -1; +} + + +/** @brief Set function to be called when someone requests a new connection to us. + * + * The set function should return -1 on failure and 0 on success. + * + * n_c is only valid for the duration of the function call. + */ +void new_connection_handler(Net_Crypto *c, new_connection_cb *new_connection_callback, void *object) +{ + c->new_connection_callback = new_connection_callback; + c->new_connection_callback_object = object; +} + +/** @brief Handle a handshake packet by someone who wants to initiate a new connection with us. + * This calls the callback set by `new_connection_handler()` if the handshake is ok. + * + * @retval -1 on failure. + * @retval 0 on success. + */ +non_null(1, 2, 3) nullable(5) +static int handle_new_connection_handshake(Net_Crypto *c, const IP_Port *source, const uint8_t *data, uint16_t length, + void *userdata) +{ + New_Connection n_c; + n_c.cookie = (uint8_t *)malloc(COOKIE_LENGTH); + + if (n_c.cookie == nullptr) { + return -1; + } + + n_c.source = *source; + n_c.cookie_length = COOKIE_LENGTH; + + if (!handle_crypto_handshake(c, n_c.recv_nonce, n_c.peersessionpublic_key, n_c.public_key, n_c.dht_public_key, + n_c.cookie, data, length, nullptr)) { + free(n_c.cookie); + return -1; + } + + const int crypt_connection_id = getcryptconnection_id(c, n_c.public_key); + + if (crypt_connection_id != -1) { + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + if (!pk_equal(n_c.dht_public_key, conn->dht_public_key)) { + connection_kill(c, crypt_connection_id, userdata); + } else { + if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING && conn->status != CRYPTO_CONN_HANDSHAKE_SENT) { + free(n_c.cookie); + return -1; + } + + memcpy(conn->recv_nonce, n_c.recv_nonce, CRYPTO_NONCE_SIZE); + memcpy(conn->peersessionpublic_key, n_c.peersessionpublic_key, CRYPTO_PUBLIC_KEY_SIZE); + encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); + + crypto_connection_add_source(c, crypt_connection_id, source); + + if (create_send_handshake(c, crypt_connection_id, n_c.cookie, n_c.dht_public_key) != 0) { + free(n_c.cookie); + return -1; + } + + conn->status = CRYPTO_CONN_NOT_CONFIRMED; + free(n_c.cookie); + return 0; + } + } + + const int ret = c->new_connection_callback(c->new_connection_callback_object, &n_c); + free(n_c.cookie); + return ret; +} + +/** @brief Accept a crypto connection. + * + * return -1 on failure. + * return connection id on success. + */ +int accept_crypto_connection(Net_Crypto *c, const New_Connection *n_c) +{ + if (getcryptconnection_id(c, n_c->public_key) != -1) { + return -1; + } + + const int crypt_connection_id = create_crypto_connection(c); + + if (crypt_connection_id == -1) { + LOGGER_ERROR(c->log, "Could not create new crypto connection"); + return -1; + } + + Crypto_Connection *conn = &c->crypto_connections[crypt_connection_id]; + + if (n_c->cookie_length != COOKIE_LENGTH) { + wipe_crypto_connection(c, crypt_connection_id); + return -1; + } + + pthread_mutex_lock(&c->tcp_mutex); + const int connection_number_tcp = new_tcp_connection_to(c->tcp_c, n_c->dht_public_key, crypt_connection_id); + pthread_mutex_unlock(&c->tcp_mutex); + + if (connection_number_tcp == -1) { + wipe_crypto_connection(c, crypt_connection_id); + return -1; + } + + conn->connection_number_tcp = connection_number_tcp; + memcpy(conn->public_key, n_c->public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(conn->recv_nonce, n_c->recv_nonce, CRYPTO_NONCE_SIZE); + memcpy(conn->peersessionpublic_key, n_c->peersessionpublic_key, CRYPTO_PUBLIC_KEY_SIZE); + random_nonce(c->rng, conn->sent_nonce); + crypto_new_keypair(c->rng, conn->sessionpublic_key, conn->sessionsecret_key); + encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); + conn->status = CRYPTO_CONN_NOT_CONFIRMED; + + if (create_send_handshake(c, crypt_connection_id, n_c->cookie, n_c->dht_public_key) != 0) { + pthread_mutex_lock(&c->tcp_mutex); + kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); + pthread_mutex_unlock(&c->tcp_mutex); + wipe_crypto_connection(c, crypt_connection_id); + return -1; + } + + memcpy(conn->dht_public_key, n_c->dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; + conn->packet_send_rate_requested = CRYPTO_PACKET_MIN_RATE; + conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; + conn->rtt_time = DEFAULT_PING_CONNECTION; + crypto_connection_add_source(c, crypt_connection_id, &n_c->source); + return crypt_connection_id; +} + +/** @brief Create a crypto connection. + * If one to that real public key already exists, return it. + * + * return -1 on failure. + * return connection id on success. + */ +int new_crypto_connection(Net_Crypto *c, const uint8_t *real_public_key, const uint8_t *dht_public_key) +{ + int crypt_connection_id = getcryptconnection_id(c, real_public_key); + + if (crypt_connection_id != -1) { + return crypt_connection_id; + } + + crypt_connection_id = create_crypto_connection(c); + + if (crypt_connection_id == -1) { + return -1; + } + + Crypto_Connection *conn = &c->crypto_connections[crypt_connection_id]; + + pthread_mutex_lock(&c->tcp_mutex); + const int connection_number_tcp = new_tcp_connection_to(c->tcp_c, dht_public_key, crypt_connection_id); + pthread_mutex_unlock(&c->tcp_mutex); + + if (connection_number_tcp == -1) { + wipe_crypto_connection(c, crypt_connection_id); + return -1; + } + + conn->connection_number_tcp = connection_number_tcp; + memcpy(conn->public_key, real_public_key, CRYPTO_PUBLIC_KEY_SIZE); + random_nonce(c->rng, conn->sent_nonce); + crypto_new_keypair(c->rng, conn->sessionpublic_key, conn->sessionsecret_key); + conn->status = CRYPTO_CONN_COOKIE_REQUESTING; + conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; + conn->packet_send_rate_requested = CRYPTO_PACKET_MIN_RATE; + conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; + conn->rtt_time = DEFAULT_PING_CONNECTION; + memcpy(conn->dht_public_key, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + conn->cookie_request_number = random_u64(c->rng); + uint8_t cookie_request[COOKIE_REQUEST_LENGTH]; + + if (create_cookie_request(c, cookie_request, conn->dht_public_key, conn->cookie_request_number, + conn->shared_key) != sizeof(cookie_request) + || new_temp_packet(c, crypt_connection_id, cookie_request, sizeof(cookie_request)) != 0) { + pthread_mutex_lock(&c->tcp_mutex); + kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); + pthread_mutex_unlock(&c->tcp_mutex); + wipe_crypto_connection(c, crypt_connection_id); + return -1; + } + + return crypt_connection_id; +} + +/** @brief Set the direct ip of the crypto connection. + * + * Connected is 0 if we are not sure we are connected to that person, 1 if we are sure. + * + * return -1 on failure. + * return 0 on success. + */ +int set_direct_ip_port(Net_Crypto *c, int crypt_connection_id, const IP_Port *ip_port, bool connected) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + if (add_ip_port_connection(c, crypt_connection_id, ip_port) != 0) { + return -1; + } + + const uint64_t direct_lastrecv_time = connected ? mono_time_get(c->mono_time) : 0; + + if (net_family_is_ipv4(ip_port->ip.family)) { + conn->direct_lastrecv_timev4 = direct_lastrecv_time; + } else { + conn->direct_lastrecv_timev6 = direct_lastrecv_time; + } + + return 0; +} + + +non_null(1, 3) nullable(5) +static int tcp_data_callback(void *object, int crypt_connection_id, const uint8_t *data, uint16_t length, + void *userdata) +{ + Net_Crypto *c = (Net_Crypto *)object; + + if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { + return -1; + } + + const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + if (data[0] == NET_PACKET_COOKIE_REQUEST) { + return tcp_handle_cookie_request(c, conn->connection_number_tcp, data, length); + } + + // This unlocks the mutex that at this point is locked by do_tcp before + // calling do_tcp_connections. + pthread_mutex_unlock(&c->tcp_mutex); + const int ret = handle_packet_connection(c, crypt_connection_id, data, length, false, userdata); + pthread_mutex_lock(&c->tcp_mutex); + + if (ret != 0) { + return -1; + } + + // TODO(irungentoo): detect and kill bad TCP connections. + return 0; +} + +non_null(1, 2, 4) nullable(6) +static int tcp_oob_callback(void *object, const uint8_t *public_key, unsigned int tcp_connections_number, + const uint8_t *data, uint16_t length, void *userdata) +{ + Net_Crypto *c = (Net_Crypto *)object; + + if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { + return -1; + } + + if (data[0] == NET_PACKET_COOKIE_REQUEST) { + return tcp_oob_handle_cookie_request(c, tcp_connections_number, public_key, data, length); + } + + if (data[0] == NET_PACKET_CRYPTO_HS) { + IP_Port source = tcp_connections_number_to_ip_port(tcp_connections_number); + + if (handle_new_connection_handshake(c, &source, data, length, userdata) != 0) { + return -1; + } + + return 0; + } + + return -1; +} + +/** @brief Add a tcp relay, associating it to a crypt_connection_id. + * + * return 0 if it was added. + * return -1 if it wasn't. + */ +int add_tcp_relay_peer(Net_Crypto *c, int crypt_connection_id, const IP_Port *ip_port, const uint8_t *public_key) +{ + const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + pthread_mutex_lock(&c->tcp_mutex); + const int ret = add_tcp_relay_connection(c->tcp_c, conn->connection_number_tcp, ip_port, public_key); + pthread_mutex_unlock(&c->tcp_mutex); + return ret; +} + +/** @brief Add a tcp relay to the array. + * + * return 0 if it was added. + * return -1 if it wasn't. + */ +int add_tcp_relay(Net_Crypto *c, const IP_Port *ip_port, const uint8_t *public_key) +{ + pthread_mutex_lock(&c->tcp_mutex); + const int ret = add_tcp_relay_global(c->tcp_c, ip_port, public_key); + pthread_mutex_unlock(&c->tcp_mutex); + return ret; +} + +/** @brief Return a random TCP connection number for use in send_tcp_onion_request. + * + * TODO(irungentoo): This number is just the index of an array that the elements can + * change without warning. + * + * return TCP connection number on success. + * return -1 on failure. + */ +int get_random_tcp_con_number(Net_Crypto *c) +{ + pthread_mutex_lock(&c->tcp_mutex); + const int ret = get_random_tcp_onion_conn_number(c->tcp_c); + pthread_mutex_unlock(&c->tcp_mutex); + + return ret; +} + +/** @brief Put IP_Port of a random onion TCP connection in ip_port. + * + * return true on success. + * return false on failure. + */ +bool get_random_tcp_conn_ip_port(Net_Crypto *c, IP_Port *ip_port) +{ + pthread_mutex_lock(&c->tcp_mutex); + const bool ret = tcp_get_random_conn_ip_port(c->tcp_c, ip_port); + pthread_mutex_unlock(&c->tcp_mutex); + + return ret; +} + +/** @brief Send an onion packet via the TCP relay corresponding to tcp_connections_number. + * + * return 0 on success. + * return -1 on failure. + */ +int send_tcp_onion_request(Net_Crypto *c, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length) +{ + pthread_mutex_lock(&c->tcp_mutex); + const int ret = tcp_send_onion_request(c->tcp_c, tcp_connections_number, data, length); + pthread_mutex_unlock(&c->tcp_mutex); + + return ret; +} + +/** + * Send a forward request to the TCP relay with IP_Port tcp_forwarder, + * requesting to forward data via a chain of dht nodes starting with dht_node. + * A chain_length of 0 means that dht_node is the final destination of data. + * + * return 0 on success. + * return -1 on failure. + */ +int send_tcp_forward_request(const Logger *logger, Net_Crypto *c, const IP_Port *tcp_forwarder, const IP_Port *dht_node, + const uint8_t *chain_keys, uint16_t chain_length, + const uint8_t *data, uint16_t data_length) +{ + pthread_mutex_lock(&c->tcp_mutex); + const int ret = tcp_send_forward_request(logger, c->tcp_c, tcp_forwarder, dht_node, + chain_keys, chain_length, data, data_length); + pthread_mutex_unlock(&c->tcp_mutex); + + return ret; +} + +/** @brief Copy a maximum of num random TCP relays we are connected to to tcp_relays. + * + * NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6. + * + * return number of relays copied to tcp_relays on success. + * return 0 on failure. + */ +unsigned int copy_connected_tcp_relays(Net_Crypto *c, Node_format *tcp_relays, uint16_t num) +{ + if (num == 0) { + return 0; + } + + pthread_mutex_lock(&c->tcp_mutex); + const unsigned int ret = tcp_copy_connected_relays(c->tcp_c, tcp_relays, num); + pthread_mutex_unlock(&c->tcp_mutex); + + return ret; +} + +uint32_t copy_connected_tcp_relays_index(Net_Crypto *c, Node_format *tcp_relays, uint16_t num, uint32_t idx) +{ + if (num == 0) { + return 0; + } + + pthread_mutex_lock(&c->tcp_mutex); + const uint32_t ret = tcp_copy_connected_relays_index(c->tcp_c, tcp_relays, num, idx); + pthread_mutex_unlock(&c->tcp_mutex); + + return ret; +} + +non_null() +static void do_tcp(Net_Crypto *c, void *userdata) +{ + pthread_mutex_lock(&c->tcp_mutex); + do_tcp_connections(c->log, c->tcp_c, userdata); + pthread_mutex_unlock(&c->tcp_mutex); + + for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { + const Crypto_Connection *conn = get_crypto_connection(c, i); + + if (conn == nullptr) { + continue; + } + + if (conn->status != CRYPTO_CONN_ESTABLISHED) { + continue; + } + + bool direct_connected = false; + + if (!crypto_connection_status(c, i, &direct_connected, nullptr)) { + continue; + } + + pthread_mutex_lock(&c->tcp_mutex); + set_tcp_connection_to_status(c->tcp_c, conn->connection_number_tcp, !direct_connected); + pthread_mutex_unlock(&c->tcp_mutex); + } +} + +/** @brief Set function to be called when connection with crypt_connection_id goes connects/disconnects. + * + * The set function should return -1 on failure and 0 on success. + * Note that if this function is set, the connection will clear itself on disconnect. + * Object and id will be passed to this function untouched. + * status is 1 if the connection is going online, 0 if it is going offline. + * + * return -1 on failure. + * return 0 on success. + */ +int connection_status_handler(const Net_Crypto *c, int crypt_connection_id, + connection_status_cb *connection_status_callback, void *object, int id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + conn->connection_status_callback = connection_status_callback; + conn->connection_status_callback_object = object; + conn->connection_status_callback_id = id; + return 0; +} + +/** @brief Set function to be called when connection with crypt_connection_id receives a lossless data packet of length. + * + * The set function should return -1 on failure and 0 on success. + * Object and id will be passed to this function untouched. + * + * return -1 on failure. + * return 0 on success. + */ +int connection_data_handler(const Net_Crypto *c, int crypt_connection_id, + connection_data_cb *connection_data_callback, void *object, int id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + conn->connection_data_callback = connection_data_callback; + conn->connection_data_callback_object = object; + conn->connection_data_callback_id = id; + return 0; +} + +/** @brief Set function to be called when connection with crypt_connection_id receives a lossy data packet of length. + * + * The set function should return -1 on failure and 0 on success. + * Object and id will be passed to this function untouched. + * + * return -1 on failure. + * return 0 on success. + */ +int connection_lossy_data_handler(const Net_Crypto *c, int crypt_connection_id, + connection_lossy_data_cb *connection_lossy_data_callback, + void *object, int id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + conn->connection_lossy_data_callback = connection_lossy_data_callback; + conn->connection_lossy_data_callback_object = object; + conn->connection_lossy_data_callback_id = id; + return 0; +} + + +/** @brief Set the function for this friend that will be callbacked with object and number if + * the friend sends us a different dht public key than we have associated to him. + * + * If this function is called, the connection should be recreated with the new public key. + * + * object and number will be passed as argument to this function. + * + * return -1 on failure. + * return 0 on success. + */ +int nc_dht_pk_callback(const Net_Crypto *c, int crypt_connection_id, dht_pk_cb *function, void *object, uint32_t number) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + conn->dht_pk_callback = function; + conn->dht_pk_callback_object = object; + conn->dht_pk_callback_number = number; + return 0; +} + +/** @brief Get the crypto connection id from the ip_port. + * + * return -1 on failure. + * return connection id on success. + */ +non_null() +static int crypto_id_ip_port(const Net_Crypto *c, const IP_Port *ip_port) +{ + return bs_list_find(&c->ip_port_list, (const uint8_t *)ip_port); +} + +#define CRYPTO_MIN_PACKET_SIZE (1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE) + +/** @brief Handle raw UDP packets coming directly from the socket. + * + * Handles: + * Cookie response packets. + * Crypto handshake packets. + * Crypto data packets. + * + */ +non_null(1, 2, 3) nullable(5) +static int udp_handle_packet(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Net_Crypto *c = (Net_Crypto *)object; + + if (length <= CRYPTO_MIN_PACKET_SIZE || length > MAX_CRYPTO_PACKET_SIZE) { + return 1; + } + + const int crypt_connection_id = crypto_id_ip_port(c, source); + + if (crypt_connection_id == -1) { + if (packet[0] != NET_PACKET_CRYPTO_HS) { + return 1; + } + + if (handle_new_connection_handshake(c, source, packet, length, userdata) != 0) { + return 1; + } + + return 0; + } + + if (handle_packet_connection(c, crypt_connection_id, packet, length, true, userdata) != 0) { + return 1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + pthread_mutex_lock(conn->mutex); + + if (net_family_is_ipv4(source->ip.family)) { + conn->direct_lastrecv_timev4 = mono_time_get(c->mono_time); + } else { + conn->direct_lastrecv_timev6 = mono_time_get(c->mono_time); + } + + pthread_mutex_unlock(conn->mutex); + return 0; +} + +/** @brief The dT for the average packet receiving rate calculations. + * Also used as the + */ +#define PACKET_COUNTER_AVERAGE_INTERVAL 50 + +/** @brief Ratio of recv queue size / recv packet rate (in seconds) times + * the number of ms between request packets to send at that ratio + */ +#define REQUEST_PACKETS_COMPARE_CONSTANT (0.125 * 100.0) + +/** @brief Timeout for increasing speed after congestion event (in ms). */ +#define CONGESTION_EVENT_TIMEOUT 1000 + +/** + * If the send queue is SEND_QUEUE_RATIO times larger than the + * calculated link speed the packet send speed will be reduced + * by a value depending on this number. + */ +#define SEND_QUEUE_RATIO 2.0 + +non_null() +static void send_crypto_packets(Net_Crypto *c) +{ + const uint64_t temp_time = current_time_monotonic(c->mono_time); + double total_send_rate = 0; + uint32_t peak_request_packet_interval = -1; + + for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { + Crypto_Connection *conn = get_crypto_connection(c, i); + + if (conn == nullptr) { + continue; + } + + if ((CRYPTO_SEND_PACKET_INTERVAL + conn->temp_packet_sent_time) < temp_time) { + send_temp_packet(c, i); + } + + if ((conn->status == CRYPTO_CONN_NOT_CONFIRMED || conn->status == CRYPTO_CONN_ESTABLISHED) + && (CRYPTO_SEND_PACKET_INTERVAL + conn->last_request_packet_sent) < temp_time) { + if (send_request_packet(c, i) == 0) { + conn->last_request_packet_sent = temp_time; + } + } + + if (conn->status == CRYPTO_CONN_ESTABLISHED) { + if (conn->packet_recv_rate > CRYPTO_PACKET_MIN_RATE) { + double request_packet_interval = REQUEST_PACKETS_COMPARE_CONSTANT / ((num_packets_array( + &conn->recv_array) + 1.0) / (conn->packet_recv_rate + 1.0)); + + const double request_packet_interval2 = ((CRYPTO_PACKET_MIN_RATE / conn->packet_recv_rate) * + (double)CRYPTO_SEND_PACKET_INTERVAL) + (double)PACKET_COUNTER_AVERAGE_INTERVAL; + + if (request_packet_interval2 < request_packet_interval) { + request_packet_interval = request_packet_interval2; + } + + if (request_packet_interval < PACKET_COUNTER_AVERAGE_INTERVAL) { + request_packet_interval = PACKET_COUNTER_AVERAGE_INTERVAL; + } + + if (request_packet_interval > CRYPTO_SEND_PACKET_INTERVAL) { + request_packet_interval = CRYPTO_SEND_PACKET_INTERVAL; + } + + if (temp_time - conn->last_request_packet_sent > (uint64_t)request_packet_interval) { + if (send_request_packet(c, i) == 0) { + conn->last_request_packet_sent = temp_time; + } + } + + if (request_packet_interval < peak_request_packet_interval) { + peak_request_packet_interval = request_packet_interval; + } + } + + if ((PACKET_COUNTER_AVERAGE_INTERVAL + conn->packet_counter_set) < temp_time) { + const double dt = (double)(temp_time - conn->packet_counter_set); + + conn->packet_recv_rate = (double)conn->packet_counter / (dt / 1000.0); + conn->packet_counter = 0; + conn->packet_counter_set = temp_time; + + const uint32_t packets_sent = conn->packets_sent; + conn->packets_sent = 0; + + const uint32_t packets_resent = conn->packets_resent; + conn->packets_resent = 0; + + /* conjestion control + * calculate a new value of conn->packet_send_rate based on some data + */ + + const unsigned int pos = conn->last_sendqueue_counter % CONGESTION_QUEUE_ARRAY_SIZE; + conn->last_sendqueue_size[pos] = num_packets_array(&conn->send_array); + + long signed int sum = 0; + sum = (long signed int)conn->last_sendqueue_size[pos] - + (long signed int)conn->last_sendqueue_size[(pos + 1) % CONGESTION_QUEUE_ARRAY_SIZE]; + + const unsigned int n_p_pos = conn->last_sendqueue_counter % CONGESTION_LAST_SENT_ARRAY_SIZE; + conn->last_num_packets_sent[n_p_pos] = packets_sent; + conn->last_num_packets_resent[n_p_pos] = packets_resent; + + conn->last_sendqueue_counter = (conn->last_sendqueue_counter + 1) % + (CONGESTION_QUEUE_ARRAY_SIZE * CONGESTION_LAST_SENT_ARRAY_SIZE); + + bool direct_connected = false; + /* return value can be ignored since the `if` above ensures the connection is established */ + crypto_connection_status(c, i, &direct_connected, nullptr); + + /* When switching from TCP to UDP, don't change the packet send rate for CONGESTION_EVENT_TIMEOUT ms. */ + if (!(direct_connected && conn->last_tcp_sent + CONGESTION_EVENT_TIMEOUT > temp_time)) { + long signed int total_sent = 0; + long signed int total_resent = 0; + + // TODO(irungentoo): use real delay + unsigned int delay = (unsigned int)(((double)conn->rtt_time / PACKET_COUNTER_AVERAGE_INTERVAL) + 0.5); + const unsigned int packets_set_rem_array = CONGESTION_LAST_SENT_ARRAY_SIZE - CONGESTION_QUEUE_ARRAY_SIZE; + + if (delay > packets_set_rem_array) { + delay = packets_set_rem_array; + } + + for (unsigned j = 0; j < CONGESTION_QUEUE_ARRAY_SIZE; ++j) { + const unsigned int ind = (j + (packets_set_rem_array - delay) + n_p_pos) % CONGESTION_LAST_SENT_ARRAY_SIZE; + total_sent += conn->last_num_packets_sent[ind]; + total_resent += conn->last_num_packets_resent[ind]; + } + + if (sum > 0) { + total_sent -= sum; + } else { + if (total_resent > -sum) { + total_resent = -sum; + } + } + + /* if queue is too big only allow resending packets. */ + const uint32_t npackets = num_packets_array(&conn->send_array); + double min_speed = 1000.0 * (((double)total_sent) / ((double)CONGESTION_QUEUE_ARRAY_SIZE * + PACKET_COUNTER_AVERAGE_INTERVAL)); + + const double min_speed_request = 1000.0 * (((double)(total_sent + total_resent)) / ( + (double)CONGESTION_QUEUE_ARRAY_SIZE * PACKET_COUNTER_AVERAGE_INTERVAL)); + + if (min_speed < CRYPTO_PACKET_MIN_RATE) { + min_speed = CRYPTO_PACKET_MIN_RATE; + } + + const double send_array_ratio = (double)npackets / min_speed; + + // TODO(irungentoo): Improve formula? + if (send_array_ratio > SEND_QUEUE_RATIO && CRYPTO_MIN_QUEUE_LENGTH < npackets) { + conn->packet_send_rate = min_speed * (1.0 / (send_array_ratio / SEND_QUEUE_RATIO)); + } else if (conn->last_congestion_event + CONGESTION_EVENT_TIMEOUT < temp_time) { + conn->packet_send_rate = min_speed * 1.2; + } else { + conn->packet_send_rate = min_speed * 0.9; + } + + conn->packet_send_rate_requested = min_speed_request * 1.2; + + if (conn->packet_send_rate < CRYPTO_PACKET_MIN_RATE) { + conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; + } + + if (conn->packet_send_rate_requested < conn->packet_send_rate) { + conn->packet_send_rate_requested = conn->packet_send_rate; + } + } + } + + if (conn->last_packets_left_set == 0 || conn->last_packets_left_requested_set == 0) { + conn->last_packets_left_requested_set = temp_time; + conn->last_packets_left_set = temp_time; + conn->packets_left_requested = CRYPTO_MIN_QUEUE_LENGTH; + conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; + } else { + if (((uint64_t)((1000.0 / conn->packet_send_rate) + 0.5) + conn->last_packets_left_set) <= temp_time) { + double n_packets = conn->packet_send_rate * (((double)(temp_time - conn->last_packets_left_set)) / 1000.0); + n_packets += conn->last_packets_left_rem; + + const uint32_t num_packets = n_packets; + const double rem = n_packets - (double)num_packets; + + if (conn->packets_left > num_packets * 4 + CRYPTO_MIN_QUEUE_LENGTH) { + conn->packets_left = num_packets * 4 + CRYPTO_MIN_QUEUE_LENGTH; + } else { + conn->packets_left += num_packets; + } + + conn->last_packets_left_set = temp_time; + conn->last_packets_left_rem = rem; + } + + if (((uint64_t)((1000.0 / conn->packet_send_rate_requested) + 0.5) + conn->last_packets_left_requested_set) <= + temp_time) { + double n_packets = conn->packet_send_rate_requested * (((double)(temp_time - conn->last_packets_left_requested_set)) / + 1000.0); + n_packets += conn->last_packets_left_requested_rem; + + uint32_t num_packets = n_packets; + double rem = n_packets - (double)num_packets; + conn->packets_left_requested = num_packets; + + conn->last_packets_left_requested_set = temp_time; + conn->last_packets_left_requested_rem = rem; + } + + if (conn->packets_left > conn->packets_left_requested) { + conn->packets_left_requested = conn->packets_left; + } + } + + const int ret = send_requested_packets(c, i, conn->packets_left_requested); + + if (ret != -1) { + conn->packets_left_requested -= ret; + conn->packets_resent += ret; + + if ((unsigned int)ret < conn->packets_left) { + conn->packets_left -= ret; + } else { + conn->last_congestion_event = temp_time; + conn->packets_left = 0; + } + } + + if (conn->packet_send_rate > CRYPTO_PACKET_MIN_RATE * 1.5) { + total_send_rate += conn->packet_send_rate; + } + } + } + + c->current_sleep_time = -1; + uint32_t sleep_time = peak_request_packet_interval; + + if (c->current_sleep_time > sleep_time) { + c->current_sleep_time = sleep_time; + } + + if (total_send_rate > CRYPTO_PACKET_MIN_RATE) { + sleep_time = 1000.0 / total_send_rate; + + if (c->current_sleep_time > sleep_time) { + c->current_sleep_time = sleep_time + 1; + } + } + + sleep_time = CRYPTO_SEND_PACKET_INTERVAL; + + if (c->current_sleep_time > sleep_time) { + c->current_sleep_time = sleep_time; + } +} + +/** + * @retval 1 if max speed was reached for this connection (no more data can be physically through the pipe). + * @retval 0 if it wasn't reached. + */ +bool max_speed_reached(Net_Crypto *c, int crypt_connection_id) +{ + return reset_max_speed_reached(c, crypt_connection_id) != 0; +} + +/** + * @return the number of packet slots left in the sendbuffer. + * @retval 0 if failure. + */ +uint32_t crypto_num_free_sendqueue_slots(const Net_Crypto *c, int crypt_connection_id) +{ + const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return 0; + } + + const uint32_t max_packets = CRYPTO_PACKET_BUFFER_SIZE - num_packets_array(&conn->send_array); + + if (conn->packets_left < max_packets) { + return conn->packets_left; + } + + return max_packets; +} + +/** @brief Sends a lossless cryptopacket. + * + * return -1 if data could not be put in packet queue. + * return positive packet number if data was put into the queue. + * + * The first byte of data must be in the PACKET_ID_RANGE_LOSSLESS. + * + * congestion_control: should congestion control apply to this packet? + */ +int64_t write_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length, + bool congestion_control) +{ + if (length == 0) { + // We need at least a packet id. + LOGGER_ERROR(c->log, "rejecting empty packet for crypto connection %d", crypt_connection_id); + return -1; + } + + if (data[0] < PACKET_ID_RANGE_LOSSLESS_START || data[0] > PACKET_ID_RANGE_LOSSLESS_END) { + LOGGER_ERROR(c->log, "rejecting lossless packet with out-of-range id %d", data[0]); + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + LOGGER_WARNING(c->log, "invalid crypt connection id %d", crypt_connection_id); + return -1; + } + + if (conn->status != CRYPTO_CONN_ESTABLISHED) { + LOGGER_WARNING(c->log, "attempted to send packet to non-established connection %d", crypt_connection_id); + return -1; + } + + if (congestion_control && conn->packets_left == 0) { + LOGGER_ERROR(c->log, "congestion control: rejecting packet of length %d on crypt connection %d", length, + crypt_connection_id); + return -1; + } + + const int64_t ret = send_lossless_packet(c, crypt_connection_id, data, length, congestion_control); + + if (ret == -1) { + return -1; + } + + if (congestion_control) { + --conn->packets_left; + --conn->packets_left_requested; + ++conn->packets_sent; + } + + return ret; +} + +/** @brief Check if packet_number was received by the other side. + * + * packet_number must be a valid packet number of a packet sent on this connection. + * + * return -1 on failure. + * return 0 on success. + * + * Note: The condition `buffer_end - buffer_start < packet_number - buffer_start` is + * a trick which handles situations `buffer_end >= buffer_start` and + * `buffer_end < buffer_start` (when buffer_end overflowed) both correctly. + * + * It CANNOT be simplified to `packet_number < buffer_start`, as it will fail + * when `buffer_end < buffer_start`. + */ +int cryptpacket_received(const Net_Crypto *c, int crypt_connection_id, uint32_t packet_number) +{ + const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return -1; + } + + const uint32_t num = num_packets_array(&conn->send_array); + const uint32_t num1 = packet_number - conn->send_array.buffer_start; + + if (num >= num1) { + return -1; + } + + return 0; +} + +/** @brief Sends a lossy cryptopacket. + * + * return -1 on failure. + * return 0 on success. + * + * The first byte of data must be in the PACKET_ID_RANGE_LOSSY. + */ +int send_lossy_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length) +{ + if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { + return -1; + } + + if (data[0] < PACKET_ID_RANGE_LOSSY_START || data[0] > PACKET_ID_RANGE_LOSSY_END) { + return -1; + } + + pthread_mutex_lock(&c->connections_mutex); + ++c->connection_use_counter; + pthread_mutex_unlock(&c->connections_mutex); + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + int ret = -1; + + if (conn != nullptr) { + pthread_mutex_lock(conn->mutex); + const uint32_t buffer_start = conn->recv_array.buffer_start; + const uint32_t buffer_end = conn->send_array.buffer_end; + pthread_mutex_unlock(conn->mutex); + ret = send_data_packet_helper(c, crypt_connection_id, buffer_start, buffer_end, data, length); + } + + pthread_mutex_lock(&c->connections_mutex); + --c->connection_use_counter; + pthread_mutex_unlock(&c->connections_mutex); + + return ret; +} + +/** @brief Kill a crypto connection. + * + * return -1 on failure. + * return 0 on success. + */ +int crypto_kill(Net_Crypto *c, int crypt_connection_id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + int ret = -1; + + if (conn != nullptr) { + if (conn->status == CRYPTO_CONN_ESTABLISHED) { + send_kill_packet(c, crypt_connection_id); + } + + pthread_mutex_lock(&c->tcp_mutex); + kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); + pthread_mutex_unlock(&c->tcp_mutex); + + bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv4, crypt_connection_id); + bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv6, crypt_connection_id); + clear_temp_packet(c, crypt_connection_id); + clear_buffer(&conn->send_array); + clear_buffer(&conn->recv_array); + ret = wipe_crypto_connection(c, crypt_connection_id); + } + + return ret; +} + +bool crypto_connection_status(const Net_Crypto *c, int crypt_connection_id, bool *direct_connected, + uint32_t *online_tcp_relays) +{ + const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == nullptr) { + return false; + } + + if (direct_connected != nullptr) { + *direct_connected = false; + + const uint64_t current_time = mono_time_get(c->mono_time); + + if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev4) > current_time) { + *direct_connected = true; + } else if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev6) > current_time) { + *direct_connected = true; + } + } + + if (online_tcp_relays != nullptr) { + *online_tcp_relays = tcp_connection_to_online_tcp_relays(c->tcp_c, conn->connection_number_tcp); + } + + return true; +} + +void new_keys(Net_Crypto *c) +{ + crypto_new_keypair(c->rng, c->self_public_key, c->self_secret_key); +} + +/** @brief Save the public and private keys to the keys array. + * Length must be CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE. + * + * TODO(irungentoo): Save only secret key. + */ +void save_keys(const Net_Crypto *c, uint8_t *keys) +{ + memcpy(keys, c->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(keys + CRYPTO_PUBLIC_KEY_SIZE, c->self_secret_key, CRYPTO_SECRET_KEY_SIZE); +} + +/** @brief Load the secret key. + * Length must be CRYPTO_SECRET_KEY_SIZE. + */ +void load_secret_key(Net_Crypto *c, const uint8_t *sk) +{ + memcpy(c->self_secret_key, sk, CRYPTO_SECRET_KEY_SIZE); + crypto_derive_public_key(c->self_public_key, c->self_secret_key); +} + +/** @brief Create new instance of Net_Crypto. + * Sets all the global connection variables to their default values. + */ +Net_Crypto *new_net_crypto(const Logger *log, const Random *rng, const Network *ns, Mono_Time *mono_time, DHT *dht, const TCP_Proxy_Info *proxy_info) +{ + if (dht == nullptr) { + return nullptr; + } + + Net_Crypto *temp = (Net_Crypto *)calloc(1, sizeof(Net_Crypto)); + + if (temp == nullptr) { + return nullptr; + } + + temp->log = log; + temp->rng = rng; + temp->mono_time = mono_time; + temp->ns = ns; + + temp->tcp_c = new_tcp_connections(log, rng, ns, mono_time, dht_get_self_secret_key(dht), proxy_info); + + if (temp->tcp_c == nullptr) { + free(temp); + return nullptr; + } + + set_packet_tcp_connection_callback(temp->tcp_c, &tcp_data_callback, temp); + set_oob_packet_tcp_connection_callback(temp->tcp_c, &tcp_oob_callback, temp); + + if (create_recursive_mutex(&temp->tcp_mutex) != 0 || + pthread_mutex_init(&temp->connections_mutex, nullptr) != 0) { + kill_tcp_connections(temp->tcp_c); + free(temp); + return nullptr; + } + + temp->dht = dht; + + new_keys(temp); + new_symmetric_key(rng, temp->secret_symmetric_key); + + temp->current_sleep_time = CRYPTO_SEND_PACKET_INTERVAL; + + networking_registerhandler(dht_get_net(dht), NET_PACKET_COOKIE_REQUEST, &udp_handle_cookie_request, temp); + networking_registerhandler(dht_get_net(dht), NET_PACKET_COOKIE_RESPONSE, &udp_handle_packet, temp); + networking_registerhandler(dht_get_net(dht), NET_PACKET_CRYPTO_HS, &udp_handle_packet, temp); + networking_registerhandler(dht_get_net(dht), NET_PACKET_CRYPTO_DATA, &udp_handle_packet, temp); + + bs_list_init(&temp->ip_port_list, sizeof(IP_Port), 8); + + return temp; +} + +non_null(1) nullable(2) +static void kill_timedout(Net_Crypto *c, void *userdata) +{ + for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { + const Crypto_Connection *conn = get_crypto_connection(c, i); + + if (conn == nullptr) { + continue; + } + + if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING || conn->status == CRYPTO_CONN_HANDSHAKE_SENT + || conn->status == CRYPTO_CONN_NOT_CONFIRMED) { + if (conn->temp_packet_num_sent < MAX_NUM_SENDPACKET_TRIES) { + continue; + } + + connection_kill(c, i, userdata); + } + +#if 0 + + if (conn->status == CRYPTO_CONN_ESTABLISHED) { + // TODO(irungentoo): add a timeout here? + /* do_timeout_here(); */ + } + +#endif + } +} + +/** return the optimal interval in ms for running do_net_crypto. */ +uint32_t crypto_run_interval(const Net_Crypto *c) +{ + return c->current_sleep_time; +} + +/** Main loop. */ +void do_net_crypto(Net_Crypto *c, void *userdata) +{ + kill_timedout(c, userdata); + do_tcp(c, userdata); + send_crypto_packets(c); +} + +void kill_net_crypto(Net_Crypto *c) +{ + if (c == nullptr) { + return; + } + + for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { + crypto_kill(c, i); + } + + pthread_mutex_destroy(&c->tcp_mutex); + pthread_mutex_destroy(&c->connections_mutex); + + kill_tcp_connections(c->tcp_c); + bs_list_free(&c->ip_port_list); + networking_registerhandler(dht_get_net(c->dht), NET_PACKET_COOKIE_REQUEST, nullptr, nullptr); + networking_registerhandler(dht_get_net(c->dht), NET_PACKET_COOKIE_RESPONSE, nullptr, nullptr); + networking_registerhandler(dht_get_net(c->dht), NET_PACKET_CRYPTO_HS, nullptr, nullptr); + networking_registerhandler(dht_get_net(c->dht), NET_PACKET_CRYPTO_DATA, nullptr, nullptr); + crypto_memzero(c, sizeof(Net_Crypto)); + free(c); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/network.h b/local_pod_repo/toxcore/toxcore/toxcore/network.h new file mode 100644 index 0000000..a1fc6a7 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/network.h @@ -0,0 +1,605 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Datatypes, functions and includes for the core networking. + */ +#ifndef C_TOXCORE_TOXCORE_NETWORK_H +#define C_TOXCORE_TOXCORE_NETWORK_H + +#include // bool +#include // size_t +#include // uint*_t + +#include "logger.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Wrapper for sockaddr_storage and size. + */ +typedef struct Network_Addr Network_Addr; + +typedef int net_close_cb(void *obj, int sock); +typedef int net_accept_cb(void *obj, int sock); +typedef int net_bind_cb(void *obj, int sock, const Network_Addr *addr); +typedef int net_listen_cb(void *obj, int sock, int backlog); +typedef int net_recvbuf_cb(void *obj, int sock); +typedef int net_recv_cb(void *obj, int sock, uint8_t *buf, size_t len); +typedef int net_recvfrom_cb(void *obj, int sock, uint8_t *buf, size_t len, Network_Addr *addr); +typedef int net_send_cb(void *obj, int sock, const uint8_t *buf, size_t len); +typedef int net_sendto_cb(void *obj, int sock, const uint8_t *buf, size_t len, const Network_Addr *addr); +typedef int net_socket_cb(void *obj, int domain, int type, int proto); +typedef int net_socket_nonblock_cb(void *obj, int sock, bool nonblock); +typedef int net_getsockopt_cb(void *obj, int sock, int level, int optname, void *optval, size_t *optlen); +typedef int net_setsockopt_cb(void *obj, int sock, int level, int optname, const void *optval, size_t optlen); +typedef int net_getaddrinfo_cb(void *obj, int family, Network_Addr **addrs); +typedef int net_freeaddrinfo_cb(void *obj, Network_Addr *addrs); + +/** @brief Functions wrapping POSIX network functions. + * + * Refer to POSIX man pages for documentation of what these functions are + * expected to do when providing alternative Network implementations. + */ +typedef struct Network_Funcs { + net_close_cb *close; + net_accept_cb *accept; + net_bind_cb *bind; + net_listen_cb *listen; + net_recvbuf_cb *recvbuf; + net_recv_cb *recv; + net_recvfrom_cb *recvfrom; + net_send_cb *send; + net_sendto_cb *sendto; + net_socket_cb *socket; + net_socket_nonblock_cb *socket_nonblock; + net_getsockopt_cb *getsockopt; + net_setsockopt_cb *setsockopt; + net_getaddrinfo_cb *getaddrinfo; + net_freeaddrinfo_cb *freeaddrinfo; +} Network_Funcs; + +typedef struct Network { + const Network_Funcs *funcs; + void *obj; +} Network; + +const Network *system_network(void); + +typedef struct Family { + uint8_t value; +} Family; + +bool net_family_is_unspec(Family family); +bool net_family_is_ipv4(Family family); +bool net_family_is_ipv6(Family family); +bool net_family_is_tcp_server(Family family); +bool net_family_is_tcp_client(Family family); +bool net_family_is_tcp_ipv4(Family family); +bool net_family_is_tcp_ipv6(Family family); +bool net_family_is_tox_tcp_ipv4(Family family); +bool net_family_is_tox_tcp_ipv6(Family family); + +Family net_family_unspec(void); +Family net_family_ipv4(void); +Family net_family_ipv6(void); +Family net_family_tcp_server(void); +Family net_family_tcp_client(void); +Family net_family_tcp_ipv4(void); +Family net_family_tcp_ipv6(void); +Family net_family_tox_tcp_ipv4(void); +Family net_family_tox_tcp_ipv6(void); + +#define MAX_UDP_PACKET_SIZE 2048 + +#ifdef USE_TEST_NETWORK +typedef enum Net_Packet_Type { + NET_PACKET_PING_REQUEST = 0x05, /* Ping request packet ID. */ + NET_PACKET_PING_RESPONSE = 0x06, /* Ping response packet ID. */ + NET_PACKET_GET_NODES = 0x07, /* Get nodes request packet ID. */ + NET_PACKET_SEND_NODES_IPV6 = 0x08, /* Send nodes response packet ID for other addresses. */ + NET_PACKET_COOKIE_REQUEST = 0x1c, /* Cookie request packet */ + NET_PACKET_COOKIE_RESPONSE = 0x1d, /* Cookie response packet */ + NET_PACKET_CRYPTO_HS = 0x1e, /* Crypto handshake packet */ + NET_PACKET_CRYPTO_DATA = 0x1f, /* Crypto data packet */ + NET_PACKET_CRYPTO = 0x24, /* Encrypted data packet ID. */ + NET_PACKET_LAN_DISCOVERY = 0x25, /* LAN discovery packet ID. */ + + // TODO(Jfreegman): Uncomment these when we merge the rest of new groupchats + // NET_PACKET_GC_HANDSHAKE = 0x62, /* Group chat handshake packet ID */ + // NET_PACKET_GC_LOSSLESS = 0x63, /* Group chat lossless packet ID */ + // NET_PACKET_GC_LOSSY = 0x64, /* Group chat lossy packet ID */ + + /* See: `docs/Prevent_Tracking.txt` and `onion.{c,h}` */ + NET_PACKET_ONION_SEND_INITIAL = 0x8f, + NET_PACKET_ONION_SEND_1 = 0x90, + NET_PACKET_ONION_SEND_2 = 0x91, + + NET_PACKET_ANNOUNCE_REQUEST = 0x92, + NET_PACKET_ANNOUNCE_RESPONSE = 0x93, + NET_PACKET_ONION_DATA_REQUEST = 0x94, + NET_PACKET_ONION_DATA_RESPONSE = 0x95, + + NET_PACKET_ANNOUNCE_REQUEST_OLD = 0x96, /* TODO: DEPRECATE */ + NET_PACKET_ANNOUNCE_RESPONSE_OLD = 0x97, /* TODO: DEPRECATE */ + + NET_PACKET_ONION_RECV_3 = 0x9b, + NET_PACKET_ONION_RECV_2 = 0x9c, + NET_PACKET_ONION_RECV_1 = 0x9d, + + BOOTSTRAP_INFO_PACKET_ID = 0xf1, /* Only used for bootstrap nodes */ + + NET_PACKET_MAX = 0xff, /* This type must remain within a single uint8. */ +} Net_Packet_Type; +#else +typedef enum Net_Packet_Type { + NET_PACKET_PING_REQUEST = 0x00, /* Ping request packet ID. */ + NET_PACKET_PING_RESPONSE = 0x01, /* Ping response packet ID. */ + NET_PACKET_GET_NODES = 0x02, /* Get nodes request packet ID. */ + NET_PACKET_SEND_NODES_IPV6 = 0x04, /* Send nodes response packet ID for other addresses. */ + NET_PACKET_COOKIE_REQUEST = 0x18, /* Cookie request packet */ + NET_PACKET_COOKIE_RESPONSE = 0x19, /* Cookie response packet */ + NET_PACKET_CRYPTO_HS = 0x1a, /* Crypto handshake packet */ + NET_PACKET_CRYPTO_DATA = 0x1b, /* Crypto data packet */ + NET_PACKET_CRYPTO = 0x20, /* Encrypted data packet ID. */ + NET_PACKET_LAN_DISCOVERY = 0x21, /* LAN discovery packet ID. */ + + // TODO(Jfreegman): Uncomment these when we merge the rest of new groupchats + // NET_PACKET_GC_HANDSHAKE = 0x5a, /* Group chat handshake packet ID */ + // NET_PACKET_GC_LOSSLESS = 0x5b, /* Group chat lossless packet ID */ + // NET_PACKET_GC_LOSSY = 0x5c, /* Group chat lossy packet ID */ + + /* See: `docs/Prevent_Tracking.txt` and `onion.{c,h}` */ + NET_PACKET_ONION_SEND_INITIAL = 0x80, + NET_PACKET_ONION_SEND_1 = 0x81, + NET_PACKET_ONION_SEND_2 = 0x82, + + NET_PACKET_ANNOUNCE_REQUEST_OLD = 0x83, /* TODO: DEPRECATE */ + NET_PACKET_ANNOUNCE_RESPONSE_OLD = 0x84, /* TODO: DEPRECATE */ + + NET_PACKET_ONION_DATA_REQUEST = 0x85, + NET_PACKET_ONION_DATA_RESPONSE = 0x86, + NET_PACKET_ANNOUNCE_REQUEST = 0x87, + NET_PACKET_ANNOUNCE_RESPONSE = 0x88, + + NET_PACKET_ONION_RECV_3 = 0x8c, + NET_PACKET_ONION_RECV_2 = 0x8d, + NET_PACKET_ONION_RECV_1 = 0x8e, + + NET_PACKET_FORWARD_REQUEST = 0x90, + NET_PACKET_FORWARDING = 0x91, + NET_PACKET_FORWARD_REPLY = 0x92, + + NET_PACKET_DATA_SEARCH_REQUEST = 0x93, + NET_PACKET_DATA_SEARCH_RESPONSE = 0x94, + NET_PACKET_DATA_RETRIEVE_REQUEST = 0x95, + NET_PACKET_DATA_RETRIEVE_RESPONSE = 0x96, + NET_PACKET_STORE_ANNOUNCE_REQUEST = 0x97, + NET_PACKET_STORE_ANNOUNCE_RESPONSE = 0x98, + + BOOTSTRAP_INFO_PACKET_ID = 0xf0, /* Only used for bootstrap nodes */ + + NET_PACKET_MAX = 0xff, /* This type must remain within a single uint8. */ +} Net_Packet_Type; +#endif // test network + + +#define TOX_PORTRANGE_FROM 33445 +#define TOX_PORTRANGE_TO 33545 +#define TOX_PORT_DEFAULT TOX_PORTRANGE_FROM + +/** Redefinitions of variables for safe transfer over wire. */ +#define TOX_AF_UNSPEC 0 +#define TOX_AF_INET 2 +#define TOX_AF_INET6 10 +#define TOX_TCP_INET 130 +#define TOX_TCP_INET6 138 + +#define TOX_SOCK_STREAM 1 +#define TOX_SOCK_DGRAM 2 + +#define TOX_PROTO_TCP 1 +#define TOX_PROTO_UDP 2 + +/** TCP related */ +#define TCP_CLIENT_FAMILY (TOX_AF_INET6 + 1) +#define TCP_INET (TOX_AF_INET6 + 2) +#define TCP_INET6 (TOX_AF_INET6 + 3) +#define TCP_SERVER_FAMILY (TOX_AF_INET6 + 4) + +#define SIZE_IP4 4 +#define SIZE_IP6 16 +#define SIZE_IP (1 + SIZE_IP6) +#define SIZE_PORT 2 +#define SIZE_IPPORT (SIZE_IP + SIZE_PORT) + +typedef union IP4 { + uint32_t uint32; + uint16_t uint16[2]; + uint8_t uint8[4]; +} IP4; + +IP4 get_ip4_loopback(void); +extern const IP4 ip4_broadcast; + +typedef union IP6 { + uint8_t uint8[16]; + uint16_t uint16[8]; + uint32_t uint32[4]; + uint64_t uint64[2]; +} IP6; + +IP6 get_ip6_loopback(void); +extern const IP6 ip6_broadcast; + +typedef union IP_Union { + IP4 v4; + IP6 v6; +} IP_Union; + +typedef struct IP { + Family family; + IP_Union ip; +} IP; + +typedef struct IP_Port { + IP ip; + uint16_t port; +} IP_Port; + +extern const IP_Port empty_ip_port; + +typedef struct Socket { + int sock; +} Socket; + +non_null() +Socket net_socket(const Network *ns, Family domain, int type, int protocol); + +/** + * Check if socket is valid. + * + * @return true if valid, false otherwise. + */ +bool sock_valid(Socket sock); + +extern const Socket net_invalid_socket; + +/** + * Calls send(sockfd, buf, len, MSG_NOSIGNAL). + */ +non_null() +int net_send(const Network *ns, const Logger *log, Socket sock, const uint8_t *buf, size_t len, const IP_Port *ip_port); +/** + * Calls recv(sockfd, buf, len, MSG_NOSIGNAL). + */ +non_null() +int net_recv(const Network *ns, const Logger *log, Socket sock, uint8_t *buf, size_t len, const IP_Port *ip_port); +/** + * Calls listen(sockfd, backlog). + */ +non_null() +int net_listen(const Network *ns, Socket sock, int backlog); +/** + * Calls accept(sockfd, nullptr, nullptr). + */ +non_null() +Socket net_accept(const Network *ns, Socket sock); + +/** + * return the size of data in the tcp recv buffer. + * return 0 on failure. + */ +non_null() +uint16_t net_socket_data_recv_buffer(const Network *ns, Socket sock); + +/** Convert values between host and network byte order. */ +uint32_t net_htonl(uint32_t hostlong); +uint16_t net_htons(uint16_t hostshort); +uint32_t net_ntohl(uint32_t hostlong); +uint16_t net_ntohs(uint16_t hostshort); + +non_null() +size_t net_pack_u16(uint8_t *bytes, uint16_t v); +non_null() +size_t net_pack_u32(uint8_t *bytes, uint32_t v); +non_null() +size_t net_pack_u64(uint8_t *bytes, uint64_t v); + +non_null() +size_t net_unpack_u16(const uint8_t *bytes, uint16_t *v); +non_null() +size_t net_unpack_u32(const uint8_t *bytes, uint32_t *v); +non_null() +size_t net_unpack_u64(const uint8_t *bytes, uint64_t *v); + +/** Does the IP6 struct a contain an IPv4 address in an IPv6 one? */ +non_null() +bool ipv6_ipv4_in_v6(const IP6 *a); + +#define TOX_ENABLE_IPV6_DEFAULT true + +#define TOX_INET6_ADDRSTRLEN 66 +#define TOX_INET_ADDRSTRLEN 22 + +/** this would be TOX_INET6_ADDRSTRLEN, but it might be too short for the error message */ +#define IP_NTOA_LEN 96 // TODO(irungentoo): magic number. Why not INET6_ADDRSTRLEN ? + +typedef struct Ip_Ntoa { + char buf[IP_NTOA_LEN]; +} Ip_Ntoa; + +/** @brief Converts IP into a string. + * + * Writes error message into the buffer on error. + * + * @param ip_str contains a buffer of the required size. + * + * @return Pointer to the buffer inside `ip_str` containing the IP string. + */ +non_null() +const char *net_ip_ntoa(const IP *ip, Ip_Ntoa *ip_str); + +/** + * Parses IP structure into an address string. + * + * @param ip IP of TOX_AF_INET or TOX_AF_INET6 families. + * @param length length of the address buffer. + * Must be at least TOX_INET_ADDRSTRLEN for TOX_AF_INET + * and TOX_INET6_ADDRSTRLEN for TOX_AF_INET6 + * + * @param address dotted notation (IPv4: quad, IPv6: 16) or colon notation (IPv6). + * + * @return true on success, false on failure. + */ +non_null() +bool ip_parse_addr(const IP *ip, char *address, size_t length); + +/** + * Directly parses the input into an IP structure. + * + * Tries IPv4 first, then IPv6. + * + * @param address dotted notation (IPv4: quad, IPv6: 16) or colon notation (IPv6). + * @param to family and the value is set on success. + * + * @return true on success, false on failure. + */ +non_null() +bool addr_parse_ip(const char *address, IP *to); + +/** + * Compares two IPAny structures. + * + * Unset means unequal. + * + * @return false when not equal or when uninitialized. + */ +nullable(1, 2) +bool ip_equal(const IP *a, const IP *b); + +/** + * Compares two IPAny_Port structures. + * + * Unset means unequal. + * + * @return false when not equal or when uninitialized. + */ +nullable(1, 2) +bool ipport_equal(const IP_Port *a, const IP_Port *b); + +/** nulls out ip */ +non_null() +void ip_reset(IP *ip); +/** nulls out ip_port */ +non_null() +void ipport_reset(IP_Port *ipport); +/** nulls out ip, sets family according to flag */ +non_null() +void ip_init(IP *ip, bool ipv6enabled); +/** checks if ip is valid */ +non_null() +bool ip_isset(const IP *ip); +/** checks if ip is valid */ +non_null() +bool ipport_isset(const IP_Port *ipport); +/** copies an ip structure (careful about direction) */ +non_null() +void ip_copy(IP *target, const IP *source); +/** copies an ip_port structure (careful about direction) */ +non_null() +void ipport_copy(IP_Port *target, const IP_Port *source); + +/** + * Resolves string into an IP address + * + * @param address a hostname (or something parseable to an IP address) + * @param to to.family MUST be initialized, either set to a specific IP version + * (TOX_AF_INET/TOX_AF_INET6) or to the unspecified TOX_AF_UNSPEC (0), if both + * IP versions are acceptable + * @param extra can be NULL and is only set in special circumstances, see returns + * + * Returns in `*to` a matching address (IPv6 or IPv4) + * Returns in `*extra`, if not NULL, an IPv4 address, if `to->family` was TOX_AF_UNSPEC + * + * @return true on success, false on failure + */ +non_null(1, 2, 3) nullable(4) +bool addr_resolve_or_parse_ip(const Network *ns, const char *address, IP *to, IP *extra); + +/** @brief Function to receive data, ip and port of sender is put into ip_port. + * Packet data is put into data. + * Packet length is put into length. + */ +typedef int packet_handler_cb(void *object, const IP_Port *ip_port, const uint8_t *data, uint16_t len, void *userdata); + +typedef struct Networking_Core Networking_Core; + +non_null() +Family net_family(const Networking_Core *net); +non_null() +uint16_t net_port(const Networking_Core *net); + +/** Close the socket. */ +non_null() +void kill_sock(const Network *ns, Socket sock); + +/** + * Set socket as nonblocking + * + * @return true on success, false on failure. + */ +non_null() +bool set_socket_nonblock(const Network *ns, Socket sock); + +/** + * Set socket to not emit SIGPIPE + * + * @return true on success, false on failure. + */ +non_null() +bool set_socket_nosigpipe(const Network *ns, Socket sock); + +/** + * Enable SO_REUSEADDR on socket. + * + * @return true on success, false on failure. + */ +non_null() +bool set_socket_reuseaddr(const Network *ns, Socket sock); + +/** + * Set socket to dual (IPv4 + IPv6 socket) + * + * @return true on success, false on failure. + */ +non_null() +bool set_socket_dualstack(const Network *ns, Socket sock); + +/* Basic network functions: */ + +/** + * An outgoing network packet. + * + * Use `send_packet` to send it to an IP/port endpoint. + */ +typedef struct Packet { + const uint8_t *data; + uint16_t length; +} Packet; + +/** + * Function to send a network packet to a given IP/port. + */ +non_null() +int send_packet(const Networking_Core *net, const IP_Port *ip_port, Packet packet); + +/** + * Function to send packet(data) of length length to ip_port. + * + * @deprecated Use send_packet instead. + */ +non_null() +int sendpacket(const Networking_Core *net, const IP_Port *ip_port, const uint8_t *data, uint16_t length); + +/** Function to call when packet beginning with byte is received. */ +non_null(1) nullable(3, 4) +void networking_registerhandler(Networking_Core *net, uint8_t byte, packet_handler_cb *cb, void *object); + +/** Call this several times a second. */ +non_null(1) nullable(2) +void networking_poll(const Networking_Core *net, void *userdata); + +/** @brief Connect a socket to the address specified by the ip_port. + * + * Return true on success. + * Return false on failure. + */ +non_null() +bool net_connect(const Logger *log, Socket sock, const IP_Port *ip_port); + +/** @brief High-level getaddrinfo implementation. + * + * Given node, which identifies an Internet host, `net_getipport()` fills an array + * with one or more IP_Port structures, each of which contains an Internet + * address that can be specified by calling `net_connect()`, the port is ignored. + * + * Skip all addresses with socktype != type (use type = -1 to get all addresses) + * To correctly deallocate array memory use `net_freeipport()` + * + * return number of elements in res array + * and -1 on error. + */ +non_null() +int32_t net_getipport(const char *node, IP_Port **res, int tox_type); + +/** Deallocates memory allocated by net_getipport */ +nullable(1) +void net_freeipport(IP_Port *ip_ports); + +/** + * @return true on success, false on failure. + */ +non_null() +bool bind_to_port(const Network *ns, Socket sock, Family family, uint16_t port); + +/** @brief Get the last networking error code. + * + * Similar to Unix's errno, but cross-platform, as not all platforms use errno + * to indicate networking errors. + * + * Note that different platforms may return different codes for the same error, + * so you likely shouldn't be checking the value returned by this function + * unless you know what you are doing, you likely just want to use it in + * combination with `net_new_strerror()` to print the error. + * + * return platform-dependent network error code, if any. + */ +int net_error(void); + +/** @brief Get a text explanation for the error code from `net_error()`. + * + * return NULL on failure. + * return pointer to a NULL-terminated string describing the error code on + * success. The returned string must be freed using `net_kill_strerror()`. + */ +char *net_new_strerror(int error); + +/** @brief Frees the string returned by `net_new_strerror()`. + * It's valid to pass NULL as the argument, the function does nothing in this + * case. + */ +non_null() +void net_kill_strerror(char *strerror); + +/** @brief Initialize networking. + * Bind to ip and port. + * ip must be in network order EX: 127.0.0.1 = (7F000001). + * port is in host byte order (this means don't worry about it). + * + * @return Networking_Core object if no problems + * @retval NULL if there are problems. + * + * If error is non NULL it is set to 0 if no issues, 1 if socket related error, 2 if other. + */ +non_null(1, 2, 3) nullable(6) +Networking_Core *new_networking_ex( + const Logger *log, const Network *ns, const IP *ip, + uint16_t port_from, uint16_t port_to, unsigned int *error); + +non_null() +Networking_Core *new_networking_no_udp(const Logger *log, const Network *ns); + +/** Function to cleanup networking stuff (doesn't do much right now). */ +nullable(1) +void kill_networking(Networking_Core *net); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/network.m b/local_pod_repo/toxcore/toxcore/toxcore/network.m new file mode 100644 index 0000000..785b143 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/network.m @@ -0,0 +1,1953 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Functions for the core networking. + */ + +#ifdef __APPLE__ +#define _DARWIN_C_SOURCE +#endif + +// For Solaris. +#ifdef __sun +#define __EXTENSIONS__ 1 +#endif + +// For Linux (and some BSDs). +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 +#endif + +#if defined(_WIN32) && _WIN32_WINNT >= _WIN32_WINNT_WINXP +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x501 +#endif + +#if !defined(OS_WIN32) && (defined(_WIN32) || defined(__WIN32__) || defined(WIN32)) +#define OS_WIN32 +#endif + +#if defined(OS_WIN32) && !defined(WINVER) +// Windows XP +#define WINVER 0x0501 +#endif + +#include "network.h" + +#ifdef PLAN9 +#include // Plan 9 requires this is imported first +// Comment line here to avoid reordering by source code formatters. +#include +#endif + +#ifdef OS_WIN32 // Put win32 includes here +// The mingw32/64 Windows library warns about including winsock2.h after +// windows.h even though with the above it's a valid thing to do. So, to make +// mingw32 headers happy, we include winsock2.h first. +#include +// Comment line here to avoid reordering by source code formatters. +#include +#include +#endif + +#ifdef __APPLE__ +#include +#include +#endif + +#if !defined(OS_WIN32) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __sun +#include +#include +#endif + +#else +#ifndef IPV6_V6ONLY +#define IPV6_V6ONLY 27 +#endif +#endif + +#include +#include +#include +#include +#include + +#ifndef VANILLA_NACL +// Used for sodium_init() +#include "sodium.h" +#endif + +#include "ccompat.h" +#include "logger.h" +#include "mono_time.h" +#include "util.h" + +// Disable MSG_NOSIGNAL on systems not supporting it, e.g. Windows, FreeBSD +#if !defined(MSG_NOSIGNAL) +#define MSG_NOSIGNAL 0 +#endif + +#ifndef IPV6_ADD_MEMBERSHIP +#ifdef IPV6_JOIN_GROUP +#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP +#endif +#endif + +static_assert(sizeof(IP4) == SIZE_IP4, "IP4 size must be 4"); + +// TODO(iphydf): Stop relying on this. We memcpy this struct (and IP4 above) +// into packets but really should be serialising it properly. +static_assert(sizeof(IP6) == SIZE_IP6, "IP6 size must be 16"); + +#if !defined(OS_WIN32) + +static bool should_ignore_recv_error(int err) +{ + return err == EWOULDBLOCK; +} + +static bool should_ignore_connect_error(int err) +{ + return err == EWOULDBLOCK || err == EINPROGRESS; +} + +non_null() +static const char *inet_ntop4(const struct in_addr *addr, char *buf, size_t bufsize) +{ + return inet_ntop(AF_INET, addr, buf, bufsize); +} + +non_null() +static const char *inet_ntop6(const struct in6_addr *addr, char *buf, size_t bufsize) +{ + return inet_ntop(AF_INET6, addr, buf, bufsize); +} + +non_null() +static int inet_pton4(const char *addrString, struct in_addr *addrbuf) +{ + return inet_pton(AF_INET, addrString, addrbuf); +} + +non_null() +static int inet_pton6(const char *addrString, struct in6_addr *addrbuf) +{ + return inet_pton(AF_INET6, addrString, addrbuf); +} + +#else +#ifndef IPV6_V6ONLY +#define IPV6_V6ONLY 27 +#endif + +static bool should_ignore_recv_error(int err) +{ + // We ignore WSAECONNRESET as Windows helpfully* sends that error if a + // previously sent UDP packet wasn't delivered. + return err == WSAEWOULDBLOCK || err == WSAECONNRESET; +} + +static bool should_ignore_connect_error(int err) +{ + return err == WSAEWOULDBLOCK || err == WSAEINPROGRESS; +} + +non_null() +static const char *inet_ntop4(const struct in_addr *addr, char *buf, size_t bufsize) +{ + struct sockaddr_in saddr = {0}; + + saddr.sin_family = AF_INET; + saddr.sin_addr = *addr; + + DWORD len = bufsize; + + if (WSAAddressToString((LPSOCKADDR)&saddr, sizeof(saddr), nullptr, buf, &len)) { + return nullptr; + } + + return buf; +} + +non_null() +static const char *inet_ntop6(const struct in6_addr *addr, char *buf, size_t bufsize) +{ + struct sockaddr_in6 saddr = {0}; + + saddr.sin6_family = AF_INET6; + saddr.sin6_addr = *addr; + + DWORD len = bufsize; + + if (WSAAddressToString((LPSOCKADDR)&saddr, sizeof(saddr), nullptr, buf, &len)) { + return nullptr; + } + + return buf; +} + +non_null() +static int inet_pton4(const char *addrString, struct in_addr *addrbuf) +{ + struct sockaddr_in saddr = {0}; + + INT len = sizeof(saddr); + + if (WSAStringToAddress((LPTSTR)addrString, AF_INET, nullptr, (LPSOCKADDR)&saddr, &len)) { + return 0; + } + + *addrbuf = saddr.sin_addr; + + return 1; +} + +non_null() +static int inet_pton6(const char *addrString, struct in6_addr *addrbuf) +{ + struct sockaddr_in6 saddr = {0}; + + INT len = sizeof(saddr); + + if (WSAStringToAddress((LPTSTR)addrString, AF_INET6, nullptr, (LPSOCKADDR)&saddr, &len)) { + return 0; + } + + *addrbuf = saddr.sin6_addr; + + return 1; +} + +#endif + +static_assert(TOX_INET6_ADDRSTRLEN >= INET6_ADDRSTRLEN, + "TOX_INET6_ADDRSTRLEN should be greater or equal to INET6_ADDRSTRLEN (#INET6_ADDRSTRLEN)"); +static_assert(TOX_INET_ADDRSTRLEN >= INET_ADDRSTRLEN, + "TOX_INET_ADDRSTRLEN should be greater or equal to INET_ADDRSTRLEN (#INET_ADDRSTRLEN)"); + +static int make_proto(int proto) +{ + switch (proto) { + case TOX_PROTO_TCP: + return IPPROTO_TCP; + + case TOX_PROTO_UDP: + return IPPROTO_UDP; + + default: + return proto; + } +} + +static int make_socktype(int type) +{ + switch (type) { + case TOX_SOCK_STREAM: + return SOCK_STREAM; + + case TOX_SOCK_DGRAM: + return SOCK_DGRAM; + + default: + return type; + } +} + +static int make_family(Family tox_family) +{ + switch (tox_family.value) { + case TOX_AF_INET: + return AF_INET; + + case TOX_AF_INET6: + return AF_INET6; + + case TOX_AF_UNSPEC: + return AF_UNSPEC; + + default: + return tox_family.value; + } +} + +static const Family family_unspec = {TOX_AF_UNSPEC}; +static const Family family_ipv4 = {TOX_AF_INET}; +static const Family family_ipv6 = {TOX_AF_INET6}; +static const Family family_tcp_server = {TCP_SERVER_FAMILY}; +static const Family family_tcp_client = {TCP_CLIENT_FAMILY}; +static const Family family_tcp_ipv4 = {TCP_INET}; +static const Family family_tcp_ipv6 = {TCP_INET6}; +static const Family family_tox_tcp_ipv4 = {TOX_TCP_INET}; +static const Family family_tox_tcp_ipv6 = {TOX_TCP_INET6}; + +static const Family *make_tox_family(int family) +{ + switch (family) { + case AF_INET: + return &family_ipv4; + + case AF_INET6: + return &family_ipv6; + + case AF_UNSPEC: + return &family_unspec; + + default: + return nullptr; + } +} + +non_null() +static void get_ip4(IP4 *result, const struct in_addr *addr) +{ + static_assert(sizeof(result->uint32) == sizeof(addr->s_addr), + "Tox and operating system don't agree on size of IPv4 addresses"); + result->uint32 = addr->s_addr; +} + +non_null() +static void get_ip6(IP6 *result, const struct in6_addr *addr) +{ + static_assert(sizeof(result->uint8) == sizeof(addr->s6_addr), + "Tox and operating system don't agree on size of IPv6 addresses"); + memcpy(result->uint8, addr->s6_addr, sizeof(result->uint8)); +} + +non_null() +static void fill_addr4(const IP4 *ip, struct in_addr *addr) +{ + addr->s_addr = ip->uint32; +} + +non_null() +static void fill_addr6(const IP6 *ip, struct in6_addr *addr) +{ + memcpy(addr->s6_addr, ip->uint8, sizeof(ip->uint8)); +} + +#if !defined(INADDR_LOOPBACK) +#define INADDR_LOOPBACK 0x7f000001 +#endif + +static const IP empty_ip = {{0}}; +const IP_Port empty_ip_port = {{{0}}}; + +const IP4 ip4_broadcast = { INADDR_BROADCAST }; +const IP6 ip6_broadcast = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } +}; + +IP4 get_ip4_loopback(void) +{ + IP4 loopback; + loopback.uint32 = htonl(INADDR_LOOPBACK); + return loopback; +} + +IP6 get_ip6_loopback(void) +{ + IP6 loopback; +#ifdef ESP_PLATFORM + loopback = empty_ip_port.ip.ip.v6; + loopback.uint8[15] = 1; +#else + get_ip6(&loopback, &in6addr_loopback); +#endif + return loopback; +} + +#ifndef OS_WIN32 +#define INVALID_SOCKET (-1) +#endif + +const Socket net_invalid_socket = { (int)INVALID_SOCKET }; + +Family net_family_unspec() +{ + return family_unspec; +} + +Family net_family_ipv4() +{ + return family_ipv4; +} + +Family net_family_ipv6() +{ + return family_ipv6; +} + +Family net_family_tcp_server() +{ + return family_tcp_server; +} + +Family net_family_tcp_client() +{ + return family_tcp_client; +} + +Family net_family_tcp_ipv4() +{ + return family_tcp_ipv4; +} + +Family net_family_tcp_ipv6() +{ + return family_tcp_ipv6; +} + +Family net_family_tox_tcp_ipv4() +{ + return family_tox_tcp_ipv4; +} + +Family net_family_tox_tcp_ipv6() +{ + return family_tox_tcp_ipv6; +} + +bool net_family_is_unspec(Family family) +{ + return family.value == family_unspec.value; +} + +bool net_family_is_ipv4(Family family) +{ + return family.value == family_ipv4.value; +} + +bool net_family_is_ipv6(Family family) +{ + return family.value == family_ipv6.value; +} + +bool net_family_is_tcp_server(Family family) +{ + return family.value == family_tcp_server.value; +} + +bool net_family_is_tcp_client(Family family) +{ + return family.value == family_tcp_client.value; +} + +bool net_family_is_tcp_ipv4(Family family) +{ + return family.value == family_tcp_ipv4.value; +} + +bool net_family_is_tcp_ipv6(Family family) +{ + return family.value == family_tcp_ipv6.value; +} + +bool net_family_is_tox_tcp_ipv4(Family family) +{ + return family.value == family_tox_tcp_ipv4.value; +} + +bool net_family_is_tox_tcp_ipv6(Family family) +{ + return family.value == family_tox_tcp_ipv6.value; +} + +bool sock_valid(Socket sock) +{ + return sock.sock != net_invalid_socket.sock; +} + +struct Network_Addr { + struct sockaddr_storage addr; + size_t size; +}; + +non_null() +static int sys_close(void *obj, int sock) +{ +#if defined(OS_WIN32) + return closesocket(sock); +#else // !OS_WIN32 + return close(sock); +#endif +} + +non_null() +static int sys_accept(void *obj, int sock) +{ + return accept(sock, nullptr, nullptr); +} + +non_null() +static int sys_bind(void *obj, int sock, const Network_Addr *addr) +{ + return bind(sock, (const struct sockaddr *)&addr->addr, addr->size); +} + +non_null() +static int sys_listen(void *obj, int sock, int backlog) +{ + return listen(sock, backlog); +} + +non_null() +static int sys_recvbuf(void *obj, int sock) +{ +#ifdef OS_WIN32 + u_long count = 0; + ioctlsocket(sock, FIONREAD, &count); +#else + int count = 0; + ioctl(sock, FIONREAD, &count); +#endif + + return count; +} + +non_null() +static int sys_recv(void *obj, int sock, uint8_t *buf, size_t len) +{ + return recv(sock, (char *)buf, len, MSG_NOSIGNAL); +} + +non_null() +static int sys_send(void *obj, int sock, const uint8_t *buf, size_t len) +{ + return send(sock, (const char *)buf, len, MSG_NOSIGNAL); +} + +non_null() +static int sys_sendto(void *obj, int sock, const uint8_t *buf, size_t len, const Network_Addr *addr) { + return sendto(sock, (const char *)buf, len, 0, (const struct sockaddr *)&addr->addr, addr->size); +} + +non_null() +static int sys_recvfrom(void *obj, int sock, uint8_t *buf, size_t len, Network_Addr *addr) { + socklen_t size = addr->size; + const int ret = recvfrom(sock, (char *)buf, len, 0, (struct sockaddr *)&addr->addr, &size); + addr->size = size; + return ret; +} + +non_null() +static int sys_socket(void *obj, int domain, int type, int proto) +{ + return (int)socket(domain, type, proto); +} + +non_null() +static int sys_socket_nonblock(void *obj, int sock, bool nonblock) +{ +#ifdef OS_WIN32 + u_long mode = nonblock ? 1 : 0; + return ioctlsocket(sock, FIONBIO, &mode); +#else + return fcntl(sock, F_SETFL, O_NONBLOCK, nonblock ? 1 : 0); +#endif /* OS_WIN32 */ +} + +non_null() +static int sys_getsockopt(void *obj, int sock, int level, int optname, void *optval, size_t *optlen) +{ + socklen_t len = *optlen; + const int ret = getsockopt(sock, level, optname, optval, &len); + *optlen = len; + return ret; +} + +non_null() +static int sys_setsockopt(void *obj, int sock, int level, int optname, const void *optval, size_t optlen) +{ + return setsockopt(sock, level, optname, optval, optlen); +} + +static const Network_Funcs system_network_funcs = { + sys_close, + sys_accept, + sys_bind, + sys_listen, + sys_recvbuf, + sys_recv, + sys_recvfrom, + sys_send, + sys_sendto, + sys_socket, + sys_socket_nonblock, + sys_getsockopt, + sys_setsockopt, +}; +static const Network system_network_obj = {&system_network_funcs}; + +const Network *system_network(void) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if ((true)) { + return nullptr; + } +#endif +#ifdef OS_WIN32 + WSADATA wsaData; + + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) { + return nullptr; + } +#endif + return &system_network_obj; +} + +#if 0 +/* TODO(iphydf): Call this from functions that use `system_network()`. */ +void system_network_deinit(const Network *ns) +{ +#ifdef OS_WIN32 + WSACleanup(); +#endif +} +#endif + +non_null() +static int net_setsockopt(const Network *ns, Socket sock, int level, int optname, const void *optval, size_t optlen) +{ + return ns->funcs->setsockopt(ns->obj, sock.sock, level, optname, optval, optlen); +} + +non_null() +static int net_getsockopt(const Network *ns, Socket sock, int level, int optname, void *optval, size_t *optlen) +{ + return ns->funcs->getsockopt(ns->obj, sock.sock, level, optname, optval, optlen); +} + +non_null() +static uint32_t data_0(uint16_t buflen, const uint8_t *buffer) +{ + uint32_t data = 0; + + if (buflen > 4) { + net_unpack_u32(buffer + 1, &data); + } + + return data; +} +non_null() +static uint32_t data_1(uint16_t buflen, const uint8_t *buffer) +{ + uint32_t data = 0; + + if (buflen > 8) { + net_unpack_u32(buffer + 5, &data); + } + + return data; +} + +non_null() +static void loglogdata(const Logger *log, const char *message, const uint8_t *buffer, + uint16_t buflen, const IP_Port *ip_port, long res) +{ + if (res < 0) { /* Windows doesn't necessarily know `%zu` */ + Ip_Ntoa ip_str; + const int error = net_error(); + char *strerror = net_new_strerror(error); + LOGGER_TRACE(log, "[%2u] %s %3u%c %s:%u (%u: %s) | %08x%08x...%02x", + buffer[0], message, min_u16(buflen, 999), 'E', + net_ip_ntoa(&ip_port->ip, &ip_str), net_ntohs(ip_port->port), error, + strerror, data_0(buflen, buffer), data_1(buflen, buffer), buffer[buflen - 1]); + net_kill_strerror(strerror); + } else if ((res > 0) && ((size_t)res <= buflen)) { + Ip_Ntoa ip_str; + LOGGER_TRACE(log, "[%2u] %s %3u%c %s:%u (%u: %s) | %08x%08x...%02x", + buffer[0], message, min_u16(res, 999), (size_t)res < buflen ? '<' : '=', + net_ip_ntoa(&ip_port->ip, &ip_str), net_ntohs(ip_port->port), 0, "OK", + data_0(buflen, buffer), data_1(buflen, buffer), buffer[buflen - 1]); + } else { /* empty or overwrite */ + Ip_Ntoa ip_str; + LOGGER_TRACE(log, "[%2u] %s %lu%c%u %s:%u (%u: %s) | %08x%08x...%02x", + buffer[0], message, res, res == 0 ? '!' : '>', buflen, + net_ip_ntoa(&ip_port->ip, &ip_str), net_ntohs(ip_port->port), 0, "OK", + data_0(buflen, buffer), data_1(buflen, buffer), buffer[buflen - 1]); + } +} + +int net_send(const Network *ns, const Logger *log, + Socket sock, const uint8_t *buf, size_t len, const IP_Port *ip_port) +{ + const int res = ns->funcs->send(ns->obj, sock.sock, buf, len); + loglogdata(log, "T=>", buf, len, ip_port, res); + return res; +} + +non_null() +static int net_sendto( + const Network *ns, + Socket sock, const uint8_t *buf, size_t len, const Network_Addr *addr, const IP_Port *ip_port) +{ + return ns->funcs->sendto(ns->obj, sock.sock, buf, len, addr); +} + +int net_recv(const Network *ns, const Logger *log, + Socket sock, uint8_t *buf, size_t len, const IP_Port *ip_port) +{ + const int res = ns->funcs->recv(ns->obj, sock.sock, buf, len); + loglogdata(log, "=>T", buf, len, ip_port, res); + return res; +} + +non_null() +static int net_recvfrom(const Network *ns, + Socket sock, uint8_t *buf, size_t len, Network_Addr *addr) +{ + return ns->funcs->recvfrom(ns->obj, sock.sock, buf, len, addr); +} + +int net_listen(const Network *ns, Socket sock, int backlog) +{ + return ns->funcs->listen(ns->obj, sock.sock, backlog); +} + +non_null() +static int net_bind(const Network *ns, Socket sock, const Network_Addr *addr) +{ + return ns->funcs->bind(ns->obj, sock.sock, addr); +} + +Socket net_accept(const Network *ns, Socket sock) +{ + const Socket newsock = {ns->funcs->accept(ns->obj, sock.sock)}; + return newsock; +} + +/** Close the socket. */ +void kill_sock(const Network *ns, Socket sock) +{ + ns->funcs->close(ns->obj, sock.sock); +} + +bool set_socket_nonblock(const Network *ns, Socket sock) +{ + return ns->funcs->socket_nonblock(ns->obj, sock.sock, true) == 0; +} + +bool set_socket_nosigpipe(const Network *ns, Socket sock) +{ +#if defined(__APPLE__) + int set = 1; + return net_setsockopt(ns, sock, SOL_SOCKET, SO_NOSIGPIPE, &set, sizeof(int)) == 0; +#else + return true; +#endif +} + +bool set_socket_reuseaddr(const Network *ns, Socket sock) +{ + int set = 1; + return net_setsockopt(ns, sock, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set)) == 0; +} + +bool set_socket_dualstack(const Network *ns, Socket sock) +{ + int ipv6only = 0; + size_t optsize = sizeof(ipv6only); + const int res = net_getsockopt(ns, sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, &optsize); + + if ((res == 0) && (ipv6only == 0)) { + return true; + } + + ipv6only = 0; + return net_setsockopt(ns, sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only)) == 0; +} + + +typedef struct Packet_Handler { + packet_handler_cb *function; + void *object; +} Packet_Handler; + +struct Networking_Core { + const Logger *log; + Packet_Handler packethandlers[256]; + const Network *ns; + + Family family; + uint16_t port; + /* Our UDP socket. */ + Socket sock; +}; + +Family net_family(const Networking_Core *net) +{ + return net->family; +} + +uint16_t net_port(const Networking_Core *net) +{ + return net->port; +} + +/* Basic network functions: + */ + +int send_packet(const Networking_Core *net, const IP_Port *ip_port, Packet packet) +{ + IP_Port ipp_copy = *ip_port; + + if (net_family_is_unspec(ip_port->ip.family)) { + // TODO(iphydf): Make this an error. Currently this fails sometimes when + // called from DHT.c:do_ping_and_sendnode_requests. + return -1; + } + + if (net_family_is_unspec(net->family)) { /* Socket not initialized */ + // TODO(iphydf): Make this an error. Currently, the onion client calls + // this via DHT getnodes. + LOGGER_WARNING(net->log, "attempted to send message of length %u on uninitialised socket", packet.length); + return -1; + } + + /* socket TOX_AF_INET, but target IP NOT: can't send */ + if (net_family_is_ipv4(net->family) && !net_family_is_ipv4(ipp_copy.ip.family)) { + // TODO(iphydf): Make this an error. Occasionally we try to send to an + // all-zero ip_port. + LOGGER_WARNING(net->log, "attempted to send message with network family %d (probably IPv6) on IPv4 socket", + ipp_copy.ip.family.value); + return -1; + } + + if (net_family_is_ipv4(ipp_copy.ip.family) && net_family_is_ipv6(net->family)) { + /* must convert to IPV4-in-IPV6 address */ + IP6 ip6; + + /* there should be a macro for this in a standards compliant + * environment, not found */ + ip6.uint32[0] = 0; + ip6.uint32[1] = 0; + ip6.uint32[2] = net_htonl(0xFFFF); + ip6.uint32[3] = ipp_copy.ip.ip.v4.uint32; + + ipp_copy.ip.family = net_family_ipv6(); + ipp_copy.ip.ip.v6 = ip6; + } + + Network_Addr addr; + + if (net_family_is_ipv4(ipp_copy.ip.family)) { + struct sockaddr_in *const addr4 = (struct sockaddr_in *)&addr.addr; + + addr.size = sizeof(struct sockaddr_in); + addr4->sin_family = AF_INET; + addr4->sin_port = ipp_copy.port; + fill_addr4(&ipp_copy.ip.ip.v4, &addr4->sin_addr); + } else if (net_family_is_ipv6(ipp_copy.ip.family)) { + struct sockaddr_in6 *const addr6 = (struct sockaddr_in6 *)&addr.addr; + + addr.size = sizeof(struct sockaddr_in6); + addr6->sin6_family = AF_INET6; + addr6->sin6_port = ipp_copy.port; + fill_addr6(&ipp_copy.ip.ip.v6, &addr6->sin6_addr); + + addr6->sin6_flowinfo = 0; + addr6->sin6_scope_id = 0; + } else { + LOGGER_ERROR(net->log, "unknown address type: %d", ipp_copy.ip.family.value); + return -1; + } + + const long res = net_sendto(net->ns, net->sock, packet.data, packet.length, &addr, &ipp_copy); + loglogdata(net->log, "O=>", packet.data, packet.length, ip_port, res); + + assert(res <= INT_MAX); + return (int)res; +} + +/** + * Function to send packet(data) of length length to ip_port. + * + * @deprecated Use send_packet instead. + */ +int sendpacket(const Networking_Core *net, const IP_Port *ip_port, const uint8_t *data, uint16_t length) +{ + const Packet packet = {data, length}; + return send_packet(net, ip_port, packet); +} + +/** @brief Function to receive data + * ip and port of sender is put into ip_port. + * Packet data is put into data. + * Packet length is put into length. + */ +non_null() +static int receivepacket(const Network *ns, const Logger *log, Socket sock, IP_Port *ip_port, uint8_t *data, uint32_t *length) +{ + memset(ip_port, 0, sizeof(IP_Port)); + Network_Addr addr = {{0}}; + addr.size = sizeof(addr.addr); + *length = 0; + + const int fail_or_len = net_recvfrom(ns, sock, data, MAX_UDP_PACKET_SIZE, &addr); + + if (fail_or_len < 0) { + const int error = net_error(); + + if (!should_ignore_recv_error(error)) { + char *strerror = net_new_strerror(error); + LOGGER_ERROR(log, "unexpected error reading from socket: %u, %s", error, strerror); + net_kill_strerror(strerror); + } + + return -1; /* Nothing received. */ + } + + *length = (uint32_t)fail_or_len; + + if (addr.addr.ss_family == AF_INET) { + const struct sockaddr_in *addr_in = (const struct sockaddr_in *)&addr.addr; + + const Family *const family = make_tox_family(addr_in->sin_family); + assert(family != nullptr); + + if (family == nullptr) { + return -1; + } + + ip_port->ip.family = *family; + get_ip4(&ip_port->ip.ip.v4, &addr_in->sin_addr); + ip_port->port = addr_in->sin_port; + } else if (addr.addr.ss_family == AF_INET6) { + const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)&addr.addr; + const Family *const family = make_tox_family(addr_in6->sin6_family); + assert(family != nullptr); + + if (family == nullptr) { + return -1; + } + + ip_port->ip.family = *family; + get_ip6(&ip_port->ip.ip.v6, &addr_in6->sin6_addr); + ip_port->port = addr_in6->sin6_port; + + if (ipv6_ipv4_in_v6(&ip_port->ip.ip.v6)) { + ip_port->ip.family = net_family_ipv4(); + ip_port->ip.ip.v4.uint32 = ip_port->ip.ip.v6.uint32[3]; + } + } else { + return -1; + } + + loglogdata(log, "=>O", data, MAX_UDP_PACKET_SIZE, ip_port, *length); + + return 0; +} + +void networking_registerhandler(Networking_Core *net, uint8_t byte, packet_handler_cb *cb, void *object) +{ + net->packethandlers[byte].function = cb; + net->packethandlers[byte].object = object; +} + +void networking_poll(const Networking_Core *net, void *userdata) +{ + if (net_family_is_unspec(net->family)) { + /* Socket not initialized */ + return; + } + + IP_Port ip_port; + uint8_t data[MAX_UDP_PACKET_SIZE]; + uint32_t length; + + while (receivepacket(net->ns, net->log, net->sock, &ip_port, data, &length) != -1) { + if (length < 1) { + continue; + } + + const Packet_Handler *const handler = &net->packethandlers[data[0]]; + + if (handler->function == nullptr) { + // TODO(https://github.com/TokTok/c-toxcore/issues/1115): Make this + // a warning or error again. + LOGGER_DEBUG(net->log, "[%02u] -- Packet has no handler", data[0]); + continue; + } + + handler->function(handler->object, &ip_port, data, length, userdata); + } +} + +/** @brief Initialize networking. + * Bind to ip and port. + * ip must be in network order EX: 127.0.0.1 = (7F000001). + * port is in host byte order (this means don't worry about it). + * + * @return Networking_Core object if no problems + * @retval NULL if there are problems. + * + * If error is non NULL it is set to 0 if no issues, 1 if socket related error, 2 if other. + */ +Networking_Core *new_networking_ex( + const Logger *log, const Network *ns, const IP *ip, + uint16_t port_from, uint16_t port_to, unsigned int *error) +{ + /* If both from and to are 0, use default port range + * If one is 0 and the other is non-0, use the non-0 value as only port + * If from > to, swap + */ + if (port_from == 0 && port_to == 0) { + port_from = TOX_PORTRANGE_FROM; + port_to = TOX_PORTRANGE_TO; + } else if (port_from == 0 && port_to != 0) { + port_from = port_to; + } else if (port_from != 0 && port_to == 0) { + port_to = port_from; + } else if (port_from > port_to) { + const uint16_t temp_port = port_from; + port_from = port_to; + port_to = temp_port; + } + + if (error != nullptr) { + *error = 2; + } + + /* maybe check for invalid IPs like 224+.x.y.z? if there is any IP set ever */ + if (!net_family_is_ipv4(ip->family) && !net_family_is_ipv6(ip->family)) { + LOGGER_ERROR(log, "invalid address family: %u", ip->family.value); + return nullptr; + } + + Networking_Core *temp = (Networking_Core *)calloc(1, sizeof(Networking_Core)); + + if (temp == nullptr) { + return nullptr; + } + + temp->ns = ns; + temp->log = log; + temp->family = ip->family; + temp->port = 0; + + /* Initialize our socket. */ + /* add log message what we're creating */ + temp->sock = net_socket(ns, temp->family, TOX_SOCK_DGRAM, TOX_PROTO_UDP); + + /* Check for socket error. */ + if (!sock_valid(temp->sock)) { + const int neterror = net_error(); + char *strerror = net_new_strerror(neterror); + LOGGER_ERROR(log, "failed to get a socket?! %d, %s", neterror, strerror); + net_kill_strerror(strerror); + free(temp); + + if (error != nullptr) { + *error = 1; + } + + return nullptr; + } + + /* Functions to increase the size of the send and receive UDP buffers. + */ + int n = 1024 * 1024 * 2; + + if (net_setsockopt(ns, temp->sock, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) != 0) { + LOGGER_ERROR(log, "failed to set socket option %d", SO_RCVBUF); + } + + if (net_setsockopt(ns, temp->sock, SOL_SOCKET, SO_SNDBUF, &n, sizeof(n)) != 0) { + LOGGER_ERROR(log, "failed to set socket option %d", SO_SNDBUF); + } + + /* Enable broadcast on socket */ + int broadcast = 1; + + if (net_setsockopt(ns, temp->sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)) != 0) { + LOGGER_ERROR(log, "failed to set socket option %d", SO_BROADCAST); + } + + /* iOS UDP sockets are weird and apparently can SIGPIPE */ + if (!set_socket_nosigpipe(ns, temp->sock)) { + kill_networking(temp); + + if (error != nullptr) { + *error = 1; + } + + return nullptr; + } + + /* Set socket nonblocking. */ + if (!set_socket_nonblock(ns, temp->sock)) { + kill_networking(temp); + + if (error != nullptr) { + *error = 1; + } + + return nullptr; + } + + /* Bind our socket to port PORT and the given IP address (usually 0.0.0.0 or ::) */ + uint16_t *portptr = nullptr; + Network_Addr addr; + + memset(&addr.addr, 0, sizeof(struct sockaddr_storage)); + + if (net_family_is_ipv4(temp->family)) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr.addr; + + addr.size = sizeof(struct sockaddr_in); + addr4->sin_family = AF_INET; + addr4->sin_port = 0; + fill_addr4(&ip->ip.v4, &addr4->sin_addr); + + portptr = &addr4->sin_port; + } else if (net_family_is_ipv6(temp->family)) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr.addr; + + addr.size = sizeof(struct sockaddr_in6); + addr6->sin6_family = AF_INET6; + addr6->sin6_port = 0; + fill_addr6(&ip->ip.v6, &addr6->sin6_addr); + + addr6->sin6_flowinfo = 0; + addr6->sin6_scope_id = 0; + + portptr = &addr6->sin6_port; + } else { + free(temp); + return nullptr; + } + + if (net_family_is_ipv6(ip->family)) { + const bool is_dualstack = set_socket_dualstack(ns, temp->sock); + + if (is_dualstack) { + LOGGER_TRACE(log, "Dual-stack socket: enabled"); + } else { + LOGGER_ERROR(log, "Dual-stack socket failed to enable, won't be able to receive from/send to IPv4 addresses"); + } + +#ifndef ESP_PLATFORM + /* multicast local nodes */ + struct ipv6_mreq mreq; + memset(&mreq, 0, sizeof(mreq)); + mreq.ipv6mr_multiaddr.s6_addr[ 0] = 0xFF; + mreq.ipv6mr_multiaddr.s6_addr[ 1] = 0x02; + mreq.ipv6mr_multiaddr.s6_addr[15] = 0x01; + mreq.ipv6mr_interface = 0; + + const int res = net_setsockopt(ns, temp->sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + + int neterror = net_error(); + char *strerror = net_new_strerror(neterror); + + if (res < 0) { + LOGGER_INFO(log, "Failed to activate local multicast membership in FF02::1. (%d, %s)", neterror, strerror); + } else { + LOGGER_TRACE(log, "Local multicast group joined successfully. (%d, %s)", neterror, strerror); + } + + net_kill_strerror(strerror); +#endif + } + + /* A hanging program or a different user might block the standard port. + * As long as it isn't a parameter coming from the commandline, + * try a few ports after it, to see if we can find a "free" one. + * + * If we go on without binding, the first sendto() automatically binds to + * a free port chosen by the system (i.e. anything from 1024 to 65535). + * + * Returning NULL after bind fails has both advantages and disadvantages: + * advantage: + * we can rely on getting the port in the range 33445..33450, which + * enables us to tell joe user to open their firewall to a small range + * + * disadvantage: + * some clients might not test return of tox_new(), blindly assuming that + * it worked ok (which it did previously without a successful bind) + */ + uint16_t port_to_try = port_from; + *portptr = net_htons(port_to_try); + + for (uint16_t tries = port_from; tries <= port_to; ++tries) { + const int res = net_bind(ns, temp->sock, &addr); + + if (res == 0) { + temp->port = *portptr; + + Ip_Ntoa ip_str; + LOGGER_DEBUG(log, "Bound successfully to %s:%u", net_ip_ntoa(ip, &ip_str), + net_ntohs(temp->port)); + + /* errno isn't reset on success, only set on failure, the failed + * binds with parallel clients yield a -EPERM to the outside if + * errno isn't cleared here */ + if (tries > 0) { + errno = 0; + } + + if (error != nullptr) { + *error = 0; + } + + return temp; + } + + ++port_to_try; + + if (port_to_try > port_to) { + port_to_try = port_from; + } + + *portptr = net_htons(port_to_try); + } + + Ip_Ntoa ip_str; + int neterror = net_error(); + char *strerror = net_new_strerror(neterror); + LOGGER_ERROR(log, "failed to bind socket: %d, %s IP: %s port_from: %u port_to: %u", neterror, strerror, + net_ip_ntoa(ip, &ip_str), port_from, port_to); + net_kill_strerror(strerror); + kill_networking(temp); + + if (error != nullptr) { + *error = 1; + } + + return nullptr; +} + +Networking_Core *new_networking_no_udp(const Logger *log, const Network *ns) +{ + /* this is the easiest way to completely disable UDP without changing too much code. */ + Networking_Core *net = (Networking_Core *)calloc(1, sizeof(Networking_Core)); + + if (net == nullptr) { + return nullptr; + } + + net->ns = ns; + net->log = log; + + return net; +} + +/** Function to cleanup networking stuff (doesn't do much right now). */ +void kill_networking(Networking_Core *net) +{ + if (net == nullptr) { + return; + } + + if (!net_family_is_unspec(net->family)) { + /* Socket is initialized, so we close it. */ + kill_sock(net->ns, net->sock); + } + + free(net); +} + + +bool ip_equal(const IP *a, const IP *b) +{ + if (a == nullptr || b == nullptr) { + return false; + } + + /* same family */ + if (a->family.value == b->family.value) { + if (net_family_is_ipv4(a->family) || net_family_is_tcp_ipv4(a->family)) { + struct in_addr addr_a; + struct in_addr addr_b; + fill_addr4(&a->ip.v4, &addr_a); + fill_addr4(&b->ip.v4, &addr_b); + return addr_a.s_addr == addr_b.s_addr; + } + + if (net_family_is_ipv6(a->family) || net_family_is_tcp_ipv6(a->family)) { + return a->ip.v6.uint64[0] == b->ip.v6.uint64[0] && + a->ip.v6.uint64[1] == b->ip.v6.uint64[1]; + } + + return false; + } + + /* different family: check on the IPv6 one if it is the IPv4 one embedded */ + if (net_family_is_ipv4(a->family) && net_family_is_ipv6(b->family)) { + if (ipv6_ipv4_in_v6(&b->ip.v6)) { + struct in_addr addr_a; + fill_addr4(&a->ip.v4, &addr_a); + return addr_a.s_addr == b->ip.v6.uint32[3]; + } + } else if (net_family_is_ipv6(a->family) && net_family_is_ipv4(b->family)) { + if (ipv6_ipv4_in_v6(&a->ip.v6)) { + struct in_addr addr_b; + fill_addr4(&b->ip.v4, &addr_b); + return a->ip.v6.uint32[3] == addr_b.s_addr; + } + } + + return false; +} + +bool ipport_equal(const IP_Port *a, const IP_Port *b) +{ + if (a == nullptr || b == nullptr) { + return false; + } + + if (a->port == 0 || (a->port != b->port)) { + return false; + } + + return ip_equal(&a->ip, &b->ip); +} + +/** nulls out ip */ +void ip_reset(IP *ip) +{ + if (ip == nullptr) { + return; + } + + *ip = empty_ip; +} + +/** nulls out ip_port */ +void ipport_reset(IP_Port *ipport) +{ + if (ipport == nullptr) { + return; + } + + *ipport = empty_ip_port; +} + +/** nulls out ip, sets family according to flag */ +void ip_init(IP *ip, bool ipv6enabled) +{ + if (ip == nullptr) { + return; + } + + *ip = empty_ip; + ip->family = ipv6enabled ? net_family_ipv6() : net_family_ipv4(); +} + +/** checks if ip is valid */ +bool ip_isset(const IP *ip) +{ + if (ip == nullptr) { + return false; + } + + return !net_family_is_unspec(ip->family); +} + +/** checks if ip is valid */ +bool ipport_isset(const IP_Port *ipport) +{ + if (ipport == nullptr) { + return false; + } + + if (ipport->port == 0) { + return false; + } + + return ip_isset(&ipport->ip); +} + +/** copies an ip structure (careful about direction) */ +void ip_copy(IP *target, const IP *source) +{ + if (source == nullptr || target == nullptr) { + return; + } + + *target = *source; +} + +/** copies an ip_port structure (careful about direction) */ +void ipport_copy(IP_Port *target, const IP_Port *source) +{ + if (source == nullptr || target == nullptr) { + return; + } + + *target = *source; +} + +/** @brief Converts IP into a string. + * + * Writes error message into the buffer on error. + * + * @param ip_str contains a buffer of the required size. + * + * @return Pointer to the buffer inside `ip_str` containing the IP string. + */ +const char *net_ip_ntoa(const IP *ip, Ip_Ntoa *ip_str) +{ + assert(ip_str != nullptr); + + if (ip == nullptr) { + snprintf(ip_str->buf, sizeof(ip_str->buf), "(IP invalid: NULL)"); + return ip_str->buf; + } + + if (!ip_parse_addr(ip, ip_str->buf, sizeof(ip_str->buf))) { + snprintf(ip_str->buf, sizeof(ip_str->buf), "(IP invalid, family %u)", ip->family.value); + return ip_str->buf; + } + + /* brute force protection against lacking termination */ + ip_str->buf[sizeof(ip_str->buf) - 1] = '\0'; + return ip_str->buf; +} + +bool ip_parse_addr(const IP *ip, char *address, size_t length) +{ + if (address == nullptr || ip == nullptr) { + return false; + } + + if (net_family_is_ipv4(ip->family)) { + struct in_addr addr; + assert(make_family(ip->family) == AF_INET); + fill_addr4(&ip->ip.v4, &addr); + return inet_ntop4(&addr, address, length) != nullptr; + } + + if (net_family_is_ipv6(ip->family)) { + struct in6_addr addr; + assert(make_family(ip->family) == AF_INET6); + fill_addr6(&ip->ip.v6, &addr); + return inet_ntop6(&addr, address, length) != nullptr; + } + + return false; +} + +bool addr_parse_ip(const char *address, IP *to) +{ + if (address == nullptr || to == nullptr) { + return false; + } + + struct in_addr addr4; + + if (inet_pton4(address, &addr4) == 1) { + to->family = net_family_ipv4(); + get_ip4(&to->ip.v4, &addr4); + return true; + } + + struct in6_addr addr6; + + if (inet_pton6(address, &addr6) == 1) { + to->family = net_family_ipv6(); + get_ip6(&to->ip.v6, &addr6); + return true; + } + + return false; +} + +/** addr_resolve return values */ +#define TOX_ADDR_RESOLVE_INET 1 +#define TOX_ADDR_RESOLVE_INET6 2 + +/** + * Uses getaddrinfo to resolve an address into an IP address. + * + * Uses the first IPv4/IPv6 addresses returned by getaddrinfo. + * + * @param address a hostname (or something parseable to an IP address) + * @param to to.family MUST be initialized, either set to a specific IP version + * (TOX_AF_INET/TOX_AF_INET6) or to the unspecified TOX_AF_UNSPEC (0), if both + * IP versions are acceptable + * @param extra can be NULL and is only set in special circumstances, see returns + * + * Returns in `*to` a valid IPAny (v4/v6), + * prefers v6 if `ip.family` was TOX_AF_UNSPEC and both available + * Returns in `*extra` an IPv4 address, if family was TOX_AF_UNSPEC and `*to` is TOX_AF_INET6 + * + * @return 0 on failure, `TOX_ADDR_RESOLVE_*` on success. + */ +non_null(1, 2, 3) nullable(4) +static int addr_resolve(const Network *ns, const char *address, IP *to, IP *extra) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if ((true)) { + return 0; + } +#endif + + if (address == nullptr || to == nullptr) { + return 0; + } + + const Family tox_family = to->family; + const int family = make_family(tox_family); + + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; // type of socket Tox uses. + + struct addrinfo *server = nullptr; + + const int rc = getaddrinfo(address, nullptr, &hints, &server); + + // Lookup failed. + if (rc != 0) { + return 0; + } + + IP ip4; + ip_init(&ip4, false); // ipv6enabled = false + IP ip6; + ip_init(&ip6, true); // ipv6enabled = true + + int result = 0; + bool done = false; + + for (struct addrinfo *walker = server; walker != nullptr && !done; walker = walker->ai_next) { + switch (walker->ai_family) { + case AF_INET: { + if (walker->ai_family == family) { /* AF_INET requested, done */ + const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)walker->ai_addr; + get_ip4(&to->ip.v4, &addr->sin_addr); + result = TOX_ADDR_RESOLVE_INET; + done = true; + } else if ((result & TOX_ADDR_RESOLVE_INET) == 0) { /* AF_UNSPEC requested, store away */ + const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)walker->ai_addr; + get_ip4(&ip4.ip.v4, &addr->sin_addr); + result |= TOX_ADDR_RESOLVE_INET; + } + + break; /* switch */ + } + + case AF_INET6: { + if (walker->ai_family == family) { /* AF_INET6 requested, done */ + if (walker->ai_addrlen == sizeof(struct sockaddr_in6)) { + const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(void *)walker->ai_addr; + get_ip6(&to->ip.v6, &addr->sin6_addr); + result = TOX_ADDR_RESOLVE_INET6; + done = true; + } + } else if ((result & TOX_ADDR_RESOLVE_INET6) == 0) { /* AF_UNSPEC requested, store away */ + if (walker->ai_addrlen == sizeof(struct sockaddr_in6)) { + const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(void *)walker->ai_addr; + get_ip6(&ip6.ip.v6, &addr->sin6_addr); + result |= TOX_ADDR_RESOLVE_INET6; + } + } + + break; /* switch */ + } + } + } + + if (family == AF_UNSPEC) { + if ((result & TOX_ADDR_RESOLVE_INET6) != 0) { + ip_copy(to, &ip6); + + if ((result & TOX_ADDR_RESOLVE_INET) != 0 && (extra != nullptr)) { + ip_copy(extra, &ip4); + } + } else if ((result & TOX_ADDR_RESOLVE_INET) != 0) { + ip_copy(to, &ip4); + } else { + result = 0; + } + } + + freeaddrinfo(server); + return result; +} + +bool addr_resolve_or_parse_ip(const Network *ns, const char *address, IP *to, IP *extra) +{ + if (addr_resolve(ns, address, to, extra) == 0) { + if (!addr_parse_ip(address, to)) { + return false; + } + } + + return true; +} + +bool net_connect(const Logger *log, Socket sock, const IP_Port *ip_port) +{ + struct sockaddr_storage addr = {0}; + size_t addrsize; + + if (net_family_is_ipv4(ip_port->ip.family)) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; + + addrsize = sizeof(struct sockaddr_in); + addr4->sin_family = AF_INET; + fill_addr4(&ip_port->ip.ip.v4, &addr4->sin_addr); + addr4->sin_port = ip_port->port; + } else if (net_family_is_ipv6(ip_port->ip.family)) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; + + addrsize = sizeof(struct sockaddr_in6); + addr6->sin6_family = AF_INET6; + fill_addr6(&ip_port->ip.ip.v6, &addr6->sin6_addr); + addr6->sin6_port = ip_port->port; + } else { + Ip_Ntoa ip_str; + LOGGER_ERROR(log, "cannot connect to %s:%d which is neither IPv4 nor IPv6", + net_ip_ntoa(&ip_port->ip, &ip_str), ip_port->port); + return false; + } + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if ((true)) { + return true; + } +#endif + + Ip_Ntoa ip_str; + LOGGER_DEBUG(log, "connecting socket %d to %s:%d", + (int)sock.sock, net_ip_ntoa(&ip_port->ip, &ip_str), net_ntohs(ip_port->port)); + errno = 0; + + if (connect(sock.sock, (struct sockaddr *)&addr, addrsize) == -1) { + const int error = net_error(); + + // Non-blocking socket: "Operation in progress" means it's connecting. + if (!should_ignore_connect_error(error)) { + char *net_strerror = net_new_strerror(error); + LOGGER_ERROR(log, "failed to connect to %s:%d: %d (%s)", + net_ip_ntoa(&ip_port->ip, &ip_str), ip_port->port, error, net_strerror); + net_kill_strerror(net_strerror); + return false; + } + } + + return true; +} + +int32_t net_getipport(const char *node, IP_Port **res, int tox_type) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if ((true)) { + *res = (IP_Port *)calloc(1, sizeof(IP_Port)); + assert(*res != nullptr); + IP_Port *ip_port = *res; + ip_port->ip.ip.v4.uint32 = 0x7F000003; // 127.0.0.3 + ip_port->ip.family = *make_tox_family(AF_INET); + + return 1; + } +#endif + + // Try parsing as IP address first. + IP_Port parsed = {{{0}}}; + + if (addr_parse_ip(node, &parsed.ip)) { + IP_Port *tmp = (IP_Port *)calloc(1, sizeof(IP_Port)); + + if (tmp == nullptr) { + return -1; + } + + tmp[0] = parsed; + *res = tmp; + return 1; + } + + // It's not an IP address, so now we try doing a DNS lookup. + struct addrinfo *infos; + const int ret = getaddrinfo(node, nullptr, nullptr, &infos); + *res = nullptr; + + if (ret != 0) { + return -1; + } + + // Used to avoid calloc parameter overflow + const size_t max_count = min_u64(SIZE_MAX, INT32_MAX) / sizeof(IP_Port); + const int type = make_socktype(tox_type); + size_t count = 0; + + for (struct addrinfo *cur = infos; count < max_count && cur != nullptr; cur = cur->ai_next) { + if (cur->ai_socktype && type > 0 && cur->ai_socktype != type) { + continue; + } + + if (cur->ai_family != AF_INET && cur->ai_family != AF_INET6) { + continue; + } + + ++count; + } + + assert(count <= max_count); + + if (count == 0) { + freeaddrinfo(infos); + return 0; + } + + *res = (IP_Port *)calloc(count, sizeof(IP_Port)); + + if (*res == nullptr) { + freeaddrinfo(infos); + return -1; + } + + IP_Port *ip_port = *res; + + for (struct addrinfo *cur = infos; cur != nullptr; cur = cur->ai_next) { + if (cur->ai_socktype && type > 0 && cur->ai_socktype != type) { + continue; + } + + if (cur->ai_family == AF_INET) { + const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)cur->ai_addr; + memcpy(&ip_port->ip.ip.v4, &addr->sin_addr, sizeof(IP4)); + } else if (cur->ai_family == AF_INET6) { + const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cur->ai_addr; + memcpy(&ip_port->ip.ip.v6, &addr->sin6_addr, sizeof(IP6)); + } else { + continue; + } + + const Family *const family = make_tox_family(cur->ai_family); + assert(family != nullptr); + + if (family == nullptr) { + freeaddrinfo(infos); + return -1; + } + + ip_port->ip.family = *family; + + ++ip_port; + } + + freeaddrinfo(infos); + + return count; +} + +void net_freeipport(IP_Port *ip_ports) +{ + free(ip_ports); +} + +bool bind_to_port(const Network *ns, Socket sock, Family family, uint16_t port) +{ + Network_Addr addr = {{0}}; + + if (net_family_is_ipv4(family)) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr.addr; + + addr.size = sizeof(struct sockaddr_in); + addr4->sin_family = AF_INET; + addr4->sin_port = net_htons(port); + } else if (net_family_is_ipv6(family)) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr.addr; + + addr.size = sizeof(struct sockaddr_in6); + addr6->sin6_family = AF_INET6; + addr6->sin6_port = net_htons(port); + } else { + return false; + } + + return net_bind(ns, sock, &addr) == 0; +} + +Socket net_socket(const Network *ns, Family domain, int type, int protocol) +{ + const int platform_domain = make_family(domain); + const int platform_type = make_socktype(type); + const int platform_prot = make_proto(protocol); + const Socket sock = {ns->funcs->socket(ns->obj, platform_domain, platform_type, platform_prot)}; + return sock; +} + +uint16_t net_socket_data_recv_buffer(const Network *ns, Socket sock) +{ + const int count = ns->funcs->recvbuf(ns->obj, sock.sock); + return (uint16_t)max_s32(0, min_s32(count, UINT16_MAX)); +} + +uint32_t net_htonl(uint32_t hostlong) +{ + return htonl(hostlong); +} + +uint16_t net_htons(uint16_t hostshort) +{ + return htons(hostshort); +} + +uint32_t net_ntohl(uint32_t hostlong) +{ + return ntohl(hostlong); +} + +uint16_t net_ntohs(uint16_t hostshort) +{ + return ntohs(hostshort); +} + +size_t net_pack_u16(uint8_t *bytes, uint16_t v) +{ + bytes[0] = (v >> 8) & 0xff; + bytes[1] = v & 0xff; + return sizeof(v); +} + +size_t net_pack_u32(uint8_t *bytes, uint32_t v) +{ + uint8_t *p = bytes; + p += net_pack_u16(p, (v >> 16) & 0xffff); + p += net_pack_u16(p, v & 0xffff); + return p - bytes; +} + +size_t net_pack_u64(uint8_t *bytes, uint64_t v) +{ + uint8_t *p = bytes; + p += net_pack_u32(p, (v >> 32) & 0xffffffff); + p += net_pack_u32(p, v & 0xffffffff); + return p - bytes; +} + +size_t net_unpack_u16(const uint8_t *bytes, uint16_t *v) +{ + const uint8_t hi = bytes[0]; + const uint8_t lo = bytes[1]; + *v = ((uint16_t)hi << 8) | lo; + return sizeof(*v); +} + +size_t net_unpack_u32(const uint8_t *bytes, uint32_t *v) +{ + const uint8_t *p = bytes; + uint16_t hi; + uint16_t lo; + p += net_unpack_u16(p, &hi); + p += net_unpack_u16(p, &lo); + *v = ((uint32_t)hi << 16) | lo; + return p - bytes; +} + +size_t net_unpack_u64(const uint8_t *bytes, uint64_t *v) +{ + const uint8_t *p = bytes; + uint32_t hi; + uint32_t lo; + p += net_unpack_u32(p, &hi); + p += net_unpack_u32(p, &lo); + *v = ((uint64_t)hi << 32) | lo; + return p - bytes; +} + +bool ipv6_ipv4_in_v6(const IP6 *a) +{ + return a->uint64[0] == 0 && a->uint32[2] == net_htonl(0xffff); +} + +int net_error(void) +{ +#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) + return WSAGetLastError(); +#else + return errno; +#endif +} + +#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +char *net_new_strerror(int error) +{ + char *str = nullptr; + // Windows API is weird. The 5th function arg is of char* type, but we + // have to pass char** so that it could assign new memory block to our + // pointer, so we have to cast our char** to char* for the compilation + // not to fail (otherwise it would fail to find a variant of this function + // accepting char** as the 5th arg) and Windows inside casts it back + // to char** to do the assignment. So no, this cast you see here, although + // it looks weird, is not a mistake. + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, + error, 0, (char *)&str, 0, nullptr); + return str; +} +#else +#ifdef _GNU_SOURCE +non_null() +static const char *net_strerror_r(int error, char *tmp, size_t tmp_size) +{ + const char *retstr = strerror_r(error, tmp, tmp_size); + + if (errno != 0) { + snprintf(tmp, tmp_size, "error %d (strerror_r failed with errno %d)", error, errno); + } + + return retstr; +} +#else +non_null() +static const char *net_strerror_r(int error, char *tmp, size_t tmp_size) +{ + const int fmt_error = strerror_r(error, tmp, tmp_size); + + if (fmt_error != 0) { + snprintf(tmp, tmp_size, "error %d (strerror_r failed with error %d, errno %d)", error, fmt_error, errno); + } + + return tmp; +} +#endif +char *net_new_strerror(int error) +{ + char tmp[256]; + + errno = 0; + + const char *retstr = net_strerror_r(error, tmp, sizeof(tmp)); + const size_t retstr_len = strlen(retstr); + + char *str = (char *)malloc(retstr_len + 1); + + if (str == nullptr) { + return nullptr; + } + + memcpy(str, retstr, retstr_len + 1); + + return str; +} +#endif + +void net_kill_strerror(char *strerror) +{ +#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) + LocalFree((char *)strerror); +#else + free(strerror); +#endif +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/onion.h b/local_pod_repo/toxcore/toxcore/toxcore/onion.h new file mode 100644 index 0000000..3da2137 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/onion.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Implementation of the onion part of docs/Prevent_Tracking.txt + */ +#ifndef C_TOXCORE_TOXCORE_ONION_H +#define C_TOXCORE_TOXCORE_ONION_H + +#include "DHT.h" +#include "logger.h" +#include "mono_time.h" + +typedef int onion_recv_1_cb(void *object, const IP_Port *dest, const uint8_t *data, uint16_t length); + +typedef struct Onion { + const Logger *log; + const Mono_Time *mono_time; + const Random *rng; + DHT *dht; + Networking_Core *net; + uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; + uint64_t timestamp; + + Shared_Keys shared_keys_1; + Shared_Keys shared_keys_2; + Shared_Keys shared_keys_3; + + onion_recv_1_cb *recv_1_function; + void *callback_object; +} Onion; + +#define ONION_MAX_PACKET_SIZE 1400 + +#define ONION_RETURN_1 (CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_MAC_SIZE) +#define ONION_RETURN_2 (CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_MAC_SIZE + ONION_RETURN_1) +#define ONION_RETURN_3 (CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_MAC_SIZE + ONION_RETURN_2) + +#define ONION_SEND_BASE (CRYPTO_PUBLIC_KEY_SIZE + SIZE_IPPORT + CRYPTO_MAC_SIZE) +#define ONION_SEND_3 (CRYPTO_NONCE_SIZE + ONION_SEND_BASE + ONION_RETURN_2) +#define ONION_SEND_2 (CRYPTO_NONCE_SIZE + ONION_SEND_BASE*2 + ONION_RETURN_1) +#define ONION_SEND_1 (CRYPTO_NONCE_SIZE + ONION_SEND_BASE*3) + +#define ONION_MAX_DATA_SIZE (ONION_MAX_PACKET_SIZE - (ONION_SEND_1 + 1)) +#define ONION_RESPONSE_MAX_DATA_SIZE (ONION_MAX_PACKET_SIZE - (1 + ONION_RETURN_3)) + +#define ONION_PATH_LENGTH 3 + +typedef struct Onion_Path { + uint8_t shared_key1[CRYPTO_SHARED_KEY_SIZE]; + uint8_t shared_key2[CRYPTO_SHARED_KEY_SIZE]; + uint8_t shared_key3[CRYPTO_SHARED_KEY_SIZE]; + + uint8_t public_key1[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t public_key2[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t public_key3[CRYPTO_PUBLIC_KEY_SIZE]; + + IP_Port ip_port1; + uint8_t node_public_key1[CRYPTO_PUBLIC_KEY_SIZE]; + + IP_Port ip_port2; + uint8_t node_public_key2[CRYPTO_PUBLIC_KEY_SIZE]; + + IP_Port ip_port3; + uint8_t node_public_key3[CRYPTO_PUBLIC_KEY_SIZE]; + + uint32_t path_num; +} Onion_Path; + +/** @brief Create a new onion path. + * + * Create a new onion path out of nodes (nodes is a list of ONION_PATH_LENGTH nodes) + * + * new_path must be an empty memory location of at least Onion_Path size. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int create_onion_path(const Random *rng, const DHT *dht, Onion_Path *new_path, const Node_format *nodes); + +/** @brief Dump nodes in onion path to nodes of length num_nodes. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int onion_path_to_nodes(Node_format *nodes, unsigned int num_nodes, const Onion_Path *path); + +/** @brief Create a onion packet. + * + * Use Onion_Path path to create packet for data of length to dest. + * Maximum length of data is ONION_MAX_DATA_SIZE. + * packet should be at least ONION_MAX_PACKET_SIZE big. + * + * return -1 on failure. + * return length of created packet on success. + */ +non_null() +int create_onion_packet(const Random *rng, uint8_t *packet, uint16_t max_packet_length, + const Onion_Path *path, const IP_Port *dest, + const uint8_t *data, uint16_t length); + + +/** @brief Create a onion packet to be sent over tcp. + * + * Use Onion_Path path to create packet for data of length to dest. + * Maximum length of data is ONION_MAX_DATA_SIZE. + * packet should be at least ONION_MAX_PACKET_SIZE big. + * + * return -1 on failure. + * return length of created packet on success. + */ +non_null() +int create_onion_packet_tcp(const Random *rng, uint8_t *packet, uint16_t max_packet_length, + const Onion_Path *path, const IP_Port *dest, + const uint8_t *data, uint16_t length); + +/** @brief Create and send a onion response sent initially to dest with. + * Maximum length of data is ONION_RESPONSE_MAX_DATA_SIZE. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int send_onion_response(const Networking_Core *net, const IP_Port *dest, const uint8_t *data, uint16_t length, + const uint8_t *ret); + +/** @brief Function to handle/send received decrypted versions of the packet created by create_onion_packet. + * + * return 0 on success. + * return 1 on failure. + * + * Used to handle these packets that are received in a non traditional way (by TCP for example). + * + * Source family must be set to something else than TOX_AF_INET6 or TOX_AF_INET so that the callback gets called + * when the response is received. + */ +non_null() +int onion_send_1(const Onion *onion, const uint8_t *plain, uint16_t len, const IP_Port *source, const uint8_t *nonce); + +/** Set the callback to be called when the dest ip_port doesn't have TOX_AF_INET6 or TOX_AF_INET as the family. */ +non_null(1) nullable(2, 3) +void set_callback_handle_recv_1(Onion *onion, onion_recv_1_cb *function, void *object); + +non_null() +Onion *new_onion(const Logger *log, const Mono_Time *mono_time, const Random *rng, DHT *dht); + +nullable(1) +void kill_onion(Onion *onion); + + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/onion.m b/local_pod_repo/toxcore/toxcore/toxcore/onion.m new file mode 100644 index 0000000..d7e3f02 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/onion.m @@ -0,0 +1,695 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Implementation of the onion part of docs/Prevent_Tracking.txt + */ +#include "onion.h" + +#include +#include +#include + +#include "ccompat.h" +#include "mono_time.h" +#include "util.h" + +#define RETURN_1 ONION_RETURN_1 +#define RETURN_2 ONION_RETURN_2 +#define RETURN_3 ONION_RETURN_3 + +#define SEND_BASE ONION_SEND_BASE +#define SEND_3 ONION_SEND_3 +#define SEND_2 ONION_SEND_2 +#define SEND_1 ONION_SEND_1 + +#define KEY_REFRESH_INTERVAL (2 * 60 * 60) + +/** Change symmetric keys every 2 hours to make paths expire eventually. */ +non_null() +static void change_symmetric_key(Onion *onion) +{ + if (mono_time_is_timeout(onion->mono_time, onion->timestamp, KEY_REFRESH_INTERVAL)) { + new_symmetric_key(onion->rng, onion->secret_symmetric_key); + onion->timestamp = mono_time_get(onion->mono_time); + } +} + +/** packing and unpacking functions */ +non_null() +static void ip_pack(uint8_t *data, const IP *source) +{ + data[0] = source->family.value; + + if (net_family_is_ipv4(source->family) || net_family_is_tox_tcp_ipv4(source->family)) { + memset(data + 1, 0, SIZE_IP6); + memcpy(data + 1, source->ip.v4.uint8, SIZE_IP4); + } else { + memcpy(data + 1, source->ip.v6.uint8, SIZE_IP6); + } +} + +/** return 0 on success, -1 on failure. */ +non_null() +static int ip_unpack(IP *target, const uint8_t *data, unsigned int data_size, bool disable_family_check) +{ + if (data_size < (1 + SIZE_IP6)) { + return -1; + } + + // TODO(iphydf): Validate input. + target->family.value = data[0]; + + if (net_family_is_ipv4(target->family) || net_family_is_tox_tcp_ipv4(target->family)) { + memcpy(target->ip.v4.uint8, data + 1, SIZE_IP4); + } else { + memcpy(target->ip.v6.uint8, data + 1, SIZE_IP6); + } + + const bool valid = disable_family_check || + net_family_is_ipv4(target->family) || + net_family_is_ipv6(target->family); + + return valid ? 0 : -1; +} + +non_null() +static void ipport_pack(uint8_t *data, const IP_Port *source) +{ + ip_pack(data, &source->ip); + memcpy(data + SIZE_IP, &source->port, SIZE_PORT); +} + +/** return 0 on success, -1 on failure. */ +non_null() +static int ipport_unpack(IP_Port *target, const uint8_t *data, unsigned int data_size, bool disable_family_check) +{ + if (data_size < (SIZE_IP + SIZE_PORT)) { + return -1; + } + + if (ip_unpack(&target->ip, data, data_size, disable_family_check) == -1) { + return -1; + } + + memcpy(&target->port, data + SIZE_IP, SIZE_PORT); + return 0; +} + + +/** @brief Create a new onion path. + * + * Create a new onion path out of nodes (nodes is a list of ONION_PATH_LENGTH nodes) + * + * new_path must be an empty memory location of at least Onion_Path size. + * + * return -1 on failure. + * return 0 on success. + */ +int create_onion_path(const Random *rng, const DHT *dht, Onion_Path *new_path, const Node_format *nodes) +{ + if (new_path == nullptr || nodes == nullptr) { + return -1; + } + + encrypt_precompute(nodes[0].public_key, dht_get_self_secret_key(dht), new_path->shared_key1); + memcpy(new_path->public_key1, dht_get_self_public_key(dht), CRYPTO_PUBLIC_KEY_SIZE); + + uint8_t random_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t random_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + crypto_new_keypair(rng, random_public_key, random_secret_key); + encrypt_precompute(nodes[1].public_key, random_secret_key, new_path->shared_key2); + memcpy(new_path->public_key2, random_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + crypto_new_keypair(rng, random_public_key, random_secret_key); + encrypt_precompute(nodes[2].public_key, random_secret_key, new_path->shared_key3); + memcpy(new_path->public_key3, random_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + crypto_memzero(random_secret_key, sizeof(random_secret_key)); + + new_path->ip_port1 = nodes[0].ip_port; + new_path->ip_port2 = nodes[1].ip_port; + new_path->ip_port3 = nodes[2].ip_port; + + memcpy(new_path->node_public_key1, nodes[0].public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(new_path->node_public_key2, nodes[1].public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(new_path->node_public_key3, nodes[2].public_key, CRYPTO_PUBLIC_KEY_SIZE); + + return 0; +} + +/** @brief Dump nodes in onion path to nodes of length num_nodes. + * + * return -1 on failure. + * return 0 on success. + */ +int onion_path_to_nodes(Node_format *nodes, unsigned int num_nodes, const Onion_Path *path) +{ + if (num_nodes < ONION_PATH_LENGTH) { + return -1; + } + + nodes[0].ip_port = path->ip_port1; + nodes[1].ip_port = path->ip_port2; + nodes[2].ip_port = path->ip_port3; + + memcpy(nodes[0].public_key, path->node_public_key1, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(nodes[1].public_key, path->node_public_key2, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(nodes[2].public_key, path->node_public_key3, CRYPTO_PUBLIC_KEY_SIZE); + return 0; +} + +/** @brief Create a onion packet. + * + * Use Onion_Path path to create packet for data of length to dest. + * Maximum length of data is ONION_MAX_DATA_SIZE. + * packet should be at least ONION_MAX_PACKET_SIZE big. + * + * return -1 on failure. + * return length of created packet on success. + */ +int create_onion_packet(const Random *rng, uint8_t *packet, uint16_t max_packet_length, + const Onion_Path *path, const IP_Port *dest, + const uint8_t *data, uint16_t length) +{ + if (1 + length + SEND_1 > max_packet_length || length == 0) { + return -1; + } + + VLA(uint8_t, step1, SIZE_IPPORT + length); + + ipport_pack(step1, dest); + memcpy(step1 + SIZE_IPPORT, data, length); + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_nonce(rng, nonce); + + VLA(uint8_t, step2, SIZE_IPPORT + SEND_BASE + length); + ipport_pack(step2, &path->ip_port3); + memcpy(step2 + SIZE_IPPORT, path->public_key3, CRYPTO_PUBLIC_KEY_SIZE); + + int len = encrypt_data_symmetric(path->shared_key3, nonce, step1, SIZEOF_VLA(step1), + step2 + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE); + + if (len != SIZE_IPPORT + length + CRYPTO_MAC_SIZE) { + return -1; + } + + VLA(uint8_t, step3, SIZE_IPPORT + SEND_BASE * 2 + length); + ipport_pack(step3, &path->ip_port2); + memcpy(step3 + SIZE_IPPORT, path->public_key2, CRYPTO_PUBLIC_KEY_SIZE); + len = encrypt_data_symmetric(path->shared_key2, nonce, step2, SIZEOF_VLA(step2), + step3 + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE); + + if (len != SIZE_IPPORT + SEND_BASE + length + CRYPTO_MAC_SIZE) { + return -1; + } + + packet[0] = NET_PACKET_ONION_SEND_INITIAL; + memcpy(packet + 1, nonce, CRYPTO_NONCE_SIZE); + memcpy(packet + 1 + CRYPTO_NONCE_SIZE, path->public_key1, CRYPTO_PUBLIC_KEY_SIZE); + + len = encrypt_data_symmetric(path->shared_key1, nonce, step3, SIZEOF_VLA(step3), + packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE); + + if (len != SIZE_IPPORT + SEND_BASE * 2 + length + CRYPTO_MAC_SIZE) { + return -1; + } + + return 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + len; +} + +/** @brief Create a onion packet to be sent over tcp. + * + * Use Onion_Path path to create packet for data of length to dest. + * Maximum length of data is ONION_MAX_DATA_SIZE. + * packet should be at least ONION_MAX_PACKET_SIZE big. + * + * return -1 on failure. + * return length of created packet on success. + */ +int create_onion_packet_tcp(const Random *rng, uint8_t *packet, uint16_t max_packet_length, + const Onion_Path *path, const IP_Port *dest, + const uint8_t *data, uint16_t length) +{ + if (CRYPTO_NONCE_SIZE + SIZE_IPPORT + SEND_BASE * 2 + length > max_packet_length || length == 0) { + return -1; + } + + VLA(uint8_t, step1, SIZE_IPPORT + length); + + ipport_pack(step1, dest); + memcpy(step1 + SIZE_IPPORT, data, length); + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_nonce(rng, nonce); + + VLA(uint8_t, step2, SIZE_IPPORT + SEND_BASE + length); + ipport_pack(step2, &path->ip_port3); + memcpy(step2 + SIZE_IPPORT, path->public_key3, CRYPTO_PUBLIC_KEY_SIZE); + + int len = encrypt_data_symmetric(path->shared_key3, nonce, step1, SIZEOF_VLA(step1), + step2 + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE); + + if (len != SIZE_IPPORT + length + CRYPTO_MAC_SIZE) { + return -1; + } + + ipport_pack(packet + CRYPTO_NONCE_SIZE, &path->ip_port2); + memcpy(packet + CRYPTO_NONCE_SIZE + SIZE_IPPORT, path->public_key2, CRYPTO_PUBLIC_KEY_SIZE); + len = encrypt_data_symmetric(path->shared_key2, nonce, step2, SIZEOF_VLA(step2), + packet + CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE); + + if (len != SIZE_IPPORT + SEND_BASE + length + CRYPTO_MAC_SIZE) { + return -1; + } + + memcpy(packet, nonce, CRYPTO_NONCE_SIZE); + + return CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE + len; +} + +/** @brief Create and send a onion response sent initially to dest with. + * Maximum length of data is ONION_RESPONSE_MAX_DATA_SIZE. + * + * return -1 on failure. + * return 0 on success. + */ +int send_onion_response(const Networking_Core *net, const IP_Port *dest, const uint8_t *data, uint16_t length, + const uint8_t *ret) +{ + if (length > ONION_RESPONSE_MAX_DATA_SIZE || length == 0) { + return -1; + } + + VLA(uint8_t, packet, 1 + RETURN_3 + length); + packet[0] = NET_PACKET_ONION_RECV_3; + memcpy(packet + 1, ret, RETURN_3); + memcpy(packet + 1 + RETURN_3, data, length); + + if ((uint32_t)sendpacket(net, dest, packet, SIZEOF_VLA(packet)) != SIZEOF_VLA(packet)) { + return -1; + } + + return 0; +} + +non_null() +static int handle_send_initial(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + if (length <= 1 + SEND_1) { + return 1; + } + + change_symmetric_key(onion); + + uint8_t plain[ONION_MAX_PACKET_SIZE]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + get_shared_key(onion->mono_time, &onion->shared_keys_1, shared_key, dht_get_self_secret_key(onion->dht), + packet + 1 + CRYPTO_NONCE_SIZE); + const int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE), plain); + + if (len != length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE)) { + return 1; + } + + return onion_send_1(onion, plain, len, source, packet + 1); +} + +int onion_send_1(const Onion *onion, const uint8_t *plain, uint16_t len, const IP_Port *source, const uint8_t *nonce) +{ + if (len > ONION_MAX_PACKET_SIZE + SIZE_IPPORT - (1 + CRYPTO_NONCE_SIZE + ONION_RETURN_1)) { + return 1; + } + + if (len <= SIZE_IPPORT + SEND_BASE * 2) { + return 1; + } + + IP_Port send_to; + + if (ipport_unpack(&send_to, plain, len, false) == -1) { + return 1; + } + + uint8_t ip_port[SIZE_IPPORT]; + ipport_pack(ip_port, source); + + uint8_t data[ONION_MAX_PACKET_SIZE] = {0}; + data[0] = NET_PACKET_ONION_SEND_1; + memcpy(data + 1, nonce, CRYPTO_NONCE_SIZE); + memcpy(data + 1 + CRYPTO_NONCE_SIZE, plain + SIZE_IPPORT, len - SIZE_IPPORT); + uint16_t data_len = 1 + CRYPTO_NONCE_SIZE + (len - SIZE_IPPORT); + uint8_t *ret_part = data + data_len; + random_nonce(onion->rng, ret_part); + len = encrypt_data_symmetric(onion->secret_symmetric_key, ret_part, ip_port, SIZE_IPPORT, + ret_part + CRYPTO_NONCE_SIZE); + + if (len != SIZE_IPPORT + CRYPTO_MAC_SIZE) { + return 1; + } + + data_len += CRYPTO_NONCE_SIZE + len; + + if ((uint32_t)sendpacket(onion->net, &send_to, data, data_len) != data_len) { + return 1; + } + + return 0; +} + +non_null() +static int handle_send_1(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + if (length <= 1 + SEND_2) { + return 1; + } + + change_symmetric_key(onion); + + uint8_t plain[ONION_MAX_PACKET_SIZE]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + get_shared_key(onion->mono_time, &onion->shared_keys_2, shared_key, dht_get_self_secret_key(onion->dht), + packet + 1 + CRYPTO_NONCE_SIZE); + int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + RETURN_1), plain); + + if (len != length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + RETURN_1 + CRYPTO_MAC_SIZE)) { + return 1; + } + + IP_Port send_to; + + if (ipport_unpack(&send_to, plain, len, false) == -1) { + return 1; + } + + uint8_t data[ONION_MAX_PACKET_SIZE] = {0}; + data[0] = NET_PACKET_ONION_SEND_2; + memcpy(data + 1, packet + 1, CRYPTO_NONCE_SIZE); + memcpy(data + 1 + CRYPTO_NONCE_SIZE, plain + SIZE_IPPORT, len - SIZE_IPPORT); + uint16_t data_len = 1 + CRYPTO_NONCE_SIZE + (len - SIZE_IPPORT); + uint8_t *ret_part = data + data_len; + random_nonce(onion->rng, ret_part); + uint8_t ret_data[RETURN_1 + SIZE_IPPORT]; + ipport_pack(ret_data, source); + memcpy(ret_data + SIZE_IPPORT, packet + (length - RETURN_1), RETURN_1); + len = encrypt_data_symmetric(onion->secret_symmetric_key, ret_part, ret_data, sizeof(ret_data), + ret_part + CRYPTO_NONCE_SIZE); + + if (len != RETURN_2 - CRYPTO_NONCE_SIZE) { + return 1; + } + + data_len += CRYPTO_NONCE_SIZE + len; + + if ((uint32_t)sendpacket(onion->net, &send_to, data, data_len) != data_len) { + return 1; + } + + return 0; +} + +non_null() +static int handle_send_2(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + if (length <= 1 + SEND_3) { + return 1; + } + + change_symmetric_key(onion); + + uint8_t plain[ONION_MAX_PACKET_SIZE]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + get_shared_key(onion->mono_time, &onion->shared_keys_3, shared_key, dht_get_self_secret_key(onion->dht), + packet + 1 + CRYPTO_NONCE_SIZE); + int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + RETURN_2), plain); + + if (len != length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + RETURN_2 + CRYPTO_MAC_SIZE)) { + return 1; + } + + assert(len > SIZE_IPPORT); + + const uint8_t packet_id = plain[SIZE_IPPORT]; + + if (packet_id != NET_PACKET_ANNOUNCE_REQUEST_OLD && packet_id != NET_PACKET_ONION_DATA_REQUEST) { + return 1; + } + + IP_Port send_to; + + if (ipport_unpack(&send_to, plain, len, false) == -1) { + return 1; + } + + uint8_t data[ONION_MAX_PACKET_SIZE] = {0}; + memcpy(data, plain + SIZE_IPPORT, len - SIZE_IPPORT); + uint16_t data_len = len - SIZE_IPPORT; + uint8_t *ret_part = data + (len - SIZE_IPPORT); + random_nonce(onion->rng, ret_part); + uint8_t ret_data[RETURN_2 + SIZE_IPPORT]; + ipport_pack(ret_data, source); + memcpy(ret_data + SIZE_IPPORT, packet + (length - RETURN_2), RETURN_2); + len = encrypt_data_symmetric(onion->secret_symmetric_key, ret_part, ret_data, sizeof(ret_data), + ret_part + CRYPTO_NONCE_SIZE); + + if (len != RETURN_3 - CRYPTO_NONCE_SIZE) { + return 1; + } + + data_len += RETURN_3; + + if ((uint32_t)sendpacket(onion->net, &send_to, data, data_len) != data_len) { + return 1; + } + + return 0; +} + + +non_null() +static int handle_recv_3(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + if (length <= 1 + RETURN_3) { + return 1; + } + + const uint8_t packet_id = packet[1 + RETURN_3]; + + if (packet_id != NET_PACKET_ANNOUNCE_RESPONSE_OLD && packet_id != NET_PACKET_ONION_DATA_RESPONSE) { + return 1; + } + + change_symmetric_key(onion); + + uint8_t plain[SIZE_IPPORT + RETURN_2]; + const int len = decrypt_data_symmetric(onion->secret_symmetric_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE, + SIZE_IPPORT + RETURN_2 + CRYPTO_MAC_SIZE, plain); + + if ((uint32_t)len != sizeof(plain)) { + return 1; + } + + IP_Port send_to; + + if (ipport_unpack(&send_to, plain, len, false) == -1) { + return 1; + } + + uint8_t data[ONION_MAX_PACKET_SIZE] = {0}; + data[0] = NET_PACKET_ONION_RECV_2; + memcpy(data + 1, plain + SIZE_IPPORT, RETURN_2); + memcpy(data + 1 + RETURN_2, packet + 1 + RETURN_3, length - (1 + RETURN_3)); + const uint16_t data_len = 1 + RETURN_2 + (length - (1 + RETURN_3)); + + if ((uint32_t)sendpacket(onion->net, &send_to, data, data_len) != data_len) { + return 1; + } + + return 0; +} + +non_null() +static int handle_recv_2(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + if (length <= 1 + RETURN_2) { + return 1; + } + + const uint8_t packet_id = packet[1 + RETURN_2]; + + if (packet_id != NET_PACKET_ANNOUNCE_RESPONSE_OLD && packet_id != NET_PACKET_ONION_DATA_RESPONSE) { + return 1; + } + + change_symmetric_key(onion); + + uint8_t plain[SIZE_IPPORT + RETURN_1]; + const int len = decrypt_data_symmetric(onion->secret_symmetric_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE, + SIZE_IPPORT + RETURN_1 + CRYPTO_MAC_SIZE, plain); + + if ((uint32_t)len != sizeof(plain)) { + return 1; + } + + IP_Port send_to; + + if (ipport_unpack(&send_to, plain, len, false) == -1) { + return 1; + } + + uint8_t data[ONION_MAX_PACKET_SIZE] = {0}; + data[0] = NET_PACKET_ONION_RECV_1; + memcpy(data + 1, plain + SIZE_IPPORT, RETURN_1); + memcpy(data + 1 + RETURN_1, packet + 1 + RETURN_2, length - (1 + RETURN_2)); + const uint16_t data_len = 1 + RETURN_1 + (length - (1 + RETURN_2)); + + if ((uint32_t)sendpacket(onion->net, &send_to, data, data_len) != data_len) { + return 1; + } + + return 0; +} + +non_null() +static int handle_recv_1(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + if (length <= 1 + RETURN_1) { + return 1; + } + + const uint8_t packet_id = packet[1 + RETURN_1]; + + if (packet_id != NET_PACKET_ANNOUNCE_RESPONSE_OLD && packet_id != NET_PACKET_ONION_DATA_RESPONSE) { + return 1; + } + + change_symmetric_key(onion); + + uint8_t plain[SIZE_IPPORT]; + const int len = decrypt_data_symmetric(onion->secret_symmetric_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE, + SIZE_IPPORT + CRYPTO_MAC_SIZE, plain); + + if ((uint32_t)len != SIZE_IPPORT) { + return 1; + } + + IP_Port send_to; + + if (ipport_unpack(&send_to, plain, len, true) == -1) { + return 1; + } + + const uint16_t data_len = length - (1 + RETURN_1); + + if (onion->recv_1_function != nullptr && + !net_family_is_ipv4(send_to.ip.family) && + !net_family_is_ipv6(send_to.ip.family)) { + return onion->recv_1_function(onion->callback_object, &send_to, packet + (1 + RETURN_1), data_len); + } + + if ((uint32_t)sendpacket(onion->net, &send_to, packet + (1 + RETURN_1), data_len) != data_len) { + return 1; + } + + return 0; +} + +void set_callback_handle_recv_1(Onion *onion, onion_recv_1_cb *function, void *object) +{ + onion->recv_1_function = function; + onion->callback_object = object; +} + +Onion *new_onion(const Logger *log, const Mono_Time *mono_time, const Random *rng, DHT *dht) +{ + if (dht == nullptr) { + return nullptr; + } + + Onion *onion = (Onion *)calloc(1, sizeof(Onion)); + + if (onion == nullptr) { + return nullptr; + } + + onion->log = log; + onion->dht = dht; + onion->net = dht_get_net(dht); + onion->mono_time = mono_time; + onion->rng = rng; + new_symmetric_key(rng, onion->secret_symmetric_key); + onion->timestamp = mono_time_get(onion->mono_time); + + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_INITIAL, &handle_send_initial, onion); + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_1, &handle_send_1, onion); + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_2, &handle_send_2, onion); + + networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_3, &handle_recv_3, onion); + networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_2, &handle_recv_2, onion); + networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_1, &handle_recv_1, onion); + + return onion; +} + +void kill_onion(Onion *onion) +{ + if (onion == nullptr) { + return; + } + + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_INITIAL, nullptr, nullptr); + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_1, nullptr, nullptr); + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_2, nullptr, nullptr); + + networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_3, nullptr, nullptr); + networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_2, nullptr, nullptr); + networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_1, nullptr, nullptr); + + crypto_memzero(onion->secret_symmetric_key, sizeof(onion->secret_symmetric_key)); + + free(onion); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/onion_announce.h b/local_pod_repo/toxcore/toxcore/toxcore/onion_announce.h new file mode 100644 index 0000000..8cf1f49 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/onion_announce.h @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Implementation of the announce part of docs/Prevent_Tracking.txt + */ +#ifndef C_TOXCORE_TOXCORE_ONION_ANNOUNCE_H +#define C_TOXCORE_TOXCORE_ONION_ANNOUNCE_H + +#include "logger.h" +#include "onion.h" +#include "timed_auth.h" + +#define ONION_ANNOUNCE_MAX_ENTRIES 160 +#define ONION_ANNOUNCE_TIMEOUT 300 +#define ONION_PING_ID_SIZE TIMED_AUTH_SIZE +#define ONION_MAX_EXTRA_DATA_SIZE 136 + +#define ONION_ANNOUNCE_SENDBACK_DATA_LENGTH (sizeof(uint64_t)) + +#define ONION_ANNOUNCE_REQUEST_MIN_SIZE (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_MAC_SIZE) +#define ONION_ANNOUNCE_REQUEST_MAX_SIZE (ONION_ANNOUNCE_REQUEST_MIN_SIZE + ONION_MAX_EXTRA_DATA_SIZE) + +#define ONION_ANNOUNCE_RESPONSE_MIN_SIZE (2 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE + ONION_PING_ID_SIZE + CRYPTO_MAC_SIZE) +#define ONION_ANNOUNCE_RESPONSE_MAX_SIZE (ONION_ANNOUNCE_RESPONSE_MIN_SIZE + ONION_MAX_EXTRA_DATA_SIZE * MAX_SENT_NODES) + +/* TODO: DEPRECATE */ +#define ONION_ANNOUNCE_REQUEST_SIZE (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_MAC_SIZE) + +#define ONION_DATA_RESPONSE_MIN_SIZE (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE) + +#define ONION_DATA_REQUEST_MIN_SIZE (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE) +#define MAX_DATA_REQUEST_SIZE (ONION_MAX_DATA_SIZE - ONION_DATA_REQUEST_MIN_SIZE) + +typedef struct Onion_Announce Onion_Announce; + +/** These two are not public; they are for tests only! */ +non_null() +uint8_t *onion_announce_entry_public_key(Onion_Announce *onion_a, uint32_t entry); +non_null() +void onion_announce_entry_set_time(Onion_Announce *onion_a, uint32_t entry, uint64_t announce_time); + +/** @brief Create an onion announce request packet in packet of max_packet_length. + * + * Recommended value for max_packet_length is ONION_ANNOUNCE_REQUEST_MIN_SIZE. + * + * dest_client_id is the public key of the node the packet will be sent to. + * public_key and secret_key is the kepair which will be used to encrypt the request. + * ping_id is the ping id that will be sent in the request. + * client_id is the client id of the node we are searching for. + * data_public_key is the public key we want others to encrypt their data packets with. + * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to + * receive back in the response. + * + * return -1 on failure. + * return packet length on success. + */ +non_null() +int create_announce_request(const Random *rng, uint8_t *packet, uint16_t max_packet_length, const uint8_t *dest_client_id, + const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, + const uint8_t *data_public_key, uint64_t sendback_data); + +/** @brief Create an onion data request packet in packet of max_packet_length. + * + * Recommended value for max_packet_length is ONION_ANNOUNCE_REQUEST_SIZE. + * + * public_key is the real public key of the node which we want to send the data of length length to. + * encrypt_public_key is the public key used to encrypt the data packet. + * + * nonce is the nonce to encrypt this packet with + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int create_data_request(const Random *rng, uint8_t *packet, uint16_t max_packet_length, const uint8_t *public_key, + const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length); + +/** @brief Create and send an onion announce request packet. + * + * path is the path the request will take before it is sent to dest. + * + * public_key and secret_key is the kepair which will be used to encrypt the request. + * ping_id is the ping id that will be sent in the request. + * client_id is the client id of the node we are searching for. + * data_public_key is the public key we want others to encrypt their data packets with. + * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to + * receive back in the response. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int send_announce_request(const Networking_Core *net, const Random *rng, + const Onion_Path *path, const Node_format *dest, + const uint8_t *public_key, const uint8_t *secret_key, + const uint8_t *ping_id, const uint8_t *client_id, + const uint8_t *data_public_key, uint64_t sendback_data); + +/** @brief Create and send an onion data request packet. + * + * path is the path the request will take before it is sent to dest. + * (if dest knows the person with the public_key they should + * send the packet to that person in the form of a response) + * + * public_key is the real public key of the node which we want to send the data of length length to. + * encrypt_public_key is the public key used to encrypt the data packet. + * + * nonce is the nonce to encrypt this packet with + * + * The maximum length of data is MAX_DATA_REQUEST_SIZE. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int send_data_request(const Networking_Core *net, const Random *rng, const Onion_Path *path, const IP_Port *dest, + const uint8_t *public_key, const uint8_t *encrypt_public_key, const uint8_t *nonce, + const uint8_t *data, uint16_t length); + + +typedef int pack_extra_data_cb(void *object, const Logger *logger, const Mono_Time *mono_time, + uint8_t num_nodes, uint8_t *plain, uint16_t plain_size, + uint8_t *response, uint16_t response_size, uint16_t offset); + +non_null(1) nullable(3, 4) +void onion_announce_extra_data_callback(Onion_Announce *onion_a, uint16_t extra_data_max_size, + pack_extra_data_cb *extra_data_callback, void *extra_data_object); + +non_null() +Onion_Announce *new_onion_announce(const Logger *log, const Random *rng, const Mono_Time *mono_time, DHT *dht); + +nullable(1) +void kill_onion_announce(Onion_Announce *onion_a); + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/onion_announce.m b/local_pod_repo/toxcore/toxcore/toxcore/onion_announce.m new file mode 100644 index 0000000..f12e560 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/onion_announce.m @@ -0,0 +1,680 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Implementation of the announce part of docs/Prevent_Tracking.txt + */ +#include "onion_announce.h" + +#include +#include +#include + +#include "DHT.h" +#include "LAN_discovery.h" +#include "ccompat.h" +#include "mono_time.h" +#include "util.h" + +#define PING_ID_TIMEOUT ONION_ANNOUNCE_TIMEOUT + +#define ANNOUNCE_REQUEST_MIN_SIZE_RECV (ONION_ANNOUNCE_REQUEST_MIN_SIZE + ONION_RETURN_3) +#define ANNOUNCE_REQUEST_MAX_SIZE_RECV (ONION_ANNOUNCE_REQUEST_MAX_SIZE + ONION_RETURN_3) + +/* TODO(Jfreegman): DEPRECATE */ +#define ANNOUNCE_REQUEST_SIZE_RECV (ONION_ANNOUNCE_REQUEST_SIZE + ONION_RETURN_3) + +#define DATA_REQUEST_MIN_SIZE ONION_DATA_REQUEST_MIN_SIZE +#define DATA_REQUEST_MIN_SIZE_RECV (DATA_REQUEST_MIN_SIZE + ONION_RETURN_3) + +#define ONION_MINIMAL_SIZE (ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE * 2 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH) + +static_assert(ONION_PING_ID_SIZE == CRYPTO_PUBLIC_KEY_SIZE, + "announce response packets assume that ONION_PING_ID_SIZE is equal to CRYPTO_PUBLIC_KEY_SIZE"); + +typedef struct Onion_Announce_Entry { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + IP_Port ret_ip_port; + uint8_t ret[ONION_RETURN_3]; + uint8_t data_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint64_t announce_time; +} Onion_Announce_Entry; + +struct Onion_Announce { + const Logger *log; + const Mono_Time *mono_time; + const Random *rng; + DHT *dht; + Networking_Core *net; + Onion_Announce_Entry entries[ONION_ANNOUNCE_MAX_ENTRIES]; + uint8_t hmac_key[CRYPTO_HMAC_KEY_SIZE]; + + Shared_Keys shared_keys_recv; + + uint16_t extra_data_max_size; + pack_extra_data_cb *extra_data_callback; + void *extra_data_object; +}; + +void onion_announce_extra_data_callback(Onion_Announce *onion_a, uint16_t extra_data_max_size, + pack_extra_data_cb *extra_data_callback, void *extra_data_object) +{ + onion_a->extra_data_max_size = extra_data_max_size; + onion_a->extra_data_callback = extra_data_callback; + onion_a->extra_data_object = extra_data_object; +} + +uint8_t *onion_announce_entry_public_key(Onion_Announce *onion_a, uint32_t entry) +{ + return onion_a->entries[entry].public_key; +} + +void onion_announce_entry_set_time(Onion_Announce *onion_a, uint32_t entry, uint64_t announce_time) +{ + onion_a->entries[entry].announce_time = announce_time; +} + +/** @brief Create an onion announce request packet in packet of max_packet_length. + * + * Recommended value for max_packet_length is ONION_ANNOUNCE_REQUEST_MIN_SIZE. + * + * dest_client_id is the public key of the node the packet will be sent to. + * public_key and secret_key is the kepair which will be used to encrypt the request. + * ping_id is the ping id that will be sent in the request. + * client_id is the client id of the node we are searching for. + * data_public_key is the public key we want others to encrypt their data packets with. + * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to + * receive back in the response. + * + * return -1 on failure. + * return packet length on success. + */ +int create_announce_request(const Random *rng, uint8_t *packet, uint16_t max_packet_length, const uint8_t *dest_client_id, + const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, + const uint8_t *data_public_key, uint64_t sendback_data) +{ + if (max_packet_length < ONION_ANNOUNCE_REQUEST_MIN_SIZE) { + return -1; + } + + uint8_t plain[ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH]; + memcpy(plain, ping_id, ONION_PING_ID_SIZE); + memcpy(plain + ONION_PING_ID_SIZE, client_id, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE, data_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE, &sendback_data, + sizeof(sendback_data)); + + packet[0] = NET_PACKET_ANNOUNCE_REQUEST_OLD; + random_nonce(rng, packet + 1); + + const int len = encrypt_data(dest_client_id, secret_key, packet + 1, plain, sizeof(plain), + packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE); + + if ((uint32_t)len + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE != ONION_ANNOUNCE_REQUEST_MIN_SIZE) { + return -1; + } + + memcpy(packet + 1 + CRYPTO_NONCE_SIZE, public_key, CRYPTO_PUBLIC_KEY_SIZE); + + return ONION_ANNOUNCE_REQUEST_MIN_SIZE; +} + +/** @brief Create an onion data request packet in packet of max_packet_length. + * + * Recommended value for max_packet_length is ONION_ANNOUNCE_REQUEST_SIZE. + * + * public_key is the real public key of the node which we want to send the data of length length to. + * encrypt_public_key is the public key used to encrypt the data packet. + * + * nonce is the nonce to encrypt this packet with + * + * return -1 on failure. + * return 0 on success. + */ +int create_data_request(const Random *rng, uint8_t *packet, uint16_t max_packet_length, const uint8_t *public_key, + const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length) +{ + if (DATA_REQUEST_MIN_SIZE + length > max_packet_length) { + return -1; + } + + if (DATA_REQUEST_MIN_SIZE + length > ONION_MAX_DATA_SIZE) { + return -1; + } + + packet[0] = NET_PACKET_ONION_DATA_REQUEST; + memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); + + uint8_t random_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t random_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(rng, random_public_key, random_secret_key); + + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, random_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + const int len = encrypt_data(encrypt_public_key, random_secret_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, data, length, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE); + + if (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + len != DATA_REQUEST_MIN_SIZE + + length) { + return -1; + } + + return DATA_REQUEST_MIN_SIZE + length; +} + +/** @brief Create and send an onion announce request packet. + * + * path is the path the request will take before it is sent to dest. + * + * public_key and secret_key is the kepair which will be used to encrypt the request. + * ping_id is the ping id that will be sent in the request. + * client_id is the client id of the node we are searching for. + * data_public_key is the public key we want others to encrypt their data packets with. + * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to + * receive back in the response. + * + * return -1 on failure. + * return 0 on success. + */ +int send_announce_request(const Networking_Core *net, const Random *rng, + const Onion_Path *path, const Node_format *dest, + const uint8_t *public_key, const uint8_t *secret_key, + const uint8_t *ping_id, const uint8_t *client_id, + const uint8_t *data_public_key, uint64_t sendback_data) +{ + uint8_t request[ONION_ANNOUNCE_REQUEST_MIN_SIZE]; + int len = create_announce_request(rng, request, sizeof(request), dest->public_key, public_key, secret_key, ping_id, + client_id, data_public_key, sendback_data); + + if (len != sizeof(request)) { + return -1; + } + + uint8_t packet[ONION_MAX_PACKET_SIZE]; + len = create_onion_packet(rng, packet, sizeof(packet), path, &dest->ip_port, request, sizeof(request)); + + if (len == -1) { + return -1; + } + + if (sendpacket(net, &path->ip_port1, packet, len) != len) { + return -1; + } + + return 0; +} + +/** @brief Create and send an onion data request packet. + * + * path is the path the request will take before it is sent to dest. + * (if dest knows the person with the public_key they should + * send the packet to that person in the form of a response) + * + * public_key is the real public key of the node which we want to send the data of length length to. + * encrypt_public_key is the public key used to encrypt the data packet. + * + * nonce is the nonce to encrypt this packet with + * + * The maximum length of data is MAX_DATA_REQUEST_SIZE. + * + * return -1 on failure. + * return 0 on success. + */ +int send_data_request(const Networking_Core *net, const Random *rng, const Onion_Path *path, const IP_Port *dest, + const uint8_t *public_key, const uint8_t *encrypt_public_key, const uint8_t *nonce, + const uint8_t *data, uint16_t length) +{ + uint8_t request[ONION_MAX_DATA_SIZE]; + int len = create_data_request(rng, request, sizeof(request), public_key, encrypt_public_key, nonce, data, length); + + if (len == -1) { + return -1; + } + + uint8_t packet[ONION_MAX_PACKET_SIZE]; + len = create_onion_packet(rng, packet, sizeof(packet), path, dest, request, len); + + if (len == -1) { + return -1; + } + + if (sendpacket(net, &path->ip_port1, packet, len) != len) { + return -1; + } + + return 0; +} + +/** @brief check if public key is in entries list + * + * return -1 if no + * return position in list if yes + */ +non_null() +static int in_entries(const Onion_Announce *onion_a, const uint8_t *public_key) +{ + for (unsigned int i = 0; i < ONION_ANNOUNCE_MAX_ENTRIES; ++i) { + if (!mono_time_is_timeout(onion_a->mono_time, onion_a->entries[i].announce_time, ONION_ANNOUNCE_TIMEOUT) + && pk_equal(onion_a->entries[i].public_key, public_key)) { + return i; + } + } + + return -1; +} + +typedef struct Cmp_Data { + const Mono_Time *mono_time; + const uint8_t *base_public_key; + Onion_Announce_Entry entry; +} Cmp_Data; + +non_null() +static int cmp_entry(const void *a, const void *b) +{ + const Cmp_Data *cmp1 = (const Cmp_Data *)a; + const Cmp_Data *cmp2 = (const Cmp_Data *)b; + const Onion_Announce_Entry entry1 = cmp1->entry; + const Onion_Announce_Entry entry2 = cmp2->entry; + const uint8_t *cmp_public_key = cmp1->base_public_key; + + const bool t1 = mono_time_is_timeout(cmp1->mono_time, entry1.announce_time, ONION_ANNOUNCE_TIMEOUT); + const bool t2 = mono_time_is_timeout(cmp1->mono_time, entry2.announce_time, ONION_ANNOUNCE_TIMEOUT); + + if (t1 && t2) { + return 0; + } + + if (t1) { + return -1; + } + + if (t2) { + return 1; + } + + const int closest = id_closest(cmp_public_key, entry1.public_key, entry2.public_key); + + if (closest == 1) { + return 1; + } + + if (closest == 2) { + return -1; + } + + return 0; +} + +non_null() +static void sort_onion_announce_list(Onion_Announce_Entry *list, unsigned int length, const Mono_Time *mono_time, + const uint8_t *comp_public_key) +{ + // Pass comp_public_key to qsort with each Client_data entry, so the + // comparison function can use it as the base of comparison. + Cmp_Data *cmp_list = (Cmp_Data *)calloc(length, sizeof(Cmp_Data)); + + if (cmp_list == nullptr) { + return; + } + + for (uint32_t i = 0; i < length; ++i) { + cmp_list[i].mono_time = mono_time; + cmp_list[i].base_public_key = comp_public_key; + cmp_list[i].entry = list[i]; + } + + qsort(cmp_list, length, sizeof(Cmp_Data), cmp_entry); + + for (uint32_t i = 0; i < length; ++i) { + list[i] = cmp_list[i].entry; + } + + free(cmp_list); +} + +/** @brief add entry to entries list + * + * return -1 if failure + * return position if added + */ +non_null() +static int add_to_entries(Onion_Announce *onion_a, const IP_Port *ret_ip_port, const uint8_t *public_key, + const uint8_t *data_public_key, const uint8_t *ret) +{ + + int pos = in_entries(onion_a, public_key); + + if (pos == -1) { + for (unsigned i = 0; i < ONION_ANNOUNCE_MAX_ENTRIES; ++i) { + if (mono_time_is_timeout(onion_a->mono_time, onion_a->entries[i].announce_time, ONION_ANNOUNCE_TIMEOUT)) { + pos = i; + } + } + } + + if (pos == -1) { + if (id_closest(dht_get_self_public_key(onion_a->dht), public_key, onion_a->entries[0].public_key) == 1) { + pos = 0; + } + } + + if (pos == -1) { + return -1; + } + + memcpy(onion_a->entries[pos].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + onion_a->entries[pos].ret_ip_port = *ret_ip_port; + memcpy(onion_a->entries[pos].ret, ret, ONION_RETURN_3); + memcpy(onion_a->entries[pos].data_public_key, data_public_key, CRYPTO_PUBLIC_KEY_SIZE); + onion_a->entries[pos].announce_time = mono_time_get(onion_a->mono_time); + + sort_onion_announce_list(onion_a->entries, ONION_ANNOUNCE_MAX_ENTRIES, onion_a->mono_time, + dht_get_self_public_key(onion_a->dht)); + return in_entries(onion_a, public_key); +} + +non_null() +static void make_announce_payload_helper(const Onion_Announce *onion_a, const uint8_t *ping_id, + uint8_t *response, int index, const uint8_t *packet_public_key, const uint8_t *data_public_key) +{ + if (index < 0) { + response[0] = 0; + memcpy(response + 1, ping_id, ONION_PING_ID_SIZE); + return; + } + + if (pk_equal(onion_a->entries[index].public_key, packet_public_key)) { + if (!pk_equal(onion_a->entries[index].data_public_key, data_public_key)) { + response[0] = 0; + memcpy(response + 1, ping_id, ONION_PING_ID_SIZE); + } else { + response[0] = 2; + memcpy(response + 1, ping_id, ONION_PING_ID_SIZE); + } + } else { + response[0] = 1; + memcpy(response + 1, onion_a->entries[index].data_public_key, CRYPTO_PUBLIC_KEY_SIZE); + } +} + +/** @brief Handle an onion announce request, possibly with extra data for group chats. + * + * @param onion_a The announce object. + * @param source Requester IP/Port. + * @param packet Encrypted incoming packet. + * @param length Length of incoming packet. + * @param response_packet_id Packet ID to use for the onion announce response. + * @param plain_size Expected size of the decrypted packet. This function returns an error if the + * actual decrypted size is not exactly equal to this number. + * @param want_node_count If true, the packed nodes in the response are preceded by the number of + * nodes sent in the packet. This is necessary if you want to send extra data after the nodes. + * @param max_extra_size Amount of memory to allocate in the outgoing packet to be filled by the + * extra data callback. + * @param pack_extra_data_callback Callback that may write extra data into the packet. + * + * @retval 1 on failure. + * @retval 0 on success. + */ +non_null(1, 2, 3) nullable(9) +static int handle_announce_request_common( + Onion_Announce *onion_a, const IP_Port *source, const uint8_t *packet, uint16_t length, + uint8_t response_packet_id, uint16_t plain_size, bool want_node_count, uint16_t max_extra_size, + pack_extra_data_cb *pack_extra_data_callback) +{ + const uint8_t *packet_public_key = packet + 1 + CRYPTO_NONCE_SIZE; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + get_shared_key(onion_a->mono_time, &onion_a->shared_keys_recv, shared_key, dht_get_self_secret_key(onion_a->dht), + packet_public_key); + + uint8_t *plain = (uint8_t *)malloc(plain_size); + + if (plain == nullptr) { + return 1; + } + + const int decrypted_len = decrypt_data_symmetric(shared_key, packet + 1, + packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, plain_size + CRYPTO_MAC_SIZE, plain); + + if ((uint32_t)decrypted_len != plain_size) { + free(plain); + return 1; + } + + const uint16_t ping_id_data_len = CRYPTO_PUBLIC_KEY_SIZE + sizeof(*source); + uint8_t ping_id_data[CRYPTO_PUBLIC_KEY_SIZE + sizeof(*source)]; + memcpy(ping_id_data, packet_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(ping_id_data + CRYPTO_PUBLIC_KEY_SIZE, source, sizeof(*source)); + + const uint8_t *data_public_key = plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE; + + int index; + + if (check_timed_auth(onion_a->mono_time, PING_ID_TIMEOUT, onion_a->hmac_key, + ping_id_data, ping_id_data_len, plain)) { + index = add_to_entries(onion_a, source, packet_public_key, data_public_key, + packet + (length - ONION_RETURN_3)); + } else { + index = in_entries(onion_a, plain + ONION_PING_ID_SIZE); + } + + /* Respond with a announce response packet */ + Node_format nodes_list[MAX_SENT_NODES]; + const unsigned int num_nodes = + get_close_nodes(onion_a->dht, plain + ONION_PING_ID_SIZE, nodes_list, net_family_unspec(), ip_is_lan(&source->ip), false); + + assert(num_nodes <= UINT8_MAX); + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_nonce(onion_a->rng, nonce); + + const uint16_t nodes_offset = 1 + ONION_PING_ID_SIZE + (want_node_count ? 1 : 0); + const uint16_t response_size = nodes_offset + + MAX_SENT_NODES * PACKED_NODE_SIZE_IP6 + + max_extra_size; + uint8_t *response = (uint8_t *)malloc(response_size); + + if (response == nullptr) { + free(plain); + return 1; + } + + uint8_t ping_id[TIMED_AUTH_SIZE]; + generate_timed_auth(onion_a->mono_time, PING_ID_TIMEOUT, onion_a->hmac_key, + ping_id_data, ping_id_data_len, ping_id); + + make_announce_payload_helper(onion_a, ping_id, response, index, packet_public_key, data_public_key); + + int nodes_length = 0; + + if (num_nodes != 0) { + nodes_length = pack_nodes(onion_a->log, response + nodes_offset, sizeof(nodes_list), nodes_list, + (uint16_t)num_nodes); + + if (nodes_length <= 0) { + LOGGER_WARNING(onion_a->log, "Failed to pack nodes"); + free(response); + free(plain); + return 1; + } + } + + uint16_t offset = nodes_offset + nodes_length; + + if (want_node_count) { + response[1 + ONION_PING_ID_SIZE] = (uint8_t)num_nodes; + } + + const int extra_size = pack_extra_data_callback == nullptr ? 0 + : pack_extra_data_callback(onion_a->extra_data_object, + onion_a->log, onion_a->mono_time, num_nodes, + plain + ONION_MINIMAL_SIZE, length - ANNOUNCE_REQUEST_MIN_SIZE_RECV, + response, response_size, offset); + + if (extra_size == -1) { + free(response); + free(plain); + return 1; + } + + offset += extra_size; + + uint8_t data[ONION_ANNOUNCE_RESPONSE_MAX_SIZE]; + const int len = encrypt_data_symmetric(shared_key, nonce, response, offset, + data + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE); + + if (len != offset + CRYPTO_MAC_SIZE) { + LOGGER_ERROR(onion_a->log, "Failed to encrypt announce response"); + free(response); + free(plain); + return 1; + } + + data[0] = response_packet_id; + memcpy(data + 1, plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH); + memcpy(data + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, nonce, CRYPTO_NONCE_SIZE); + + if (send_onion_response(onion_a->net, source, data, + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE + len, + packet + (length - ONION_RETURN_3)) == -1) { + free(response); + free(plain); + return 1; + } + + free(response); + free(plain); + return 0; +} + +non_null() +static int handle_gca_announce_request(Onion_Announce *onion_a, const IP_Port *source, const uint8_t *packet, + uint16_t length) +{ + if (length > ANNOUNCE_REQUEST_MAX_SIZE_RECV || length <= ANNOUNCE_REQUEST_MIN_SIZE_RECV) { + return 1; + } + + if (onion_a->extra_data_callback == nullptr) { + return 1; + } + + return handle_announce_request_common(onion_a, source, packet, length, NET_PACKET_ANNOUNCE_RESPONSE, + ONION_MINIMAL_SIZE + length - ANNOUNCE_REQUEST_MIN_SIZE_RECV, + true, onion_a->extra_data_max_size, onion_a->extra_data_callback); +} + +non_null(1, 2, 3) nullable(5) +static int handle_announce_request(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Onion_Announce *onion_a = (Onion_Announce *)object; + + if (length != ANNOUNCE_REQUEST_MIN_SIZE_RECV) { + return handle_gca_announce_request(onion_a, source, packet, length); + } + + return handle_announce_request_common(onion_a, source, packet, length, NET_PACKET_ANNOUNCE_RESPONSE, + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE * 2 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, + true, 0, nullptr); +} + +/* TODO(Jfreegman): DEPRECATE */ +non_null(1, 2, 3) nullable(5) +static int handle_announce_request_old(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Onion_Announce *onion_a = (Onion_Announce *)object; + + if (length != ANNOUNCE_REQUEST_SIZE_RECV) { + return 1; + } + + return handle_announce_request_common(onion_a, source, packet, length, NET_PACKET_ANNOUNCE_RESPONSE_OLD, + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE * 2 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, + false, 0, nullptr); +} + +non_null() +static int handle_data_request(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + const Onion_Announce *onion_a = (const Onion_Announce *)object; + + if (length <= DATA_REQUEST_MIN_SIZE_RECV) { + return 1; + } + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + const int index = in_entries(onion_a, packet + 1); + + if (index == -1) { + return 1; + } + + VLA(uint8_t, data, length - (CRYPTO_PUBLIC_KEY_SIZE + ONION_RETURN_3)); + data[0] = NET_PACKET_ONION_DATA_RESPONSE; + memcpy(data + 1, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, length - (1 + CRYPTO_PUBLIC_KEY_SIZE + ONION_RETURN_3)); + + if (send_onion_response(onion_a->net, &onion_a->entries[index].ret_ip_port, data, SIZEOF_VLA(data), + onion_a->entries[index].ret) == -1) { + return 1; + } + + return 0; +} + +Onion_Announce *new_onion_announce(const Logger *log, const Random *rng, const Mono_Time *mono_time, DHT *dht) +{ + if (dht == nullptr) { + return nullptr; + } + + Onion_Announce *onion_a = (Onion_Announce *)calloc(1, sizeof(Onion_Announce)); + + if (onion_a == nullptr) { + return nullptr; + } + + onion_a->log = log; + onion_a->rng = rng; + onion_a->mono_time = mono_time; + onion_a->dht = dht; + onion_a->net = dht_get_net(dht); + onion_a->extra_data_max_size = 0; + onion_a->extra_data_callback = nullptr; + onion_a->extra_data_object = nullptr; + new_hmac_key(rng, onion_a->hmac_key); + + networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST, &handle_announce_request, onion_a); + networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST_OLD, &handle_announce_request_old, onion_a); + networking_registerhandler(onion_a->net, NET_PACKET_ONION_DATA_REQUEST, &handle_data_request, onion_a); + + // TODO(Jfreegman): Remove this when we merge the rest of new groupchats + onion_announce_extra_data_callback(onion_a, 0, nullptr, nullptr); + + return onion_a; +} + +void kill_onion_announce(Onion_Announce *onion_a) +{ + if (onion_a == nullptr) { + return; + } + + networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST, nullptr, nullptr); + networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST_OLD, nullptr, nullptr); + networking_registerhandler(onion_a->net, NET_PACKET_ONION_DATA_REQUEST, nullptr, nullptr); + + crypto_memzero(onion_a->hmac_key, CRYPTO_HMAC_KEY_SIZE); + + free(onion_a); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/onion_client.h b/local_pod_repo/toxcore/toxcore/toxcore/onion_client.h new file mode 100644 index 0000000..2a30005 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/onion_client.h @@ -0,0 +1,220 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Implementation of the client part of docs/Prevent_Tracking.txt (The part that + * uses the onion stuff to connect to the friend) + */ +#ifndef C_TOXCORE_TOXCORE_ONION_CLIENT_H +#define C_TOXCORE_TOXCORE_ONION_CLIENT_H + +#include + +#include "net_crypto.h" +#include "onion_announce.h" +#include "ping_array.h" + +#define MAX_ONION_CLIENTS 8 +#define MAX_ONION_CLIENTS_ANNOUNCE 12 // Number of nodes to announce ourselves to. +#define ONION_NODE_PING_INTERVAL 15 +#define ONION_NODE_TIMEOUT ONION_NODE_PING_INTERVAL + +/** The interval in seconds at which to tell our friends where we are */ +#define ONION_DHTPK_SEND_INTERVAL 30 +#define DHT_DHTPK_SEND_INTERVAL 20 + +#define NUMBER_ONION_PATHS 6 + +/** + * The timeout the first time the path is added and + * then for all the next consecutive times + */ +#define ONION_PATH_FIRST_TIMEOUT 4 +#define ONION_PATH_TIMEOUT 10 +#define ONION_PATH_MAX_LIFETIME 1200 +#define ONION_PATH_MAX_NO_RESPONSE_USES 4 + +#define MAX_STORED_PINGED_NODES 9 +#define MIN_NODE_PING_TIME 10 + +#define ONION_NODE_MAX_PINGS 3 + +#define MAX_PATH_NODES 32 + +/** + * If no announce response packets are received within this interval tox will + * be considered offline. We give time for a node to be pinged often enough + * that it times out, which leads to the network being thoroughly tested as it + * is replaced. + */ +#define ONION_OFFLINE_TIMEOUT (ONION_NODE_PING_INTERVAL * (ONION_NODE_MAX_PINGS+2)) + +/** Onion data packet ids. */ +#define ONION_DATA_FRIEND_REQ CRYPTO_PACKET_FRIEND_REQ +#define ONION_DATA_DHTPK CRYPTO_PACKET_DHTPK + +typedef struct Onion_Client Onion_Client; + +non_null() +DHT *onion_get_dht(const Onion_Client *onion_c); +non_null() +Net_Crypto *onion_get_net_crypto(const Onion_Client *onion_c); + +/** @brief Add a node to the path_nodes bootstrap array. + * + * If a node with the given public key was already in the bootstrap array, this function has no + * effect and returns successfully. There is currently no way to update the IP/port for a bootstrap + * node, so if it changes, the Onion_Client must be recreated. + * + * @param onion_c The onion client object. + * @param ip_port IP/port for the bootstrap node. + * @param public_key DHT public key for the bootstrap node. + * + * @retval false on failure + * @retval true on success + */ +non_null() +bool onion_add_bs_path_node(Onion_Client *onion_c, const IP_Port *ip_port, const uint8_t *public_key); + +/** @brief Put up to max_num nodes in nodes. + * + * return the number of nodes. + */ +non_null() +uint16_t onion_backup_nodes(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num); + +/** @brief Get the friend_num of a friend. + * + * return -1 on failure. + * return friend number on success. + */ +non_null() +int onion_friend_num(const Onion_Client *onion_c, const uint8_t *public_key); + +/** @brief Add a friend who we want to connect to. + * + * return -1 on failure. + * return the friend number on success or if the friend was already added. + */ +non_null() +int onion_addfriend(Onion_Client *onion_c, const uint8_t *public_key); + +/** @brief Delete a friend. + * + * return -1 on failure. + * return the deleted friend number on success. + */ +non_null() +int onion_delfriend(Onion_Client *onion_c, int friend_num); + +/** @brief Set if friend is online or not. + * + * NOTE: This function is there and should be used so that we don't send + * useless packets to the friend if they are online. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int onion_set_friend_online(Onion_Client *onion_c, int friend_num, bool is_online); + +/** @brief Get the ip of friend friendnum and put it in ip_port + * + * @retval -1 if public_key does NOT refer to a friend + * @retval 0 if public_key refers to a friend and we failed to find the friend (yet) + * @retval 1 if public_key refers to a friend and we found them + */ +non_null() +int onion_getfriendip(const Onion_Client *onion_c, int friend_num, IP_Port *ip_port); + +typedef int recv_tcp_relay_cb(void *object, uint32_t number, const IP_Port *ip_port, const uint8_t *public_key); + +/** @brief Set the function for this friend that will be callbacked with object and number + * when that friend gives us one of the TCP relays they are connected to. + * + * object and number will be passed as argument to this function. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int recv_tcp_relay_handler(Onion_Client *onion_c, int friend_num, + recv_tcp_relay_cb *callback, void *object, uint32_t number); + +typedef void onion_dht_pk_cb(void *data, int32_t number, const uint8_t *dht_public_key, void *userdata); + +/** @brief Set the function for this friend that will be callbacked with object and number + * when that friend gives us their DHT temporary public key. + * + * object and number will be passed as argument to this function. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int onion_dht_pk_callback(Onion_Client *onion_c, int friend_num, onion_dht_pk_cb *function, void *object, + uint32_t number); + +/** @brief Set a friend's DHT public key. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +int onion_set_friend_DHT_pubkey(Onion_Client *onion_c, int friend_num, const uint8_t *dht_key); + +/** @brief Copy friends DHT public key into dht_key. + * + * return 0 on failure (no key copied). + * return 1 on success (key copied). + */ +non_null() +unsigned int onion_getfriend_DHT_pubkey(const Onion_Client *onion_c, int friend_num, uint8_t *dht_key); + +#define ONION_DATA_IN_RESPONSE_MIN_SIZE (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE) +#define ONION_CLIENT_MAX_DATA_SIZE (MAX_DATA_REQUEST_SIZE - ONION_DATA_IN_RESPONSE_MIN_SIZE) + +/** @brief Send data of length length to friendnum. + * Maximum length of data is ONION_CLIENT_MAX_DATA_SIZE. + * This data will be received by the friend using the Onion_Data_Handlers callbacks. + * + * Even if this function succeeds, the friend might not receive any data. + * + * return the number of packets sent on success + * return -1 on failure. + */ +non_null() +int send_onion_data(Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length); + +typedef int oniondata_handler_cb(void *object, const uint8_t *source_pubkey, const uint8_t *data, + uint16_t len, void *userdata); + +/** Function to call when onion data packet with contents beginning with byte is received. */ +non_null(1) nullable(3, 4) +void oniondata_registerhandler(Onion_Client *onion_c, uint8_t byte, oniondata_handler_cb *cb, void *object); + +non_null() +void do_onion_client(Onion_Client *onion_c); + +non_null() +Onion_Client *new_onion_client(const Logger *logger, const Random *rng, const Mono_Time *mono_time, Net_Crypto *c); + +nullable(1) +void kill_onion_client(Onion_Client *onion_c); + + +typedef enum Onion_Connection_Status { + /** We are not connected to the network. */ + ONION_CONNECTION_STATUS_NONE = 0, + /** We are connected with TCP only. */ + ONION_CONNECTION_STATUS_TCP = 1, + /** We are also connected with UDP. */ + ONION_CONNECTION_STATUS_UDP = 2, +} Onion_Connection_Status; + +non_null() +Onion_Connection_Status onion_connection_status(const Onion_Client *onion_c); + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/onion_client.m b/local_pod_repo/toxcore/toxcore/toxcore/onion_client.m new file mode 100644 index 0000000..91168c6 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/onion_client.m @@ -0,0 +1,1952 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Implementation of the client part of docs/Prevent_Tracking.txt (The part that + * uses the onion stuff to connect to the friend) + */ +#include "onion_client.h" + +#include +#include +#include + +#include "LAN_discovery.h" +#include "ccompat.h" +#include "mono_time.h" +#include "util.h" + +/** @brief defines for the array size and timeout for onion announce packets. */ +#define ANNOUNCE_ARRAY_SIZE 256 +#define ANNOUNCE_TIMEOUT 10 + +typedef struct Onion_Node { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + IP_Port ip_port; + uint8_t ping_id[ONION_PING_ID_SIZE]; + uint8_t data_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t is_stored; // Tribool. + + uint64_t added_time; + + uint64_t timestamp; + + uint64_t last_pinged; + + uint8_t pings_since_last_response; + + uint32_t path_used; +} Onion_Node; + +typedef struct Onion_Client_Paths { + Onion_Path paths[NUMBER_ONION_PATHS]; + uint64_t last_path_success[NUMBER_ONION_PATHS]; + uint64_t last_path_used[NUMBER_ONION_PATHS]; + uint64_t path_creation_time[NUMBER_ONION_PATHS]; + /* number of times used without success. */ + unsigned int last_path_used_times[NUMBER_ONION_PATHS]; +} Onion_Client_Paths; + +typedef struct Last_Pinged { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint64_t timestamp; +} Last_Pinged; + +typedef struct Onion_Friend { + bool is_valid; + bool is_online; + + bool know_dht_public_key; + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t real_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + + Onion_Node clients_list[MAX_ONION_CLIENTS]; + uint8_t temp_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + uint64_t last_dht_pk_onion_sent; + uint64_t last_dht_pk_dht_sent; + + uint64_t last_noreplay; + + uint64_t last_populated; // the last time we had a fully populated client nodes list + uint64_t time_last_pinged; // the last time we pinged this friend with any node + + uint32_t run_count; + uint32_t pings; // how many sucessful pings we've made for this friend + + Last_Pinged last_pinged[MAX_STORED_PINGED_NODES]; + uint8_t last_pinged_index; + + recv_tcp_relay_cb *tcp_relay_node_callback; + void *tcp_relay_node_callback_object; + uint32_t tcp_relay_node_callback_number; + + onion_dht_pk_cb *dht_pk_callback; + void *dht_pk_callback_object; + uint32_t dht_pk_callback_number; +} Onion_Friend; + +static const Onion_Friend empty_onion_friend = {false}; + +typedef struct Onion_Data_Handler { + oniondata_handler_cb *function; + void *object; +} Onion_Data_Handler; + +struct Onion_Client { + const Mono_Time *mono_time; + const Logger *logger; + const Random *rng; + + DHT *dht; + Net_Crypto *c; + Networking_Core *net; + Onion_Friend *friends_list; + uint16_t num_friends; + + Onion_Node clients_announce_list[MAX_ONION_CLIENTS_ANNOUNCE]; + uint64_t last_announce; + + Onion_Client_Paths onion_paths_self; + Onion_Client_Paths onion_paths_friends; + + uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; + uint64_t last_run; + uint64_t first_run; + uint64_t last_time_connected; + + uint8_t temp_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + Last_Pinged last_pinged[MAX_STORED_PINGED_NODES]; + + Node_format path_nodes[MAX_PATH_NODES]; + uint16_t path_nodes_index; + + Node_format path_nodes_bs[MAX_PATH_NODES]; + uint16_t path_nodes_index_bs; + + Ping_Array *announce_ping_array; + uint8_t last_pinged_index; + Onion_Data_Handler onion_data_handlers[256]; + + uint64_t last_packet_recv; + uint64_t last_populated; // the last time we had a fully populated path nodes list + + unsigned int onion_connected; + bool udp_connected; +}; + +DHT *onion_get_dht(const Onion_Client *onion_c) +{ + return onion_c->dht; +} + +Net_Crypto *onion_get_net_crypto(const Onion_Client *onion_c) +{ + return onion_c->c; +} + +/** @brief Add a node to the path_nodes bootstrap array. + * + * If a node with the given public key was already in the bootstrap array, this function has no + * effect and returns successfully. There is currently no way to update the IP/port for a bootstrap + * node, so if it changes, the Onion_Client must be recreated. + * + * @param onion_c The onion client object. + * @param ip_port IP/port for the bootstrap node. + * @param public_key DHT public key for the bootstrap node. + * + * @retval false on failure + * @retval true on success + */ +bool onion_add_bs_path_node(Onion_Client *onion_c, const IP_Port *ip_port, const uint8_t *public_key) +{ + if (!net_family_is_ipv4(ip_port->ip.family) && !net_family_is_ipv6(ip_port->ip.family)) { + return false; + } + + for (unsigned int i = 0; i < MAX_PATH_NODES; ++i) { + if (pk_equal(public_key, onion_c->path_nodes_bs[i].public_key)) { + return true; + } + } + + onion_c->path_nodes_bs[onion_c->path_nodes_index_bs % MAX_PATH_NODES].ip_port = *ip_port; + memcpy(onion_c->path_nodes_bs[onion_c->path_nodes_index_bs % MAX_PATH_NODES].public_key, public_key, + CRYPTO_PUBLIC_KEY_SIZE); + + const uint16_t last = onion_c->path_nodes_index_bs; + ++onion_c->path_nodes_index_bs; + + if (onion_c->path_nodes_index_bs < last) { + onion_c->path_nodes_index_bs = MAX_PATH_NODES + 1; + } + + return true; +} + +/** @brief Add a node to the path_nodes array. + * + * return -1 on failure + * return 0 on success + */ +non_null() +static int onion_add_path_node(Onion_Client *onion_c, const IP_Port *ip_port, const uint8_t *public_key) +{ + if (!net_family_is_ipv4(ip_port->ip.family) && !net_family_is_ipv6(ip_port->ip.family)) { + return -1; + } + + for (unsigned int i = 0; i < MAX_PATH_NODES; ++i) { + if (pk_equal(public_key, onion_c->path_nodes[i].public_key)) { + return -1; + } + } + + onion_c->path_nodes[onion_c->path_nodes_index % MAX_PATH_NODES].ip_port = *ip_port; + memcpy(onion_c->path_nodes[onion_c->path_nodes_index % MAX_PATH_NODES].public_key, public_key, + CRYPTO_PUBLIC_KEY_SIZE); + + const uint16_t last = onion_c->path_nodes_index; + ++onion_c->path_nodes_index; + + if (onion_c->path_nodes_index < last) { + onion_c->path_nodes_index = MAX_PATH_NODES + 1; + } + + return 0; +} + +/** @brief Put up to max_num nodes in nodes. + * + * return the number of nodes. + */ +uint16_t onion_backup_nodes(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num) +{ + if (max_num == 0) { + return 0; + } + + const uint16_t num_nodes = min_u16(onion_c->path_nodes_index, MAX_PATH_NODES); + uint16_t i = 0; + + while (i < max_num && i < num_nodes) { + nodes[i] = onion_c->path_nodes[(onion_c->path_nodes_index - (1 + i)) % num_nodes]; + ++i; + } + + for (uint16_t j = 0; i < max_num && j < MAX_PATH_NODES && j < onion_c->path_nodes_index_bs; ++j) { + bool already_saved = false; + + for (uint16_t k = 0; k < num_nodes; ++k) { + if (pk_equal(nodes[k].public_key, onion_c->path_nodes_bs[j].public_key)) { + already_saved = true; + break; + } + } + + if (!already_saved) { + nodes[i] = onion_c->path_nodes_bs[j]; + ++i; + } + } + + return i; +} + +/** @brief Put up to max_num random nodes in nodes. + * + * return the number of nodes. + */ +non_null() +static uint16_t random_nodes_path_onion(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num) +{ + if (max_num == 0) { + return 0; + } + + const uint16_t num_nodes = min_u16(onion_c->path_nodes_index, MAX_PATH_NODES); + + // if (dht_non_lan_connected(onion_c->dht)) { + if (dht_isconnected(onion_c->dht)) { + if (num_nodes == 0) { + return 0; + } + + for (unsigned int i = 0; i < max_num; ++i) { + const uint32_t rand_idx = random_range_u32(onion_c->rng, num_nodes); + nodes[i] = onion_c->path_nodes[rand_idx]; + } + } else { + const int random_tcp = get_random_tcp_con_number(onion_c->c); + + if (random_tcp == -1) { + return 0; + } + + if (num_nodes >= 2) { + nodes[0] = empty_node_format; + nodes[0].ip_port = tcp_connections_number_to_ip_port(random_tcp); + + for (unsigned int i = 1; i < max_num; ++i) { + const uint32_t rand_idx = random_range_u32(onion_c->rng, num_nodes); + nodes[i] = onion_c->path_nodes[rand_idx]; + } + } else { + const uint16_t num_nodes_bs = min_u16(onion_c->path_nodes_index_bs, MAX_PATH_NODES); + + if (num_nodes_bs == 0) { + return 0; + } + + nodes[0] = empty_node_format; + nodes[0].ip_port = tcp_connections_number_to_ip_port(random_tcp); + + for (unsigned int i = 1; i < max_num; ++i) { + const uint32_t rand_idx = random_range_u32(onion_c->rng, num_nodes_bs); + nodes[i] = onion_c->path_nodes_bs[rand_idx]; + } + } + } + + return max_num; +} + +/** + * return -1 if nodes are suitable for creating a new path. + * return path number of already existing similar path if one already exists. + */ +non_null() +static int is_path_used(const Mono_Time *mono_time, const Onion_Client_Paths *onion_paths, const Node_format *nodes) +{ + for (unsigned int i = 0; i < NUMBER_ONION_PATHS; ++i) { + if (mono_time_is_timeout(mono_time, onion_paths->last_path_success[i], ONION_PATH_TIMEOUT)) { + continue; + } + + if (mono_time_is_timeout(mono_time, onion_paths->path_creation_time[i], ONION_PATH_MAX_LIFETIME)) { + continue; + } + + // TODO(irungentoo): do we really have to check it with the last node? + if (ipport_equal(&onion_paths->paths[i].ip_port1, &nodes[ONION_PATH_LENGTH - 1].ip_port)) { + return i; + } + } + + return -1; +} + +/** is path timed out */ +non_null() +static bool path_timed_out(const Mono_Time *mono_time, const Onion_Client_Paths *onion_paths, uint32_t pathnum) +{ + pathnum = pathnum % NUMBER_ONION_PATHS; + + const bool is_new = onion_paths->last_path_success[pathnum] == onion_paths->path_creation_time[pathnum]; + const uint64_t timeout = is_new ? ONION_PATH_FIRST_TIMEOUT : ONION_PATH_TIMEOUT; + + return (onion_paths->last_path_used_times[pathnum] >= ONION_PATH_MAX_NO_RESPONSE_USES + && mono_time_is_timeout(mono_time, onion_paths->last_path_used[pathnum], timeout)) + || mono_time_is_timeout(mono_time, onion_paths->path_creation_time[pathnum], ONION_PATH_MAX_LIFETIME); +} + +/** should node be considered to have timed out */ +non_null() +static bool onion_node_timed_out(const Onion_Node *node, const Mono_Time *mono_time) +{ + return node->timestamp == 0 + || (node->pings_since_last_response >= ONION_NODE_MAX_PINGS + && mono_time_is_timeout(mono_time, node->last_pinged, ONION_NODE_TIMEOUT)); +} + +/** @brief Create a new path or use an old suitable one (if pathnum is valid) + * or a random one from onion_paths. + * + * return -1 on failure + * return 0 on success + * + * TODO(irungentoo): Make this function better, it currently probably is + * vulnerable to some attacks that could deanonimize us. + */ +non_null() +static int random_path(const Onion_Client *onion_c, Onion_Client_Paths *onion_paths, uint32_t pathnum, Onion_Path *path) +{ + if (pathnum == UINT32_MAX) { + pathnum = random_range_u32(onion_c->rng, NUMBER_ONION_PATHS); + } else { + pathnum = pathnum % NUMBER_ONION_PATHS; + } + + if (path_timed_out(onion_c->mono_time, onion_paths, pathnum)) { + Node_format nodes[ONION_PATH_LENGTH]; + + if (random_nodes_path_onion(onion_c, nodes, ONION_PATH_LENGTH) != ONION_PATH_LENGTH) { + return -1; + } + + const int n = is_path_used(onion_c->mono_time, onion_paths, nodes); + + if (n == -1) { + if (create_onion_path(onion_c->rng, onion_c->dht, &onion_paths->paths[pathnum], nodes) == -1) { + return -1; + } + + onion_paths->path_creation_time[pathnum] = mono_time_get(onion_c->mono_time); + onion_paths->last_path_success[pathnum] = onion_paths->path_creation_time[pathnum]; + onion_paths->last_path_used_times[pathnum] = ONION_PATH_MAX_NO_RESPONSE_USES / 2; + + uint32_t path_num = random_u32(onion_c->rng); + path_num /= NUMBER_ONION_PATHS; + path_num *= NUMBER_ONION_PATHS; + path_num += pathnum; + + onion_paths->paths[pathnum].path_num = path_num; + } else { + pathnum = n; + } + } + + if (onion_paths->last_path_used_times[pathnum] < ONION_PATH_MAX_NO_RESPONSE_USES) { + onion_paths->last_path_used[pathnum] = mono_time_get(onion_c->mono_time); + } + + ++onion_paths->last_path_used_times[pathnum]; + *path = onion_paths->paths[pathnum]; + return 0; +} + +/** Does path with path_num exist. */ +non_null() +static bool path_exists(const Mono_Time *mono_time, const Onion_Client_Paths *onion_paths, uint32_t path_num) +{ + if (path_timed_out(mono_time, onion_paths, path_num)) { + return false; + } + + return onion_paths->paths[path_num % NUMBER_ONION_PATHS].path_num == path_num; +} + +/** Set path timeouts, return the path number. */ +non_null() +static uint32_t set_path_timeouts(Onion_Client *onion_c, uint32_t num, uint32_t path_num) +{ + if (num > onion_c->num_friends) { + return -1; + } + + Onion_Client_Paths *onion_paths; + + if (num == 0) { + onion_paths = &onion_c->onion_paths_self; + } else { + onion_paths = &onion_c->onion_paths_friends; + } + + if (onion_paths->paths[path_num % NUMBER_ONION_PATHS].path_num == path_num) { + onion_paths->last_path_success[path_num % NUMBER_ONION_PATHS] = mono_time_get(onion_c->mono_time); + onion_paths->last_path_used_times[path_num % NUMBER_ONION_PATHS] = 0; + + Node_format nodes[ONION_PATH_LENGTH]; + + if (onion_path_to_nodes(nodes, ONION_PATH_LENGTH, &onion_paths->paths[path_num % NUMBER_ONION_PATHS]) == 0) { + for (unsigned int i = 0; i < ONION_PATH_LENGTH; ++i) { + onion_add_path_node(onion_c, &nodes[i].ip_port, nodes[i].public_key); + } + } + + return path_num; + } + + return -1; +} + +/** @brief Function to send onion packet via TCP and UDP. + * + * return -1 on failure. + * return 0 on success. + */ +non_null() +static int send_onion_packet_tcp_udp(const Onion_Client *onion_c, const Onion_Path *path, const IP_Port *dest, + const uint8_t *data, uint16_t length) +{ + if (net_family_is_ipv4(path->ip_port1.ip.family) || net_family_is_ipv6(path->ip_port1.ip.family)) { + uint8_t packet[ONION_MAX_PACKET_SIZE]; + const int len = create_onion_packet(onion_c->rng, packet, sizeof(packet), path, dest, data, length); + + if (len == -1) { + return -1; + } + + if (sendpacket(onion_c->net, &path->ip_port1, packet, len) != len) { + return -1; + } + + return 0; + } + + unsigned int tcp_connections_number; + + if (ip_port_to_tcp_connections_number(&path->ip_port1, &tcp_connections_number)) { + uint8_t packet[ONION_MAX_PACKET_SIZE]; + const int len = create_onion_packet_tcp(onion_c->rng, packet, sizeof(packet), path, dest, data, length); + + if (len == -1) { + return -1; + } + + return send_tcp_onion_request(onion_c->c, tcp_connections_number, packet, len); + } + + return -1; +} + +/** @brief Creates a sendback for use in an announce request. + * + * num is 0 if we used our secret public key for the announce + * num is 1 + friendnum if we use a temporary one. + * + * Public key is the key we will be sending it to. + * ip_port is the ip_port of the node we will be sending + * it to. + * + * sendback must be at least ONION_ANNOUNCE_SENDBACK_DATA_LENGTH big + * + * return -1 on failure + * return 0 on success + * + */ +non_null() +static int new_sendback(Onion_Client *onion_c, uint32_t num, const uint8_t *public_key, const IP_Port *ip_port, + uint32_t path_num, uint64_t *sendback) +{ + uint8_t data[sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port) + sizeof(uint32_t)]; + memcpy(data, &num, sizeof(uint32_t)); + memcpy(data + sizeof(uint32_t), public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE, ip_port, sizeof(IP_Port)); + memcpy(data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port), &path_num, sizeof(uint32_t)); + *sendback = ping_array_add(onion_c->announce_ping_array, onion_c->mono_time, onion_c->rng, data, sizeof(data)); + + if (*sendback == 0) { + return -1; + } + + return 0; +} + +/** @brief Checks if the sendback is valid and returns the public key contained in it in ret_pubkey and the + * ip contained in it in ret_ip_port + * + * sendback is the sendback ONION_ANNOUNCE_SENDBACK_DATA_LENGTH big + * ret_pubkey must be at least CRYPTO_PUBLIC_KEY_SIZE big + * ret_ip_port must be at least 1 big + * + * return -1 on failure + * return num (see new_sendback(...)) on success + */ +non_null() +static uint32_t check_sendback(Onion_Client *onion_c, const uint8_t *sendback, uint8_t *ret_pubkey, + IP_Port *ret_ip_port, uint32_t *path_num) +{ + uint64_t sback; + memcpy(&sback, sendback, sizeof(uint64_t)); + uint8_t data[sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port) + sizeof(uint32_t)]; + + if (ping_array_check(onion_c->announce_ping_array, onion_c->mono_time, data, sizeof(data), sback) != sizeof(data)) { + return -1; + } + + memcpy(ret_pubkey, data + sizeof(uint32_t), CRYPTO_PUBLIC_KEY_SIZE); + memcpy(ret_ip_port, data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE, sizeof(IP_Port)); + memcpy(path_num, data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port), sizeof(uint32_t)); + + uint32_t num; + memcpy(&num, data, sizeof(uint32_t)); + return num; +} + +non_null(1, 3, 4) nullable(5) +static int client_send_announce_request(Onion_Client *onion_c, uint32_t num, const IP_Port *dest, + const uint8_t *dest_pubkey, const uint8_t *ping_id, uint32_t pathnum) +{ + if (num > onion_c->num_friends) { + return -1; + } + + uint64_t sendback; + Onion_Path path; + + if (num == 0) { + if (random_path(onion_c, &onion_c->onion_paths_self, pathnum, &path) == -1) { + return -1; + } + } else { + if (random_path(onion_c, &onion_c->onion_paths_friends, pathnum, &path) == -1) { + return -1; + } + } + + if (new_sendback(onion_c, num, dest_pubkey, dest, path.path_num, &sendback) == -1) { + return -1; + } + + uint8_t zero_ping_id[ONION_PING_ID_SIZE] = {0}; + + if (ping_id == nullptr) { + ping_id = zero_ping_id; + } + + uint8_t request[ONION_ANNOUNCE_REQUEST_SIZE]; + int len; + + if (num == 0) { + len = create_announce_request( + onion_c->rng, request, sizeof(request), dest_pubkey, nc_get_self_public_key(onion_c->c), + nc_get_self_secret_key(onion_c->c), ping_id, nc_get_self_public_key(onion_c->c), + onion_c->temp_public_key, sendback); + } else { + len = create_announce_request( + onion_c->rng, request, sizeof(request), dest_pubkey, onion_c->friends_list[num - 1].temp_public_key, + onion_c->friends_list[num - 1].temp_secret_key, ping_id, + onion_c->friends_list[num - 1].real_public_key, zero_ping_id, sendback); + } + + if (len == -1) { + return -1; + } + + return send_onion_packet_tcp_udp(onion_c, &path, dest, request, len); +} + +typedef struct Onion_Client_Cmp_Data { + const Mono_Time *mono_time; + const uint8_t *base_public_key; + Onion_Node entry; +} Onion_Client_Cmp_Data; + +non_null() +static int onion_client_cmp_entry(const void *a, const void *b) +{ + const Onion_Client_Cmp_Data *cmp1 = (const Onion_Client_Cmp_Data *)a; + const Onion_Client_Cmp_Data *cmp2 = (const Onion_Client_Cmp_Data *)b; + const Onion_Node entry1 = cmp1->entry; + const Onion_Node entry2 = cmp2->entry; + const uint8_t *cmp_public_key = cmp1->base_public_key; + + const bool t1 = onion_node_timed_out(&entry1, cmp1->mono_time); + const bool t2 = onion_node_timed_out(&entry2, cmp2->mono_time); + + if (t1 && t2) { + return 0; + } + + if (t1) { + return -1; + } + + if (t2) { + return 1; + } + + const int closest = id_closest(cmp_public_key, entry1.public_key, entry2.public_key); + + if (closest == 1) { + return 1; + } + + if (closest == 2) { + return -1; + } + + return 0; +} + +non_null() +static void sort_onion_node_list(Onion_Node *list, unsigned int length, const Mono_Time *mono_time, + const uint8_t *comp_public_key) +{ + // Pass comp_public_key to qsort with each Client_data entry, so the + // comparison function can use it as the base of comparison. + Onion_Client_Cmp_Data *cmp_list = (Onion_Client_Cmp_Data *)calloc(length, sizeof(Onion_Client_Cmp_Data)); + + if (cmp_list == nullptr) { + return; + } + + for (uint32_t i = 0; i < length; ++i) { + cmp_list[i].mono_time = mono_time; + cmp_list[i].base_public_key = comp_public_key; + cmp_list[i].entry = list[i]; + } + + qsort(cmp_list, length, sizeof(Onion_Client_Cmp_Data), onion_client_cmp_entry); + + for (uint32_t i = 0; i < length; ++i) { + list[i] = cmp_list[i].entry; + } + + free(cmp_list); +} + +non_null() +static int client_add_to_list(Onion_Client *onion_c, uint32_t num, const uint8_t *public_key, const IP_Port *ip_port, + uint8_t is_stored, const uint8_t *pingid_or_key, uint32_t path_used) +{ + if (num > onion_c->num_friends) { + return -1; + } + + Onion_Node *node_list = nullptr; + const uint8_t *reference_id = nullptr; + unsigned int list_length; + + if (num == 0) { + node_list = onion_c->clients_announce_list; + reference_id = nc_get_self_public_key(onion_c->c); + list_length = MAX_ONION_CLIENTS_ANNOUNCE; + + if (is_stored == 1 && !pk_equal(pingid_or_key, onion_c->temp_public_key)) { + is_stored = 0; + } + } else { + if (is_stored >= 2) { + return -1; + } + + node_list = onion_c->friends_list[num - 1].clients_list; + reference_id = onion_c->friends_list[num - 1].real_public_key; + list_length = MAX_ONION_CLIENTS; + } + + sort_onion_node_list(node_list, list_length, onion_c->mono_time, reference_id); + + int index = -1; + bool stored = false; + + if (onion_node_timed_out(&node_list[0], onion_c->mono_time) + || id_closest(reference_id, node_list[0].public_key, public_key) == 2) { + index = 0; + } + + for (unsigned int i = 0; i < list_length; ++i) { + if (pk_equal(node_list[i].public_key, public_key)) { + index = i; + stored = true; + break; + } + } + + if (index == -1) { + return 0; + } + + memcpy(node_list[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + node_list[index].ip_port = *ip_port; + + // TODO(irungentoo): remove this and find a better source of nodes to use for paths. + onion_add_path_node(onion_c, ip_port, public_key); + + if (is_stored == 1) { + memcpy(node_list[index].data_public_key, pingid_or_key, CRYPTO_PUBLIC_KEY_SIZE); + } else { + memcpy(node_list[index].ping_id, pingid_or_key, ONION_PING_ID_SIZE); + } + + node_list[index].is_stored = is_stored; + node_list[index].timestamp = mono_time_get(onion_c->mono_time); + node_list[index].pings_since_last_response = 0; + + if (!stored) { + node_list[index].last_pinged = 0; + node_list[index].added_time = mono_time_get(onion_c->mono_time); + } + + node_list[index].path_used = path_used; + return 0; +} + +non_null() +static bool good_to_ping(const Mono_Time *mono_time, Last_Pinged *last_pinged, uint8_t *last_pinged_index, + const uint8_t *public_key) +{ + for (unsigned int i = 0; i < MAX_STORED_PINGED_NODES; ++i) { + if (!mono_time_is_timeout(mono_time, last_pinged[i].timestamp, MIN_NODE_PING_TIME)) { + if (pk_equal(last_pinged[i].public_key, public_key)) { + return false; + } + } + } + + memcpy(last_pinged[*last_pinged_index % MAX_STORED_PINGED_NODES].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + last_pinged[*last_pinged_index % MAX_STORED_PINGED_NODES].timestamp = mono_time_get(mono_time); + ++*last_pinged_index; + return true; +} + +non_null() +static int client_ping_nodes(Onion_Client *onion_c, uint32_t num, const Node_format *nodes, uint16_t num_nodes, + const IP_Port *source) +{ + if (num > onion_c->num_friends) { + return -1; + } + + if (num_nodes == 0) { + return 0; + } + + const Onion_Node *node_list = nullptr; + const uint8_t *reference_id = nullptr; + unsigned int list_length; + + Last_Pinged *last_pinged = nullptr; + uint8_t *last_pinged_index = nullptr; + + if (num == 0) { + node_list = onion_c->clients_announce_list; + reference_id = nc_get_self_public_key(onion_c->c); + list_length = MAX_ONION_CLIENTS_ANNOUNCE; + last_pinged = onion_c->last_pinged; + last_pinged_index = &onion_c->last_pinged_index; + } else { + node_list = onion_c->friends_list[num - 1].clients_list; + reference_id = onion_c->friends_list[num - 1].real_public_key; + list_length = MAX_ONION_CLIENTS; + last_pinged = onion_c->friends_list[num - 1].last_pinged; + last_pinged_index = &onion_c->friends_list[num - 1].last_pinged_index; + } + + const bool lan_ips_accepted = ip_is_lan(&source->ip); + + for (uint32_t i = 0; i < num_nodes; ++i) { + if (!lan_ips_accepted) { + if (ip_is_lan(&nodes[i].ip_port.ip)) { + continue; + } + } + + if (onion_node_timed_out(&node_list[0], onion_c->mono_time) + || id_closest(reference_id, node_list[0].public_key, nodes[i].public_key) == 2 + || onion_node_timed_out(&node_list[1], onion_c->mono_time) + || id_closest(reference_id, node_list[1].public_key, nodes[i].public_key) == 2) { + uint32_t j; + + /* check if node is already in list. */ + for (j = 0; j < list_length; ++j) { + if (pk_equal(node_list[j].public_key, nodes[i].public_key)) { + break; + } + } + + if (j == list_length && good_to_ping(onion_c->mono_time, last_pinged, last_pinged_index, nodes[i].public_key)) { + client_send_announce_request(onion_c, num, &nodes[i].ip_port, nodes[i].public_key, nullptr, -1); + } + } + } + + return 0; +} + +non_null() +static int handle_announce_response(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Onion_Client *onion_c = (Onion_Client *)object; + + if (length < ONION_ANNOUNCE_RESPONSE_MIN_SIZE || length > ONION_ANNOUNCE_RESPONSE_MAX_SIZE) { + return 1; + } + + const uint16_t len_nodes = length - ONION_ANNOUNCE_RESPONSE_MIN_SIZE; + + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + IP_Port ip_port; + uint32_t path_num; + const uint32_t num = check_sendback(onion_c, packet + 1, public_key, &ip_port, &path_num); + + if (num > onion_c->num_friends) { + return 1; + } + + VLA(uint8_t, plain, 1 + ONION_PING_ID_SIZE + len_nodes); + int len; + + if (num == 0) { + len = decrypt_data(public_key, nc_get_self_secret_key(onion_c->c), + packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, + packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE, + length - (1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE), plain); + } else { + if (!onion_c->friends_list[num - 1].is_valid) { + return 1; + } + + len = decrypt_data(public_key, onion_c->friends_list[num - 1].temp_secret_key, + packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, + packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE, + length - (1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE), plain); + } + + if ((uint32_t)len != SIZEOF_VLA(plain)) { + return 1; + } + + const uint32_t path_used = set_path_timeouts(onion_c, num, path_num); + + if (client_add_to_list(onion_c, num, public_key, &ip_port, plain[0], plain + 1, path_used) == -1) { + return 1; + } + + if (len_nodes != 0) { + Node_format nodes[MAX_SENT_NODES]; + const int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, nullptr, plain + 1 + ONION_PING_ID_SIZE, len_nodes, false); + + if (num_nodes <= 0) { + return 1; + } + + if (client_ping_nodes(onion_c, num, nodes, num_nodes, source) == -1) { + return 1; + } + } + + // TODO(irungentoo): LAN vs non LAN ips?, if we are connected only to LAN, are we offline? + onion_c->last_packet_recv = mono_time_get(onion_c->mono_time); + return 0; +} + +#define DATA_IN_RESPONSE_MIN_SIZE ONION_DATA_IN_RESPONSE_MIN_SIZE + +non_null() +static int handle_data_response(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Onion_Client *onion_c = (Onion_Client *)object; + + if (length <= (ONION_DATA_RESPONSE_MIN_SIZE + DATA_IN_RESPONSE_MIN_SIZE)) { + return 1; + } + + if (length > MAX_DATA_REQUEST_SIZE) { + return 1; + } + + VLA(uint8_t, temp_plain, length - ONION_DATA_RESPONSE_MIN_SIZE); + int len = decrypt_data(packet + 1 + CRYPTO_NONCE_SIZE, onion_c->temp_secret_key, packet + 1, + packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE), temp_plain); + + if ((uint32_t)len != SIZEOF_VLA(temp_plain)) { + return 1; + } + + VLA(uint8_t, plain, SIZEOF_VLA(temp_plain) - DATA_IN_RESPONSE_MIN_SIZE); + len = decrypt_data(temp_plain, nc_get_self_secret_key(onion_c->c), + packet + 1, temp_plain + CRYPTO_PUBLIC_KEY_SIZE, + SIZEOF_VLA(temp_plain) - CRYPTO_PUBLIC_KEY_SIZE, plain); + + if ((uint32_t)len != SIZEOF_VLA(plain)) { + return 1; + } + + if (onion_c->onion_data_handlers[plain[0]].function == nullptr) { + return 1; + } + + return onion_c->onion_data_handlers[plain[0]].function(onion_c->onion_data_handlers[plain[0]].object, temp_plain, plain, + SIZEOF_VLA(plain), userdata); +} + +#define DHTPK_DATA_MIN_LENGTH (1 + sizeof(uint64_t) + CRYPTO_PUBLIC_KEY_SIZE) +#define DHTPK_DATA_MAX_LENGTH (DHTPK_DATA_MIN_LENGTH + sizeof(Node_format)*MAX_SENT_NODES) +non_null(1, 2, 3) nullable(5) +static int handle_dhtpk_announce(void *object, const uint8_t *source_pubkey, const uint8_t *data, uint16_t length, + void *userdata) +{ + Onion_Client *onion_c = (Onion_Client *)object; + + if (length < DHTPK_DATA_MIN_LENGTH) { + return 1; + } + + if (length > DHTPK_DATA_MAX_LENGTH) { + return 1; + } + + const int friend_num = onion_friend_num(onion_c, source_pubkey); + + if (friend_num == -1) { + return 1; + } + + uint64_t no_replay; + net_unpack_u64(data + 1, &no_replay); + + if (no_replay <= onion_c->friends_list[friend_num].last_noreplay) { + return 1; + } + + onion_c->friends_list[friend_num].last_noreplay = no_replay; + + if (onion_c->friends_list[friend_num].dht_pk_callback != nullptr) { + onion_c->friends_list[friend_num].dht_pk_callback(onion_c->friends_list[friend_num].dht_pk_callback_object, + onion_c->friends_list[friend_num].dht_pk_callback_number, data + 1 + sizeof(uint64_t), userdata); + } + + onion_set_friend_DHT_pubkey(onion_c, friend_num, data + 1 + sizeof(uint64_t)); + + const uint16_t len_nodes = length - DHTPK_DATA_MIN_LENGTH; + + if (len_nodes != 0) { + Node_format nodes[MAX_SENT_NODES]; + const int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, nullptr, data + 1 + sizeof(uint64_t) + CRYPTO_PUBLIC_KEY_SIZE, + len_nodes, true); + + if (num_nodes <= 0) { + return 1; + } + + for (int i = 0; i < num_nodes; ++i) { + const Family family = nodes[i].ip_port.ip.family; + + if (net_family_is_ipv4(family) || net_family_is_ipv6(family)) { + dht_getnodes(onion_c->dht, &nodes[i].ip_port, nodes[i].public_key, onion_c->friends_list[friend_num].dht_public_key); + } else if (net_family_is_tcp_ipv4(family) || net_family_is_tcp_ipv6(family)) { + if (onion_c->friends_list[friend_num].tcp_relay_node_callback != nullptr) { + void *obj = onion_c->friends_list[friend_num].tcp_relay_node_callback_object; + const uint32_t number = onion_c->friends_list[friend_num].tcp_relay_node_callback_number; + onion_c->friends_list[friend_num].tcp_relay_node_callback(obj, number, &nodes[i].ip_port, nodes[i].public_key); + } + } + } + } + + return 0; +} + +non_null() +static int handle_tcp_onion(void *object, const uint8_t *data, uint16_t length, void *userdata) +{ + if (length == 0) { + return 1; + } + + IP_Port ip_port = {{{0}}}; + ip_port.ip.family = net_family_tcp_server(); + + if (data[0] == NET_PACKET_ANNOUNCE_RESPONSE_OLD) { + return handle_announce_response(object, &ip_port, data, length, userdata); + } + + if (data[0] == NET_PACKET_ONION_DATA_RESPONSE) { + return handle_data_response(object, &ip_port, data, length, userdata); + } + + return 1; +} + +/** @brief Send data of length length to friendnum. + * Maximum length of data is ONION_CLIENT_MAX_DATA_SIZE. + * This data will be received by the friend using the Onion_Data_Handlers callbacks. + * + * Even if this function succeeds, the friend might not receive any data. + * + * return the number of packets sent on success + * return -1 on failure. + */ +int send_onion_data(Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + if (length + DATA_IN_RESPONSE_MIN_SIZE > MAX_DATA_REQUEST_SIZE) { + return -1; + } + + if (length == 0) { + return -1; + } + + unsigned int good_nodes[MAX_ONION_CLIENTS]; + unsigned int num_good = 0; + unsigned int num_nodes = 0; + const Onion_Node *node_list = onion_c->friends_list[friend_num].clients_list; + + for (unsigned int i = 0; i < MAX_ONION_CLIENTS; ++i) { + if (onion_node_timed_out(&node_list[i], onion_c->mono_time)) { + continue; + } + + ++num_nodes; + + if (node_list[i].is_stored != 0) { + good_nodes[num_good] = i; + ++num_good; + } + } + + if (num_good < (num_nodes - 1) / 4 + 1) { + return -1; + } + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_nonce(onion_c->rng, nonce); + + VLA(uint8_t, packet, DATA_IN_RESPONSE_MIN_SIZE + length); + memcpy(packet, nc_get_self_public_key(onion_c->c), CRYPTO_PUBLIC_KEY_SIZE); + int len = encrypt_data(onion_c->friends_list[friend_num].real_public_key, + nc_get_self_secret_key(onion_c->c), nonce, data, + length, packet + CRYPTO_PUBLIC_KEY_SIZE); + + if ((uint32_t)len + CRYPTO_PUBLIC_KEY_SIZE != SIZEOF_VLA(packet)) { + return -1; + } + + unsigned int good = 0; + + for (unsigned int i = 0; i < num_good; ++i) { + Onion_Path path; + + if (random_path(onion_c, &onion_c->onion_paths_friends, -1, &path) == -1) { + continue; + } + + uint8_t o_packet[ONION_MAX_PACKET_SIZE]; + len = create_data_request( + onion_c->rng, o_packet, sizeof(o_packet), onion_c->friends_list[friend_num].real_public_key, + node_list[good_nodes[i]].data_public_key, nonce, packet, SIZEOF_VLA(packet)); + + if (len == -1) { + continue; + } + + if (send_onion_packet_tcp_udp(onion_c, &path, &node_list[good_nodes[i]].ip_port, o_packet, len) == 0) { + ++good; + } + } + + return good; +} + +/** @brief Try to send the dht public key via the DHT instead of onion + * + * Even if this function succeeds, the friend might not receive any data. + * + * return the number of packets sent on success + * return -1 on failure. + */ +non_null() +static int send_dht_dhtpk(const Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + if (!onion_c->friends_list[friend_num].know_dht_public_key) { + return -1; + } + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_nonce(onion_c->rng, nonce); + + VLA(uint8_t, temp, DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE + length); + memcpy(temp, nc_get_self_public_key(onion_c->c), CRYPTO_PUBLIC_KEY_SIZE); + memcpy(temp + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); + int len = encrypt_data(onion_c->friends_list[friend_num].real_public_key, + nc_get_self_secret_key(onion_c->c), nonce, data, + length, temp + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); + + if ((uint32_t)len + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE != SIZEOF_VLA(temp)) { + return -1; + } + + uint8_t packet_data[MAX_CRYPTO_REQUEST_SIZE]; + len = create_request( + onion_c->rng, dht_get_self_public_key(onion_c->dht), dht_get_self_secret_key(onion_c->dht), packet_data, + onion_c->friends_list[friend_num].dht_public_key, temp, SIZEOF_VLA(temp), CRYPTO_PACKET_DHTPK); + assert(len <= UINT16_MAX); + const Packet packet = {packet_data, (uint16_t)len}; + + if (len == -1) { + return -1; + } + + return route_to_friend(onion_c->dht, onion_c->friends_list[friend_num].dht_public_key, &packet); +} + +non_null() +static int handle_dht_dhtpk(void *object, const IP_Port *source, const uint8_t *source_pubkey, const uint8_t *packet, + uint16_t length, void *userdata) +{ + Onion_Client *onion_c = (Onion_Client *)object; + + if (length < DHTPK_DATA_MIN_LENGTH + DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE) { + return 1; + } + + if (length > DHTPK_DATA_MAX_LENGTH + DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE) { + return 1; + } + + uint8_t plain[DHTPK_DATA_MAX_LENGTH]; + const int len = decrypt_data(packet, nc_get_self_secret_key(onion_c->c), + packet + CRYPTO_PUBLIC_KEY_SIZE, + packet + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + length - (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE), plain); + + if (len != length - (DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE)) { + return 1; + } + + if (!pk_equal(source_pubkey, plain + 1 + sizeof(uint64_t))) { + return 1; + } + + return handle_dhtpk_announce(onion_c, packet, plain, len, userdata); +} +/** @brief Send the packets to tell our friends what our DHT public key is. + * + * if onion_dht_both is 0, use only the onion to send the packet. + * if it is 1, use only the dht. + * if it is something else, use both. + * + * return the number of packets sent on success + * return -1 on failure. + */ +non_null() +static int send_dhtpk_announce(Onion_Client *onion_c, uint16_t friend_num, uint8_t onion_dht_both) +{ + if (friend_num >= onion_c->num_friends) { + return -1; + } + + uint8_t data[DHTPK_DATA_MAX_LENGTH]; + data[0] = ONION_DATA_DHTPK; + const uint64_t no_replay = mono_time_get(onion_c->mono_time); + net_pack_u64(data + 1, no_replay); + memcpy(data + 1 + sizeof(uint64_t), dht_get_self_public_key(onion_c->dht), CRYPTO_PUBLIC_KEY_SIZE); + Node_format nodes[MAX_SENT_NODES]; + const uint16_t num_relays = copy_connected_tcp_relays(onion_c->c, nodes, MAX_SENT_NODES / 2); + uint16_t num_nodes = closelist_nodes(onion_c->dht, &nodes[num_relays], MAX_SENT_NODES - num_relays); + num_nodes += num_relays; + int nodes_len = 0; + + if (num_nodes != 0) { + nodes_len = pack_nodes(onion_c->logger, data + DHTPK_DATA_MIN_LENGTH, DHTPK_DATA_MAX_LENGTH - DHTPK_DATA_MIN_LENGTH, nodes, num_nodes); + + if (nodes_len <= 0) { + return -1; + } + } + + int num1 = -1; + int num2 = -1; + + if (onion_dht_both != 1) { + num1 = send_onion_data(onion_c, friend_num, data, DHTPK_DATA_MIN_LENGTH + nodes_len); + } + + if (onion_dht_both != 0) { + num2 = send_dht_dhtpk(onion_c, friend_num, data, DHTPK_DATA_MIN_LENGTH + nodes_len); + } + + if (num1 == -1) { + return num2; + } + + if (num2 == -1) { + return num1; + } + + return num1 + num2; +} + +/** @brief Get the friend_num of a friend. + * + * return -1 on failure. + * return friend number on success. + */ +int onion_friend_num(const Onion_Client *onion_c, const uint8_t *public_key) +{ + for (unsigned int i = 0; i < onion_c->num_friends; ++i) { + if (!onion_c->friends_list[i].is_valid) { + continue; + } + + if (pk_equal(public_key, onion_c->friends_list[i].real_public_key)) { + return i; + } + } + + return -1; +} + +/** @brief Set the size of the friend list to num. + * + * @retval -1 if realloc fails. + * @retval 0 if it succeeds. + */ +non_null() +static int realloc_onion_friends(Onion_Client *onion_c, uint32_t num) +{ + if (num == 0) { + free(onion_c->friends_list); + onion_c->friends_list = nullptr; + return 0; + } + + Onion_Friend *newonion_friends = (Onion_Friend *)realloc(onion_c->friends_list, num * sizeof(Onion_Friend)); + + if (newonion_friends == nullptr) { + return -1; + } + + onion_c->friends_list = newonion_friends; + return 0; +} + +/** @brief Add a friend who we want to connect to. + * + * return -1 on failure. + * return the friend number on success or if the friend was already added. + */ +int onion_addfriend(Onion_Client *onion_c, const uint8_t *public_key) +{ + const int num = onion_friend_num(onion_c, public_key); + + if (num != -1) { + return num; + } + + unsigned int index = -1; + + for (unsigned int i = 0; i < onion_c->num_friends; ++i) { + if (!onion_c->friends_list[i].is_valid) { + index = i; + break; + } + } + + if (index == (uint32_t) -1) { + if (realloc_onion_friends(onion_c, onion_c->num_friends + 1) == -1) { + return -1; + } + + index = onion_c->num_friends; + onion_c->friends_list[onion_c->num_friends] = empty_onion_friend; + ++onion_c->num_friends; + } + + onion_c->friends_list[index].is_valid = true; + memcpy(onion_c->friends_list[index].real_public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + crypto_new_keypair(onion_c->rng, onion_c->friends_list[index].temp_public_key, onion_c->friends_list[index].temp_secret_key); + return index; +} + +/** @brief Delete a friend. + * + * return -1 on failure. + * return the deleted friend number on success. + */ +int onion_delfriend(Onion_Client *onion_c, int friend_num) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + +#if 0 + + if (onion_c->friends_list[friend_num].know_dht_public_key) { + dht_delfriend(onion_c->dht, onion_c->friends_list[friend_num].dht_public_key, 0); + } + +#endif + + crypto_memzero(&onion_c->friends_list[friend_num], sizeof(Onion_Friend)); + unsigned int i; + + for (i = onion_c->num_friends; i != 0; --i) { + if (onion_c->friends_list[i - 1].is_valid) { + break; + } + } + + if (onion_c->num_friends != i) { + onion_c->num_friends = i; + realloc_onion_friends(onion_c, onion_c->num_friends); + } + + return friend_num; +} + +/** @brief Set the function for this friend that will be callbacked with object and number + * when that friend gives us one of the TCP relays they are connected to. + * + * object and number will be passed as argument to this function. + * + * return -1 on failure. + * return 0 on success. + */ +int recv_tcp_relay_handler(Onion_Client *onion_c, int friend_num, + recv_tcp_relay_cb *callback, void *object, uint32_t number) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + onion_c->friends_list[friend_num].tcp_relay_node_callback = callback; + onion_c->friends_list[friend_num].tcp_relay_node_callback_object = object; + onion_c->friends_list[friend_num].tcp_relay_node_callback_number = number; + return 0; +} + +/** @brief Set the function for this friend that will be callbacked with object and number + * when that friend gives us their DHT temporary public key. + * + * object and number will be passed as argument to this function. + * + * return -1 on failure. + * return 0 on success. + */ +int onion_dht_pk_callback(Onion_Client *onion_c, int friend_num, + onion_dht_pk_cb *function, void *object, uint32_t number) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + onion_c->friends_list[friend_num].dht_pk_callback = function; + onion_c->friends_list[friend_num].dht_pk_callback_object = object; + onion_c->friends_list[friend_num].dht_pk_callback_number = number; + return 0; +} + +/** @brief Set a friend's DHT public key. + * + * return -1 on failure. + * return 0 on success. + */ +int onion_set_friend_DHT_pubkey(Onion_Client *onion_c, int friend_num, const uint8_t *dht_key) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + if (!onion_c->friends_list[friend_num].is_valid) { + return -1; + } + + if (onion_c->friends_list[friend_num].know_dht_public_key) { + if (pk_equal(dht_key, onion_c->friends_list[friend_num].dht_public_key)) { + return -1; + } + } + + onion_c->friends_list[friend_num].know_dht_public_key = true; + memcpy(onion_c->friends_list[friend_num].dht_public_key, dht_key, CRYPTO_PUBLIC_KEY_SIZE); + + return 0; +} + +/** @brief Copy friends DHT public key into dht_key. + * + * return 0 on failure (no key copied). + * return 1 on success (key copied). + */ +unsigned int onion_getfriend_DHT_pubkey(const Onion_Client *onion_c, int friend_num, uint8_t *dht_key) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return 0; + } + + if (!onion_c->friends_list[friend_num].is_valid) { + return 0; + } + + if (!onion_c->friends_list[friend_num].know_dht_public_key) { + return 0; + } + + memcpy(dht_key, onion_c->friends_list[friend_num].dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + return 1; +} + +/** @brief Get the ip of friend friendnum and put it in ip_port + * + * @retval -1 if public_key does NOT refer to a friend + * @retval 0 if public_key refers to a friend and we failed to find the friend (yet) + * @retval 1 if public_key refers to a friend and we found them + */ +int onion_getfriendip(const Onion_Client *onion_c, int friend_num, IP_Port *ip_port) +{ + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + + if (onion_getfriend_DHT_pubkey(onion_c, friend_num, dht_public_key) == 0) { + return -1; + } + + return dht_getfriendip(onion_c->dht, dht_public_key, ip_port); +} + + +/** @brief Set if friend is online or not. + * + * NOTE: This function is there and should be used so that we don't send + * useless packets to the friend if they are online. + * + * return -1 on failure. + * return 0 on success. + */ +int onion_set_friend_online(Onion_Client *onion_c, int friend_num, bool is_online) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + onion_c->friends_list[friend_num].is_online = is_online; + + /* This should prevent some clock related issues */ + if (!is_online) { + onion_c->friends_list[friend_num].last_noreplay = 0; + onion_c->friends_list[friend_num].run_count = 0; + } + + return 0; +} + +non_null() +static void populate_path_nodes(Onion_Client *onion_c) +{ + Node_format node_list[MAX_FRIEND_CLIENTS]; + + const unsigned int num_nodes = randfriends_nodes(onion_c->dht, node_list, MAX_FRIEND_CLIENTS); + + for (unsigned int i = 0; i < num_nodes; ++i) { + onion_add_path_node(onion_c, &node_list[i].ip_port, node_list[i].public_key); + } +} + +/* How often we ping new friends per node */ +#define ANNOUNCE_FRIEND_NEW_INTERVAL 3 + +/* How long we consider a friend new based on the value of their run_count */ +#define ANNOUNCE_FRIEND_RUN_COUNT_BEGINNING 5 + +/* How often we try to re-populate the nodes lists if we don't meet a minimum threshhold of nodes */ +#define ANNOUNCE_POPULATE_TIMEOUT (60 * 10) + +/* The max time between lookup requests for a friend per node */ +#define ANNOUNCE_FRIEND_MAX_INTERVAL (60 * 60) + +/* Max exponent when calculating the announce request interval */ +#define MAX_RUN_COUNT_EXPONENT 12 + +non_null() +static void do_friend(Onion_Client *onion_c, uint16_t friendnum) +{ + if (friendnum >= onion_c->num_friends) { + return; + } + + Onion_Friend *o_friend = &onion_c->friends_list[friendnum]; + + if (!o_friend->is_valid) { + return; + } + + uint32_t interval; + const uint64_t tm = mono_time_get(onion_c->mono_time); + const bool friend_is_new = o_friend->run_count <= ANNOUNCE_FRIEND_RUN_COUNT_BEGINNING; + + if (!friend_is_new) { + // how often we ping a node for a friend depends on how many times we've already tried. + // the interval increases exponentially, as the longer a friend has been offline, the less + // likely the case is that they're online and failed to find us + const uint32_t c = 1 << min_u32(MAX_RUN_COUNT_EXPONENT, o_friend->run_count - 2); + interval = min_u32(c, ANNOUNCE_FRIEND_MAX_INTERVAL); + } else { + interval = ANNOUNCE_FRIEND_NEW_INTERVAL; + } + + if (o_friend->is_online) { + return; + } + + assert(interval >= ANNOUNCE_FRIEND_NEW_INTERVAL); // an int overflow would be devastating + + /* send packets to friend telling them our DHT public key. */ + if (mono_time_is_timeout(onion_c->mono_time, onion_c->friends_list[friendnum].last_dht_pk_onion_sent, + ONION_DHTPK_SEND_INTERVAL)) { + if (send_dhtpk_announce(onion_c, friendnum, 0) >= 1) { + onion_c->friends_list[friendnum].last_dht_pk_onion_sent = tm; + } + } + + if (mono_time_is_timeout(onion_c->mono_time, onion_c->friends_list[friendnum].last_dht_pk_dht_sent, + DHT_DHTPK_SEND_INTERVAL)) { + if (send_dhtpk_announce(onion_c, friendnum, 1) >= 1) { + onion_c->friends_list[friendnum].last_dht_pk_dht_sent = tm; + } + } + + uint16_t count = 0; // number of alive path nodes + + Onion_Node *node_list = o_friend->clients_list; + + for (unsigned i = 0; i < MAX_ONION_CLIENTS; ++i) { + if (onion_node_timed_out(&node_list[i], onion_c->mono_time)) { + continue; + } + + ++count; + + // we don't want new nodes to be pinged immediately + if (node_list[i].last_pinged == 0) { + node_list[i].last_pinged = tm; + continue; + } + + // node hasn't responded in a while so we skip it + if (node_list[i].pings_since_last_response >= ONION_NODE_MAX_PINGS) { + continue; + } + + // space requests out between nodes + if (!mono_time_is_timeout(onion_c->mono_time, o_friend->time_last_pinged, interval / (MAX_ONION_CLIENTS / 2))) { + continue; + } + + if (!mono_time_is_timeout(onion_c->mono_time, node_list[i].last_pinged, interval)) { + continue; + } + + if (client_send_announce_request(onion_c, friendnum + 1, &node_list[i].ip_port, + node_list[i].public_key, nullptr, -1) == 0) { + node_list[i].last_pinged = tm; + o_friend->time_last_pinged = tm; + ++node_list[i].pings_since_last_response; + ++o_friend->pings; + + if (o_friend->pings % (MAX_ONION_CLIENTS / 2) == 0) { + ++o_friend->run_count; + } + } + } + + if (count == MAX_ONION_CLIENTS) { + if (!friend_is_new) { + o_friend->last_populated = tm; + } + + return; + } + + // check if path nodes list for this friend needs to be repopulated + if (count <= MAX_ONION_CLIENTS / 2 + || mono_time_is_timeout(onion_c->mono_time, o_friend->last_populated, ANNOUNCE_POPULATE_TIMEOUT)) { + const uint16_t num_nodes = min_u16(onion_c->path_nodes_index, MAX_PATH_NODES); + const uint16_t n = min_u16(num_nodes, MAX_PATH_NODES / 4); + + if (n == 0) { + return; + } + + o_friend->last_populated = tm; + + for (uint16_t i = 0; i < n; ++i) { + const uint32_t num = random_range_u32(onion_c->rng, num_nodes); + client_send_announce_request(onion_c, friendnum + 1, &onion_c->path_nodes[num].ip_port, + onion_c->path_nodes[num].public_key, nullptr, -1); + } + } +} + + +/** Function to call when onion data packet with contents beginning with byte is received. */ +void oniondata_registerhandler(Onion_Client *onion_c, uint8_t byte, oniondata_handler_cb *cb, void *object) +{ + onion_c->onion_data_handlers[byte].function = cb; + onion_c->onion_data_handlers[byte].object = object; +} + +#define ANNOUNCE_INTERVAL_NOT_ANNOUNCED 3 +#define ANNOUNCE_INTERVAL_ANNOUNCED ONION_NODE_PING_INTERVAL + +#define TIME_TO_STABLE (ONION_NODE_PING_INTERVAL * 6) +#define ANNOUNCE_INTERVAL_STABLE (ONION_NODE_PING_INTERVAL * 8) + +non_null() +static void do_announce(Onion_Client *onion_c) +{ + unsigned int count = 0; + Onion_Node *node_list = onion_c->clients_announce_list; + + for (unsigned int i = 0; i < MAX_ONION_CLIENTS_ANNOUNCE; ++i) { + if (onion_node_timed_out(&node_list[i], onion_c->mono_time)) { + continue; + } + + ++count; + + /* Don't announce ourselves the first time this is run to new peers */ + if (node_list[i].last_pinged == 0) { + node_list[i].last_pinged = 1; + continue; + } + + if (node_list[i].pings_since_last_response >= ONION_NODE_MAX_PINGS) { + continue; + } + + + unsigned int interval = ANNOUNCE_INTERVAL_NOT_ANNOUNCED; + + if (node_list[i].is_stored != 0 + && path_exists(onion_c->mono_time, &onion_c->onion_paths_self, node_list[i].path_used)) { + interval = ANNOUNCE_INTERVAL_ANNOUNCED; + + const uint32_t pathnum = node_list[i].path_used % NUMBER_ONION_PATHS; + + /* A node/path is considered "stable", and can be pinged less + * aggressively, if it has survived for at least TIME_TO_STABLE + * and the latest packets sent to it are not timing out. + */ + if (mono_time_is_timeout(onion_c->mono_time, node_list[i].added_time, TIME_TO_STABLE) + && !(node_list[i].pings_since_last_response > 0 + && mono_time_is_timeout(onion_c->mono_time, node_list[i].last_pinged, ONION_NODE_TIMEOUT)) + && mono_time_is_timeout(onion_c->mono_time, onion_c->onion_paths_self.path_creation_time[pathnum], TIME_TO_STABLE) + && !(onion_c->onion_paths_self.last_path_used_times[pathnum] > 0 + && mono_time_is_timeout(onion_c->mono_time, onion_c->onion_paths_self.last_path_used[pathnum], ONION_PATH_TIMEOUT))) { + interval = ANNOUNCE_INTERVAL_STABLE; + } + } + + if (mono_time_is_timeout(onion_c->mono_time, node_list[i].last_pinged, interval) + || mono_time_is_timeout(onion_c->mono_time, onion_c->last_announce, ONION_NODE_PING_INTERVAL)) { + uint32_t path_to_use = node_list[i].path_used; + + if (node_list[i].pings_since_last_response == ONION_NODE_MAX_PINGS - 1 + && mono_time_is_timeout(onion_c->mono_time, node_list[i].added_time, TIME_TO_STABLE)) { + /* Last chance for a long-lived node - try a random path */ + path_to_use = -1; + } + + if (client_send_announce_request(onion_c, 0, &node_list[i].ip_port, node_list[i].public_key, + node_list[i].ping_id, path_to_use) == 0) { + node_list[i].last_pinged = mono_time_get(onion_c->mono_time); + ++node_list[i].pings_since_last_response; + onion_c->last_announce = mono_time_get(onion_c->mono_time); + } + } + } + + if (count == MAX_ONION_CLIENTS_ANNOUNCE) { + onion_c->last_populated = mono_time_get(onion_c->mono_time); + return; + } + + // check if list needs to be re-populated + if (count <= MAX_ONION_CLIENTS_ANNOUNCE / 2 + || mono_time_is_timeout(onion_c->mono_time, onion_c->last_populated, ANNOUNCE_POPULATE_TIMEOUT)) { + uint16_t num_nodes; + const Node_format *path_nodes; + + if (onion_c->path_nodes_index == 0) { + num_nodes = min_u16(onion_c->path_nodes_index_bs, MAX_PATH_NODES); + path_nodes = onion_c->path_nodes_bs; + } else { + num_nodes = min_u16(onion_c->path_nodes_index, MAX_PATH_NODES); + path_nodes = onion_c->path_nodes; + } + + if (num_nodes == 0) { + return; + } + + for (unsigned int i = 0; i < (MAX_ONION_CLIENTS_ANNOUNCE / 2); ++i) { + const uint32_t num = random_range_u32(onion_c->rng, num_nodes); + client_send_announce_request(onion_c, 0, &path_nodes[num].ip_port, path_nodes[num].public_key, nullptr, -1); + } + } +} + +/** + * @retval false if we are not connected to the network. + * @retval true if we are. + */ +non_null() +static bool onion_isconnected(Onion_Client *onion_c) +{ + unsigned int num = 0; + unsigned int announced = 0; + + if (mono_time_is_timeout(onion_c->mono_time, onion_c->last_packet_recv, ONION_OFFLINE_TIMEOUT)) { + onion_c->last_populated = 0; + return false; + } + + if (onion_c->path_nodes_index == 0) { + onion_c->last_populated = 0; + return false; + } + + for (unsigned int i = 0; i < MAX_ONION_CLIENTS_ANNOUNCE; ++i) { + if (!onion_node_timed_out(&onion_c->clients_announce_list[i], onion_c->mono_time)) { + ++num; + + if (onion_c->clients_announce_list[i].is_stored != 0) { + ++announced; + } + } + } + + unsigned int pnodes = onion_c->path_nodes_index; + + if (pnodes > MAX_ONION_CLIENTS_ANNOUNCE) { + pnodes = MAX_ONION_CLIENTS_ANNOUNCE; + } + + /* Consider ourselves online if we are announced to half or more nodes + * we are connected to */ + if (num != 0 && announced != 0) { + if ((num / 2) <= announced && (pnodes / 2) <= num) { + return true; + } + } + + onion_c->last_populated = 0; + + return false; +} + +non_null() +static void reset_friend_run_counts(Onion_Client *onion_c) +{ + for (uint16_t i = 0; i < onion_c->num_friends; ++i) { + Onion_Friend *o_friend = &onion_c->friends_list[i]; + + if (o_friend->is_valid) { + o_friend->run_count = 0; + } + } +} + +#define ONION_CONNECTION_SECONDS 3 +#define ONION_CONNECTED_TIMEOUT 10 + +Onion_Connection_Status onion_connection_status(const Onion_Client *onion_c) +{ + if (onion_c->onion_connected >= ONION_CONNECTION_SECONDS) { + if (onion_c->udp_connected) { + return ONION_CONNECTION_STATUS_UDP; + } + + return ONION_CONNECTION_STATUS_TCP; + } + + return ONION_CONNECTION_STATUS_NONE; +} + +void do_onion_client(Onion_Client *onion_c) +{ + if (onion_c->last_run == mono_time_get(onion_c->mono_time)) { + return; + } + + if (mono_time_is_timeout(onion_c->mono_time, onion_c->first_run, ONION_CONNECTION_SECONDS)) { + populate_path_nodes(onion_c); + do_announce(onion_c); + } + + if (onion_isconnected(onion_c)) { + if (mono_time_is_timeout(onion_c->mono_time, onion_c->last_time_connected, ONION_CONNECTED_TIMEOUT)) { + reset_friend_run_counts(onion_c); + } + + onion_c->last_time_connected = mono_time_get(onion_c->mono_time); + + if (onion_c->onion_connected < ONION_CONNECTION_SECONDS * 2) { + ++onion_c->onion_connected; + } + } else { + if (onion_c->onion_connected != 0) { + --onion_c->onion_connected; + } + } + + onion_c->udp_connected = dht_non_lan_connected(onion_c->dht); + + if (mono_time_is_timeout(onion_c->mono_time, onion_c->first_run, ONION_CONNECTION_SECONDS * 2)) { + set_tcp_onion_status(nc_get_tcp_c(onion_c->c), !onion_c->udp_connected); + } + + if (onion_connection_status(onion_c) != ONION_CONNECTION_STATUS_NONE) { + for (unsigned i = 0; i < onion_c->num_friends; ++i) { + do_friend(onion_c, i); + } + } + + if (onion_c->last_run == 0) { + onion_c->first_run = mono_time_get(onion_c->mono_time); + } + + onion_c->last_run = mono_time_get(onion_c->mono_time); +} + +Onion_Client *new_onion_client(const Logger *logger, const Random *rng, const Mono_Time *mono_time, Net_Crypto *c) +{ + if (c == nullptr) { + return nullptr; + } + + Onion_Client *onion_c = (Onion_Client *)calloc(1, sizeof(Onion_Client)); + + if (onion_c == nullptr) { + return nullptr; + } + + onion_c->announce_ping_array = ping_array_new(ANNOUNCE_ARRAY_SIZE, ANNOUNCE_TIMEOUT); + + if (onion_c->announce_ping_array == nullptr) { + free(onion_c); + return nullptr; + } + + onion_c->mono_time = mono_time; + onion_c->logger = logger; + onion_c->rng = rng; + onion_c->dht = nc_get_dht(c); + onion_c->net = dht_get_net(onion_c->dht); + onion_c->c = c; + new_symmetric_key(rng, onion_c->secret_symmetric_key); + crypto_new_keypair(rng, onion_c->temp_public_key, onion_c->temp_secret_key); + networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE_OLD, &handle_announce_response, onion_c); + networking_registerhandler(onion_c->net, NET_PACKET_ONION_DATA_RESPONSE, &handle_data_response, onion_c); + oniondata_registerhandler(onion_c, ONION_DATA_DHTPK, &handle_dhtpk_announce, onion_c); + cryptopacket_registerhandler(onion_c->dht, CRYPTO_PACKET_DHTPK, &handle_dht_dhtpk, onion_c); + set_onion_packet_tcp_connection_callback(nc_get_tcp_c(onion_c->c), &handle_tcp_onion, onion_c); + + return onion_c; +} + +void kill_onion_client(Onion_Client *onion_c) +{ + if (onion_c == nullptr) { + return; + } + + ping_array_kill(onion_c->announce_ping_array); + realloc_onion_friends(onion_c, 0); + networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE_OLD, nullptr, nullptr); + networking_registerhandler(onion_c->net, NET_PACKET_ONION_DATA_RESPONSE, nullptr, nullptr); + oniondata_registerhandler(onion_c, ONION_DATA_DHTPK, nullptr, nullptr); + cryptopacket_registerhandler(onion_c->dht, CRYPTO_PACKET_DHTPK, nullptr, nullptr); + set_onion_packet_tcp_connection_callback(nc_get_tcp_c(onion_c->c), nullptr, nullptr); + crypto_memzero(onion_c, sizeof(Onion_Client)); + free(onion_c); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/ping.h b/local_pod_repo/toxcore/toxcore/toxcore/ping.h new file mode 100644 index 0000000..da8d7f0 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/ping.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + * Copyright © 2013 plutooo + */ + +/** + * Buffered pinging using cyclic arrays. + */ +#ifndef C_TOXCORE_TOXCORE_PING_H +#define C_TOXCORE_TOXCORE_PING_H + +#include + +#include "DHT.h" +#include "network.h" + +typedef struct Ping Ping; + +non_null() +Ping *ping_new(const Mono_Time *mono_time, const Random *rng, DHT *dht); + +nullable(1) +void ping_kill(Ping *ping); + +/** @brief Add nodes to the to_ping list. + * All nodes in this list are pinged every TIME_TO_PING seconds + * and are then removed from the list. + * If the list is full the nodes farthest from our public_key are replaced. + * The purpose of this list is to enable quick integration of new nodes into the + * network while preventing amplification attacks. + * + * @retval 0 if node was added. + * @retval -1 if node was not added. + */ +non_null() +int32_t ping_add(Ping *ping, const uint8_t *public_key, const IP_Port *ip_port); + +/** @brief Ping all the valid nodes in the to_ping list every TIME_TO_PING seconds. + * This function must be run at least once every TIME_TO_PING seconds. + */ +non_null() +void ping_iterate(Ping *ping); + +non_null() +void ping_send_request(Ping *ping, const IP_Port *ipp, const uint8_t *public_key); + +#endif // C_TOXCORE_TOXCORE_PING_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/ping.m b/local_pod_repo/toxcore/toxcore/toxcore/ping.m new file mode 100644 index 0000000..91a780b --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/ping.m @@ -0,0 +1,386 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + * Copyright © 2013 plutooo + */ + +/** + * Buffered pinging using cyclic arrays. + */ +#include "ping.h" + +#include +#include + +#include "DHT.h" +#include "ccompat.h" +#include "mono_time.h" +#include "network.h" +#include "ping_array.h" +#include "util.h" + +#define PING_NUM_MAX 512 + +/** Maximum newly announced nodes to ping per TIME_TO_PING seconds. */ +#define MAX_TO_PING 32 + +/** Ping newly announced nodes to ping per TIME_TO_PING seconds*/ +#define TIME_TO_PING 2 + + +struct Ping { + const Mono_Time *mono_time; + const Random *rng; + DHT *dht; + + Ping_Array *ping_array; + Node_format to_ping[MAX_TO_PING]; + uint64_t last_to_ping; +}; + + +#define PING_PLAIN_SIZE (1 + sizeof(uint64_t)) +#define DHT_PING_SIZE (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + PING_PLAIN_SIZE + CRYPTO_MAC_SIZE) +#define PING_DATA_SIZE (CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port)) + +void ping_send_request(Ping *ping, const IP_Port *ipp, const uint8_t *public_key) +{ + uint8_t pk[DHT_PING_SIZE]; + int rc; + uint64_t ping_id; + + if (pk_equal(public_key, dht_get_self_public_key(ping->dht))) { + return; + } + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + + // generate key to encrypt ping_id with recipient privkey + dht_get_shared_key_sent(ping->dht, shared_key, public_key); + // Generate random ping_id. + uint8_t data[PING_DATA_SIZE]; + pk_copy(data, public_key); + memcpy(data + CRYPTO_PUBLIC_KEY_SIZE, ipp, sizeof(IP_Port)); + ping_id = ping_array_add(ping->ping_array, ping->mono_time, ping->rng, data, sizeof(data)); + + if (ping_id == 0) { + crypto_memzero(shared_key, sizeof(shared_key)); + return; + } + + uint8_t ping_plain[PING_PLAIN_SIZE]; + ping_plain[0] = NET_PACKET_PING_REQUEST; + memcpy(ping_plain + 1, &ping_id, sizeof(ping_id)); + + pk[0] = NET_PACKET_PING_REQUEST; + pk_copy(pk + 1, dht_get_self_public_key(ping->dht)); // Our pubkey + random_nonce(ping->rng, pk + 1 + CRYPTO_PUBLIC_KEY_SIZE); // Generate new nonce + + + rc = encrypt_data_symmetric(shared_key, + pk + 1 + CRYPTO_PUBLIC_KEY_SIZE, + ping_plain, sizeof(ping_plain), + pk + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); + + crypto_memzero(shared_key, sizeof(shared_key)); + + if (rc != PING_PLAIN_SIZE + CRYPTO_MAC_SIZE) { + return; + } + + // We never check this return value and failures in sendpacket are already logged + sendpacket(dht_get_net(ping->dht), ipp, pk, sizeof(pk)); +} + +non_null() +static int ping_send_response(const Ping *ping, const IP_Port *ipp, const uint8_t *public_key, + uint64_t ping_id, const uint8_t *shared_encryption_key) +{ + uint8_t pk[DHT_PING_SIZE]; + + if (pk_equal(public_key, dht_get_self_public_key(ping->dht))) { + return 1; + } + + uint8_t ping_plain[PING_PLAIN_SIZE]; + ping_plain[0] = NET_PACKET_PING_RESPONSE; + memcpy(ping_plain + 1, &ping_id, sizeof(ping_id)); + + pk[0] = NET_PACKET_PING_RESPONSE; + pk_copy(pk + 1, dht_get_self_public_key(ping->dht)); // Our pubkey + random_nonce(ping->rng, pk + 1 + CRYPTO_PUBLIC_KEY_SIZE); // Generate new nonce + + // Encrypt ping_id using recipient privkey + const int rc = encrypt_data_symmetric(shared_encryption_key, + pk + 1 + CRYPTO_PUBLIC_KEY_SIZE, + ping_plain, sizeof(ping_plain), + pk + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); + + if (rc != PING_PLAIN_SIZE + CRYPTO_MAC_SIZE) { + return 1; + } + + return sendpacket(dht_get_net(ping->dht), ipp, pk, sizeof(pk)); +} + +non_null() +static int handle_ping_request(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + DHT *dht = (DHT *)object; + + if (length != DHT_PING_SIZE) { + return 1; + } + + Ping *ping = dht_get_ping(dht); + + if (pk_equal(packet + 1, dht_get_self_public_key(ping->dht))) { + return 1; + } + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t ping_plain[PING_PLAIN_SIZE]; + + // Decrypt ping_id + dht_get_shared_key_recv(dht, shared_key, packet + 1); + const int rc = decrypt_data_symmetric(shared_key, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + PING_PLAIN_SIZE + CRYPTO_MAC_SIZE, + ping_plain); + + if (rc != sizeof(ping_plain)) { + crypto_memzero(shared_key, sizeof(shared_key)); + return 1; + } + + if (ping_plain[0] != NET_PACKET_PING_REQUEST) { + crypto_memzero(shared_key, sizeof(shared_key)); + return 1; + } + + uint64_t ping_id; + memcpy(&ping_id, ping_plain + 1, sizeof(ping_id)); + // Send response + ping_send_response(ping, source, packet + 1, ping_id, shared_key); + ping_add(ping, packet + 1, source); + + crypto_memzero(shared_key, sizeof(shared_key)); + + return 0; +} + +non_null() +static int handle_ping_response(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + DHT *dht = (DHT *)object; + int rc; + + if (length != DHT_PING_SIZE) { + return 1; + } + + Ping *ping = dht_get_ping(dht); + + if (pk_equal(packet + 1, dht_get_self_public_key(ping->dht))) { + return 1; + } + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + + // generate key to encrypt ping_id with recipient privkey + dht_get_shared_key_sent(ping->dht, shared_key, packet + 1); + + uint8_t ping_plain[PING_PLAIN_SIZE]; + // Decrypt ping_id + rc = decrypt_data_symmetric(shared_key, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + PING_PLAIN_SIZE + CRYPTO_MAC_SIZE, + ping_plain); + + crypto_memzero(shared_key, sizeof(shared_key)); + + if (rc != sizeof(ping_plain)) { + return 1; + } + + if (ping_plain[0] != NET_PACKET_PING_RESPONSE) { + return 1; + } + + uint64_t ping_id; + memcpy(&ping_id, ping_plain + 1, sizeof(ping_id)); + uint8_t data[PING_DATA_SIZE]; + + if (ping_array_check(ping->ping_array, ping->mono_time, data, sizeof(data), ping_id) != sizeof(data)) { + return 1; + } + + if (!pk_equal(packet + 1, data)) { + return 1; + } + + IP_Port ipp; + memcpy(&ipp, data + CRYPTO_PUBLIC_KEY_SIZE, sizeof(IP_Port)); + + if (!ipport_equal(&ipp, source)) { + return 1; + } + + addto_lists(dht, source, packet + 1); + return 0; +} + +/** @brief Check if public_key with ip_port is in the list. + * + * return true if it is. + * return false if it isn't. + */ +non_null() +static bool in_list(const Client_data *list, uint16_t length, const Mono_Time *mono_time, const uint8_t *public_key, + const IP_Port *ip_port) +{ + for (unsigned int i = 0; i < length; ++i) { + if (pk_equal(list[i].public_key, public_key)) { + const IPPTsPng *ipptp; + + if (net_family_is_ipv4(ip_port->ip.family)) { + ipptp = &list[i].assoc4; + } else { + ipptp = &list[i].assoc6; + } + + if (!mono_time_is_timeout(mono_time, ipptp->timestamp, BAD_NODE_TIMEOUT) + && ipport_equal(&ipptp->ip_port, ip_port)) { + return true; + } + } + } + + return false; +} + +/** @brief Add nodes to the to_ping list. + * All nodes in this list are pinged every TIME_TO_PING seconds + * and are then removed from the list. + * If the list is full the nodes farthest from our public_key are replaced. + * The purpose of this list is to enable quick integration of new nodes into the + * network while preventing amplification attacks. + * + * @retval 0 if node was added. + * @retval -1 if node was not added. + */ +int32_t ping_add(Ping *ping, const uint8_t *public_key, const IP_Port *ip_port) +{ + if (!ip_isset(&ip_port->ip)) { + return -1; + } + + if (!node_addable_to_close_list(ping->dht, public_key, ip_port)) { + return -1; + } + + if (in_list(dht_get_close_clientlist(ping->dht), LCLIENT_LIST, ping->mono_time, public_key, ip_port)) { + return -1; + } + + IP_Port temp; + + if (dht_getfriendip(ping->dht, public_key, &temp) == 0) { + ping_send_request(ping, ip_port, public_key); + return -1; + } + + for (unsigned int i = 0; i < MAX_TO_PING; ++i) { + if (!ip_isset(&ping->to_ping[i].ip_port.ip)) { + memcpy(ping->to_ping[i].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + ipport_copy(&ping->to_ping[i].ip_port, ip_port); + return 0; + } + + if (pk_equal(ping->to_ping[i].public_key, public_key)) { + return -1; + } + } + + if (add_to_list(ping->to_ping, MAX_TO_PING, public_key, ip_port, dht_get_self_public_key(ping->dht))) { + return 0; + } + + return -1; +} + + +/** @brief Ping all the valid nodes in the to_ping list every TIME_TO_PING seconds. + * This function must be run at least once every TIME_TO_PING seconds. + */ +void ping_iterate(Ping *ping) +{ + if (!mono_time_is_timeout(ping->mono_time, ping->last_to_ping, TIME_TO_PING)) { + return; + } + + if (!ip_isset(&ping->to_ping[0].ip_port.ip)) { + return; + } + + unsigned int i; + + for (i = 0; i < MAX_TO_PING; ++i) { + if (!ip_isset(&ping->to_ping[i].ip_port.ip)) { + break; + } + + if (!node_addable_to_close_list(ping->dht, ping->to_ping[i].public_key, &ping->to_ping[i].ip_port)) { + continue; + } + + ping_send_request(ping, &ping->to_ping[i].ip_port, ping->to_ping[i].public_key); + ip_reset(&ping->to_ping[i].ip_port.ip); + } + + if (i != 0) { + ping->last_to_ping = mono_time_get(ping->mono_time); + } +} + + +Ping *ping_new(const Mono_Time *mono_time, const Random *rng, DHT *dht) +{ + Ping *ping = (Ping *)calloc(1, sizeof(Ping)); + + if (ping == nullptr) { + return nullptr; + } + + ping->ping_array = ping_array_new(PING_NUM_MAX, PING_TIMEOUT); + + if (ping->ping_array == nullptr) { + free(ping); + return nullptr; + } + + ping->mono_time = mono_time; + ping->rng = rng; + ping->dht = dht; + networking_registerhandler(dht_get_net(ping->dht), NET_PACKET_PING_REQUEST, &handle_ping_request, dht); + networking_registerhandler(dht_get_net(ping->dht), NET_PACKET_PING_RESPONSE, &handle_ping_response, dht); + + return ping; +} + +void ping_kill(Ping *ping) +{ + if (ping == nullptr) { + return; + } + + networking_registerhandler(dht_get_net(ping->dht), NET_PACKET_PING_REQUEST, nullptr, nullptr); + networking_registerhandler(dht_get_net(ping->dht), NET_PACKET_PING_RESPONSE, nullptr, nullptr); + ping_array_kill(ping->ping_array); + + free(ping); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/ping_array.h b/local_pod_repo/toxcore/toxcore/toxcore/ping_array.h new file mode 100644 index 0000000..fda84cb --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/ping_array.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** @file + * @brief Implementation of an efficient array to store that we pinged something. + */ +#ifndef C_TOXCORE_TOXCORE_PING_ARRAY_H +#define C_TOXCORE_TOXCORE_PING_ARRAY_H + +#include +#include + +#include "crypto_core.h" +#include "mono_time.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Ping_Array Ping_Array; + +/** + * @brief Initialize a Ping_Array. + * + * @param size represents the total size of the array and should be a power of 2. + * @param timeout represents the maximum timeout in seconds for the entry. + * + * @return pointer to allocated Ping_Array on success, nullptr on failure. + */ +struct Ping_Array *ping_array_new(uint32_t size, uint32_t timeout); + +/** + * @brief Free all the allocated memory in a @ref Ping_Array. + */ +nullable(1) +void ping_array_kill(Ping_Array *array); + +/** + * @brief Add a data with length to the @ref Ping_Array list and return a ping_id. + * + * @return ping_id on success, 0 on failure. + */ +non_null() +uint64_t ping_array_add(Ping_Array *array, const Mono_Time *mono_time, const Random *rng, + const uint8_t *data, uint32_t length); + +/** + * @brief Check if @p ping_id is valid and not timed out. + * + * On success, copies the data into data of length, + * + * @return length of data copied on success, -1 on failure. + */ +non_null() +int32_t ping_array_check(Ping_Array *array, const Mono_Time *mono_time, uint8_t *data, size_t length, + uint64_t ping_id); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // C_TOXCORE_TOXCORE_PING_ARRAY_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/ping_array.m b/local_pod_repo/toxcore/toxcore/toxcore/ping_array.m new file mode 100644 index 0000000..e22a2e6 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/ping_array.m @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * Implementation of an efficient array to store that we pinged something. + */ +#include "ping_array.h" + +#include +#include + +#include "ccompat.h" +#include "crypto_core.h" +#include "mono_time.h" +#include "util.h" + +typedef struct Ping_Array_Entry { + uint8_t *data; + uint32_t length; + uint64_t ping_time; + uint64_t ping_id; +} Ping_Array_Entry; + +struct Ping_Array { + Ping_Array_Entry *entries; + + uint32_t last_deleted; /* number representing the next entry to be deleted. */ + uint32_t last_added; /* number representing the last entry to be added. */ + uint32_t total_size; /* The length of entries */ + uint32_t timeout; /* The timeout after which entries are cleared. */ +}; + +Ping_Array *ping_array_new(uint32_t size, uint32_t timeout) +{ + if (size == 0 || timeout == 0) { + return nullptr; + } + + if ((size & (size - 1)) != 0) { + // Not a power of 2. + return nullptr; + } + + Ping_Array *const empty_array = (Ping_Array *)calloc(1, sizeof(Ping_Array)); + + if (empty_array == nullptr) { + return nullptr; + } + + empty_array->entries = (Ping_Array_Entry *)calloc(size, sizeof(Ping_Array_Entry)); + + if (empty_array->entries == nullptr) { + free(empty_array); + return nullptr; + } + + empty_array->last_deleted = 0; + empty_array->last_added = 0; + empty_array->total_size = size; + empty_array->timeout = timeout; + return empty_array; +} + +non_null() +static void clear_entry(Ping_Array *array, uint32_t index) +{ + const Ping_Array_Entry empty = {nullptr}; + free(array->entries[index].data); + array->entries[index] = empty; +} + +void ping_array_kill(Ping_Array *array) +{ + if (array == nullptr) { + return; + } + + while (array->last_deleted != array->last_added) { + const uint32_t index = array->last_deleted % array->total_size; + clear_entry(array, index); + ++array->last_deleted; + } + + free(array->entries); + free(array); +} + +/** Clear timed out entries. */ +non_null() +static void ping_array_clear_timedout(Ping_Array *array, const Mono_Time *mono_time) +{ + while (array->last_deleted != array->last_added) { + const uint32_t index = array->last_deleted % array->total_size; + + if (!mono_time_is_timeout(mono_time, array->entries[index].ping_time, array->timeout)) { + break; + } + + clear_entry(array, index); + ++array->last_deleted; + } +} + +uint64_t ping_array_add(Ping_Array *array, const Mono_Time *mono_time, const Random *rng, + const uint8_t *data, uint32_t length) +{ + ping_array_clear_timedout(array, mono_time); + const uint32_t index = array->last_added % array->total_size; + + if (array->entries[index].data != nullptr) { + array->last_deleted = array->last_added - array->total_size; + clear_entry(array, index); + } + + array->entries[index].data = (uint8_t *)malloc(length); + + if (array->entries[index].data == nullptr) { + return 0; + } + + memcpy(array->entries[index].data, data, length); + array->entries[index].length = length; + array->entries[index].ping_time = mono_time_get(mono_time); + ++array->last_added; + uint64_t ping_id = random_u64(rng); + ping_id /= array->total_size; + ping_id *= array->total_size; + ping_id += index; + + if (ping_id == 0) { + ping_id += array->total_size; + } + + array->entries[index].ping_id = ping_id; + return ping_id; +} + +int32_t ping_array_check(Ping_Array *array, const Mono_Time *mono_time, uint8_t *data, + size_t length, uint64_t ping_id) +{ + if (ping_id == 0) { + return -1; + } + + const uint32_t index = ping_id % array->total_size; + + if (array->entries[index].ping_id != ping_id) { + return -1; + } + + if (mono_time_is_timeout(mono_time, array->entries[index].ping_time, array->timeout)) { + return -1; + } + + if (array->entries[index].length > length) { + return -1; + } + + // TODO(iphydf): This can't happen? If it indeed can't, turn it into an assert. + if (array->entries[index].data == nullptr) { + return -1; + } + + memcpy(data, array->entries[index].data, array->entries[index].length); + const uint32_t len = array->entries[index].length; + clear_entry(array, index); + return len; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/state.h b/local_pod_repo/toxcore/toxcore/toxcore/state.h new file mode 100644 index 0000000..716286d --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/state.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2020 The TokTok team. + * Copyright © 2014 Tox project. + */ + +/** + * The state module is responsible for parsing the Tox save data format and for + * saving state in that format. + * + * This module provides functions for iterating over serialised data sections + * and reading/writing numbers in the correct format (little endian). + * + * Note that unlike the Tox network protocol, the save data stores its values in + * little endian, which is native to most desktop and server architectures in + * 2018. + */ +#ifndef C_TOXCORE_TOXCORE_STATE_H +#define C_TOXCORE_TOXCORE_STATE_H + +#include "logger.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define STATE_COOKIE_GLOBAL 0x15ed1b1f + +#define STATE_COOKIE_TYPE 0x01ce + +typedef enum State_Type { + STATE_TYPE_NOSPAMKEYS = 1, + STATE_TYPE_DHT = 2, + STATE_TYPE_FRIENDS = 3, + STATE_TYPE_NAME = 4, + STATE_TYPE_STATUSMESSAGE = 5, + STATE_TYPE_STATUS = 6, + STATE_TYPE_TCP_RELAY = 10, + STATE_TYPE_PATH_NODE = 11, + STATE_TYPE_CONFERENCES = 20, + STATE_TYPE_END = 255, +} State_Type; + +// Returned by the state_load_cb to instruct the loader on what to do next. +typedef enum State_Load_Status { + // Continue loading state data sections. + STATE_LOAD_STATUS_CONTINUE, + // An error occurred. Stop loading sections. + STATE_LOAD_STATUS_ERROR, + // We're at the end of the save data, terminate loading successfully. + STATE_LOAD_STATUS_END, +} State_Load_Status; + +typedef State_Load_Status state_load_cb(void *outer, const uint8_t *data, uint32_t len, uint16_t type); + +/** state load/save */ +non_null() +int state_load(const Logger *log, state_load_cb *state_load_callback, void *outer, + const uint8_t *data, uint32_t length, uint16_t cookie_inner); + +non_null() +uint8_t *state_write_section_header(uint8_t *data, uint16_t cookie_type, uint32_t len, uint32_t section_type); + +// Utilities for state data serialisation. + +uint16_t lendian_to_host16(uint16_t lendian); +uint16_t host_to_lendian16(uint16_t host); + +non_null() +void host_to_lendian_bytes64(uint8_t *dest, uint64_t num); +non_null() +void lendian_bytes_to_host64(uint64_t *dest, const uint8_t *lendian); + +non_null() +void host_to_lendian_bytes32(uint8_t *dest, uint32_t num); +non_null() +void lendian_bytes_to_host32(uint32_t *dest, const uint8_t *lendian); + +non_null() +void host_to_lendian_bytes16(uint8_t *dest, uint16_t num); +non_null() +void lendian_bytes_to_host16(uint16_t *dest, const uint8_t *lendian); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // C_TOXCORE_TOXCORE_STATE_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/state.m b/local_pod_repo/toxcore/toxcore/toxcore/state.m new file mode 100644 index 0000000..701cf44 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/state.m @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2020 The TokTok team. + * Copyright © 2014 Tox project. + */ +#include "state.h" + +#include + +#include "ccompat.h" + +/** state load/save */ +int state_load(const Logger *log, state_load_cb *state_load_callback, void *outer, + const uint8_t *data, uint32_t length, uint16_t cookie_inner) +{ + if (state_load_callback == nullptr || data == nullptr) { + LOGGER_ERROR(log, "state_load() called with invalid args."); + return -1; + } + + + const uint32_t size_head = sizeof(uint32_t) * 2; + + while (length >= size_head) { + uint32_t length_sub; + lendian_bytes_to_host32(&length_sub, data); + + uint32_t cookie_type; + lendian_bytes_to_host32(&cookie_type, data + sizeof(uint32_t)); + + data += size_head; + length -= size_head; + + if (length < length_sub) { + /* file truncated */ + LOGGER_ERROR(log, "state file too short: %u < %u", length, length_sub); + return -1; + } + + if (lendian_to_host16(cookie_type >> 16) != cookie_inner) { + /* something is not matching up in a bad way, give up */ + LOGGER_ERROR(log, "state file garbled: %04x != %04x", cookie_type >> 16, cookie_inner); + return -1; + } + + const uint16_t type = lendian_to_host16(cookie_type & 0xFFFF); + + switch (state_load_callback(outer, data, length_sub, type)) { + case STATE_LOAD_STATUS_CONTINUE: { + data += length_sub; + length -= length_sub; + break; + } + + case STATE_LOAD_STATUS_ERROR: { + LOGGER_ERROR(log, "Error occcured in state file (type: %u).", type); + return -1; + } + + case STATE_LOAD_STATUS_END: { + return 0; + } + } + } + + if (length != 0) { + LOGGER_ERROR(log, "unparsed data in state file of length %u", length); + return -1; + } + + return 0; +} + +uint8_t *state_write_section_header(uint8_t *data, uint16_t cookie_type, uint32_t len, uint32_t section_type) +{ + host_to_lendian_bytes32(data, len); + data += sizeof(uint32_t); + host_to_lendian_bytes32(data, (host_to_lendian16(cookie_type) << 16) | host_to_lendian16(section_type)); + data += sizeof(uint32_t); + return data; +} + +uint16_t lendian_to_host16(uint16_t lendian) +{ +#ifdef WORDS_BIGENDIAN + return (lendian << 8) | (lendian >> 8); +#else + return lendian; +#endif +} + +uint16_t host_to_lendian16(uint16_t host) +{ + return lendian_to_host16(host); +} + +void host_to_lendian_bytes64(uint8_t *dest, uint64_t num) +{ +#ifdef WORDS_BIGENDIAN + num = ((num << 8) & 0xFF00FF00FF00FF00) | ((num >> 8) & 0xFF00FF00FF00FF); + num = ((num << 16) & 0xFFFF0000FFFF0000) | ((num >> 16) & 0xFFFF0000FFFF); + num = (num << 32) | (num >> 32); +#endif + memcpy(dest, &num, sizeof(uint64_t)); +} + +void lendian_bytes_to_host64(uint64_t *dest, const uint8_t *lendian) +{ + uint64_t d; + memcpy(&d, lendian, sizeof(uint64_t)); +#ifdef WORDS_BIGENDIAN + d = ((d << 8) & 0xFF00FF00FF00FF00) | ((d >> 8) & 0xFF00FF00FF00FF); + d = ((d << 16) & 0xFFFF0000FFFF0000) | ((d >> 16) & 0xFFFF0000FFFF); + d = (d << 32) | (d >> 32); +#endif + *dest = d; +} + +void host_to_lendian_bytes32(uint8_t *dest, uint32_t num) +{ +#ifdef WORDS_BIGENDIAN + num = ((num << 8) & 0xFF00FF00) | ((num >> 8) & 0xFF00FF); + num = (num << 16) | (num >> 16); +#endif + memcpy(dest, &num, sizeof(uint32_t)); +} + +void lendian_bytes_to_host32(uint32_t *dest, const uint8_t *lendian) +{ + uint32_t d; + memcpy(&d, lendian, sizeof(uint32_t)); +#ifdef WORDS_BIGENDIAN + d = ((d << 8) & 0xFF00FF00) | ((d >> 8) & 0xFF00FF); + d = (d << 16) | (d >> 16); +#endif + *dest = d; +} + +void host_to_lendian_bytes16(uint8_t *dest, uint16_t num) +{ +#ifdef WORDS_BIGENDIAN + num = (num << 8) | (num >> 8); +#endif + memcpy(dest, &num, sizeof(uint16_t)); +} + +void lendian_bytes_to_host16(uint16_t *dest, const uint8_t *lendian) +{ + uint16_t d; + memcpy(&d, lendian, sizeof(uint16_t)); +#ifdef WORDS_BIGENDIAN + d = (d << 8) | (d >> 8); +#endif + *dest = d; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/timed_auth.h b/local_pod_repo/toxcore/toxcore/toxcore/timed_auth.h new file mode 100644 index 0000000..691b04d --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/timed_auth.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2019-2021 The TokTok team. + */ +#ifndef C_TOXCORE_TOXCORE_TIMED_AUTH_H +#define C_TOXCORE_TOXCORE_TIMED_AUTH_H + +#include "crypto_core.h" +#include "mono_time.h" + +#define TIMED_AUTH_SIZE CRYPTO_HMAC_SIZE + +/** + * @brief Write timed authentication code of data to timed_auth. + * + * @param timed_auth Must be of size TIMED_AUTH_SIZE. + */ + +non_null(1, 3, 6) nullable(4) +void generate_timed_auth(const Mono_Time *mono_time, uint16_t timeout, const uint8_t *key, + const uint8_t *data, uint16_t length, uint8_t *timed_auth); + +/** + * @brief Check timed_auth. This succeeds if `timed_auth` was generated by + * `generate_timed_auth` at most `timeout` seconds ago, and fails if at least + * `2*timeout` seconds ago. More precisely, it succeeds iff + * `current_time / timeout` is equal to or one more than + * `creation_time / timeout`. + * + * @param timed_auth Must be of size TIMED_AUTH_SIZE. + * @return true on success, false otherwise. + */ +non_null(1, 3, 6) nullable(4) +bool check_timed_auth(const Mono_Time *mono_time, uint16_t timeout, const uint8_t *key, + const uint8_t *data, uint16_t length, const uint8_t *timed_auth); +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxcore/timed_auth.m b/local_pod_repo/toxcore/toxcore/toxcore/timed_auth.m new file mode 100644 index 0000000..ebd5100 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/timed_auth.m @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2019-2021 The TokTok team. + */ +#include "timed_auth.h" + +#include + +#include "ccompat.h" + +non_null(1,6) nullable(4) +static void create_timed_auth_to_hash(const Mono_Time *mono_time, uint16_t timeout, bool previous, const uint8_t *data, + uint16_t length, uint8_t *to_hash) +{ + const uint64_t t = (mono_time_get(mono_time) / timeout) - (previous ? 1 : 0); + memcpy(to_hash, &t, sizeof(t)); + + if (data != nullptr) { + memcpy(to_hash + sizeof(t), data, length); + } +} + +void generate_timed_auth(const Mono_Time *mono_time, uint16_t timeout, const uint8_t *key, + const uint8_t *data, uint16_t length, uint8_t *timed_auth) +{ + VLA(uint8_t, to_hash, sizeof(uint64_t) + length); + create_timed_auth_to_hash(mono_time, timeout, false, data, length, to_hash); + crypto_hmac(timed_auth, key, to_hash, SIZEOF_VLA(to_hash)); +} + +bool check_timed_auth(const Mono_Time *mono_time, uint16_t timeout, const uint8_t *key, const uint8_t *data, + uint16_t length, const uint8_t *timed_auth) +{ + VLA(uint8_t, to_hash, sizeof(uint64_t) + length); + + for (uint8_t i = 0; i < 2; ++i) { + create_timed_auth_to_hash(mono_time, timeout, i != 0, data, length, to_hash); + + if (crypto_hmac_verify(timed_auth, key, to_hash, SIZEOF_VLA(to_hash))) { + return true; + } + } + + return false; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/tox.h b/local_pod_repo/toxcore/toxcore/toxcore/tox.h new file mode 100644 index 0000000..4b78454 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/tox.h @@ -0,0 +1,3349 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** @file + * @brief Public core API for Tox clients. + * + * Every function that can fail takes a function-specific error code pointer + * that can be used to diagnose problems with the Tox state or the function + * arguments. The error code pointer can be NULL, which does not influence the + * function's behaviour, but can be done if the reason for failure is irrelevant + * to the client. + * + * The exception to this rule are simple allocation functions whose only failure + * mode is allocation failure. They return NULL in that case, and do not set an + * error code. + * + * Every error code type has an OK value to which functions will set their error + * code value on success. Clients can keep their error code uninitialised before + * passing it to a function. The library guarantees that after returning, the + * value pointed to by the error code pointer has been initialised. + * + * Functions with pointer parameters often have a NULL error code, meaning they + * could not perform any operation, because one of the required parameters was + * NULL. Some functions operate correctly or are defined as effectless on NULL. + * + * Some functions additionally return a value outside their + * return type domain, or a bool containing true on success and false on + * failure. + * + * All functions that take a Tox instance pointer will cause undefined behaviour + * when passed a NULL Tox pointer. + * + * All integer values are expected in host byte order. + * + * Functions with parameters with enum types cause unspecified behaviour if the + * enumeration value is outside the valid range of the type. If possible, the + * function will try to use a sane default, but there will be no error code, + * and one possible action for the function to take is to have no effect. + * + * Integer constants and the memory layout of publicly exposed structs are not + * part of the ABI. + * + * @section events Events and callbacks + * + * Events are handled by callbacks. One callback can be registered per event. + * All events have a callback function type named `tox_{event}_cb` and a + * function to register it named `tox_callback_{event}`. Passing a NULL + * callback will result in no callback being registered for that event. Only + * one callback per event can be registered, so if a client needs multiple + * event listeners, it needs to implement the dispatch functionality itself. + * + * The last argument to a callback is the user data pointer. It is passed from + * tox_iterate to each callback in sequence. + * + * The user data pointer is never stored or dereferenced by any library code, so + * can be any pointer, including NULL. Callbacks must all operate on the same + * object type. In the apidsl code (tox.in.h), this is denoted with `any`. The + * `any` in tox_iterate must be the same `any` as in all callbacks. In C, + * lacking parametric polymorphism, this is a pointer to void. + * + * Old style callbacks that are registered together with a user data pointer + * receive that pointer as argument when they are called. They can each have + * their own user data pointer of their own type. + * + * @section threading Threading implications + * + * It is possible to run multiple concurrent threads with a Tox instance for + * each thread. It is also possible to run all Tox instances in the same thread. + * A common way to run Tox (multiple or single instance) is to have one thread + * running a simple tox_iterate loop, sleeping for tox_iteration_interval + * milliseconds on each iteration. + * + * If you want to access a single Tox instance from multiple threads, access + * to the instance must be synchronised. While multiple threads can concurrently + * access multiple different Tox instances, no more than one API function can + * operate on a single instance at any given time. + * + * Functions that write to variable length byte arrays will always have a size + * function associated with them. The result of this size function is only valid + * until another mutating function (one that takes a pointer to non-const Tox) + * is called. Thus, clients must ensure that no other thread calls a mutating + * function between the call to the size function and the call to the retrieval + * function. + * + * E.g. to get the current nickname, one would write + * + * @code + * size_t length = tox_self_get_name_size(tox); + * uint8_t *name = malloc(length); + * if (!name) abort(); + * tox_self_get_name(tox, name); + * @endcode + * + * If any other thread calls tox_self_set_name while this thread is allocating + * memory, the length may have become invalid, and the call to + * tox_self_get_name may cause undefined behaviour. + */ +#ifndef C_TOXCORE_TOXCORE_TOX_H +#define C_TOXCORE_TOXCORE_TOX_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @{ @namespace tox */ + +#ifndef TOX_DEFINED +#define TOX_DEFINED +/** + * @brief The Tox instance type. + * + * All the state associated with a connection is held + * within the instance. Multiple instances can exist and operate concurrently. + * The maximum number of Tox instances that can exist on a single network + * device is limited. Note that this is not just a per-process limit, since the + * limiting factor is the number of usable ports on a device. + */ +typedef struct Tox Tox; +#endif /* TOX_DEFINED */ + + +/** @{ + * @name API version + */ + +/** + * @brief The major version number. + * + * Incremented when the API or ABI changes in an incompatible way. + * + * The function variants of these constants return the version number of the + * library. They can be used to display the Tox library version or to check + * whether the client is compatible with the dynamically linked version of Tox. + */ +#define TOX_VERSION_MAJOR 0 + +uint32_t tox_version_major(void); + +/** + * @brief The minor version number. + * + * Incremented when functionality is added without breaking the API or ABI. + * Set to 0 when the major version number is incremented. + */ +#define TOX_VERSION_MINOR 2 + +uint32_t tox_version_minor(void); + +/** + * @brief The patch or revision number. + * + * Incremented when bugfixes are applied without changing any functionality or + * API or ABI. + */ +#define TOX_VERSION_PATCH 18 + +uint32_t tox_version_patch(void); + +//!TOKSTYLE- +/** + * @brief A macro to check at preprocessing time whether the client code is + * compatible with the installed version of Tox. + * + * Leading zeros in the version number are ignored. E.g. 0.1.5 is to 0.1.4 + * what 1.5 is to 1.4, that is: it can add new features, but can't break the + * API. + */ +#define TOX_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \ + ((TOX_VERSION_MAJOR > 0 && TOX_VERSION_MAJOR == MAJOR) && ( \ + /* 1.x.x, 2.x.x, etc. with matching major version. */ \ + TOX_VERSION_MINOR > MINOR || \ + (TOX_VERSION_MINOR == MINOR && TOX_VERSION_PATCH >= PATCH) \ + )) || ((TOX_VERSION_MAJOR == 0 && MAJOR == 0) && ( \ + /* 0.x.x makes minor behave like major above. */ \ + ((TOX_VERSION_MINOR > 0 && TOX_VERSION_MINOR == MINOR) && ( \ + TOX_VERSION_PATCH >= PATCH \ + )) || ((TOX_VERSION_MINOR == 0 && MINOR == 0) && ( \ + /* 0.0.x and 0.0.y are only compatible if x == y. */ \ + TOX_VERSION_PATCH == PATCH \ + )) \ + )) +//!TOKSTYLE+ + +/** + * @brief Return whether the compiled library version is compatible with the + * passed version numbers. + */ +bool tox_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch); + +/** + * @brief A convenience macro to call tox_version_is_compatible with the + * currently compiling API version. + */ +#define TOX_VERSION_IS_ABI_COMPATIBLE() \ + tox_version_is_compatible(TOX_VERSION_MAJOR, TOX_VERSION_MINOR, TOX_VERSION_PATCH) + +/** @} */ + + +/** @{ + * @name Numeric constants + * + * The values of these are not part of the ABI. Prefer to use the function + * versions of them for code that should remain compatible with future versions + * of toxcore. + */ + +/** + * @brief The size of a Tox Public Key in bytes. + */ +#define TOX_PUBLIC_KEY_SIZE 32 + +uint32_t tox_public_key_size(void); + +/** + * @brief The size of a Tox Secret Key in bytes. + */ +#define TOX_SECRET_KEY_SIZE 32 + +uint32_t tox_secret_key_size(void); + +/** + * @brief The size of a Tox Conference unique id in bytes. + * + * @deprecated Use TOX_CONFERENCE_ID_SIZE instead. + */ +#define TOX_CONFERENCE_UID_SIZE 32 + +uint32_t tox_conference_uid_size(void); + +/** + * @brief The size of a Tox Conference unique id in bytes. + */ +#define TOX_CONFERENCE_ID_SIZE 32 + +uint32_t tox_conference_id_size(void); + +/** + * @brief The size of the nospam in bytes when written in a Tox address. + */ +#define TOX_NOSPAM_SIZE (sizeof(uint32_t)) + +uint32_t tox_nospam_size(void); + +/** + * @brief The size of a Tox address in bytes. + * + * Tox addresses are in the format + * `[Public Key (TOX_PUBLIC_KEY_SIZE bytes)][nospam (4 bytes)][checksum (2 bytes)]`. + * + * The checksum is computed over the Public Key and the nospam value. The first + * byte is an XOR of all the even bytes (0, 2, 4, ...), the second byte is an + * XOR of all the odd bytes (1, 3, 5, ...) of the Public Key and nospam. + */ +#define TOX_ADDRESS_SIZE (TOX_PUBLIC_KEY_SIZE + TOX_NOSPAM_SIZE + sizeof(uint16_t)) + +uint32_t tox_address_size(void); + +/** + * @brief Maximum length of a nickname in bytes. + * + * @deprecated The macro will be removed in 0.3.0. Use the function instead. + */ +#define TOX_MAX_NAME_LENGTH 128 + +uint32_t tox_max_name_length(void); + +/** + * @brief Maximum length of a status message in bytes. + * + * @deprecated The macro will be removed in 0.3.0. Use the function instead. + */ +#define TOX_MAX_STATUS_MESSAGE_LENGTH 1007 + +uint32_t tox_max_status_message_length(void); + +/** + * @brief Maximum length of a friend request message in bytes. + * + * @deprecated The macro will be removed in 0.3.0. Use the function instead. + */ +#define TOX_MAX_FRIEND_REQUEST_LENGTH 1016 + +uint32_t tox_max_friend_request_length(void); + +/** + * @brief Maximum length of a single message after which it should be split. + * + * @deprecated The macro will be removed in 0.3.0. Use the function instead. + */ +#define TOX_MAX_MESSAGE_LENGTH 1372 + +uint32_t tox_max_message_length(void); + +#define TOX_MSGV3_MSGID_LENGTH 32 +#define TOX_MSGV3_TIMESTAMP_LENGTH 4 +#define TOX_MSGV3_GUARD 2 +#define TOX_MSGV3_MAX_MESSAGE_LENGTH (TOX_MAX_MESSAGE_LENGTH - TOX_MSGV3_MSGID_LENGTH - TOX_MSGV3_TIMESTAMP_LENGTH - TOX_MSGV3_GUARD) + +/** + * @brief Maximum size of custom packets. TODO(iphydf): should be LENGTH? + * + * @deprecated The macro will be removed in 0.3.0. Use the function instead. + */ +#define TOX_MAX_CUSTOM_PACKET_SIZE 1373 + +uint32_t tox_max_custom_packet_size(void); + +/** + * @brief The number of bytes in a hash generated by tox_hash. + */ +#define TOX_HASH_LENGTH 32 + +uint32_t tox_hash_length(void); + +/** + * @brief The number of bytes in a file id. + */ +#define TOX_FILE_ID_LENGTH 32 + +uint32_t tox_file_id_length(void); + +/** + * @brief Maximum file name length for file transfers. + * + * @deprecated The macro will be removed in 0.3.0. Use the function instead. + */ +#define TOX_MAX_FILENAME_LENGTH 255 + +uint32_t tox_max_filename_length(void); + +/** + * @brief Maximum length of a hostname, e.g. proxy or bootstrap node names. + * + * This length does not include the NUL byte. Hostnames are NUL-terminated C + * strings, so they are 255 characters plus one NUL byte. + * + * @deprecated The macro will be removed in 0.3.0. Use the function instead. + */ +#define TOX_MAX_HOSTNAME_LENGTH 255 + +uint32_t tox_max_hostname_length(void); + +/** @} */ + + +/** @{ + * @name Global enumerations + */ + +/** + * @brief Represents the possible statuses a client can have. + */ +typedef enum Tox_User_Status { + + /** + * User is online and available. + */ + TOX_USER_STATUS_NONE, + + /** + * User is away. Clients can set this e.g. after a user defined + * inactivity time. + */ + TOX_USER_STATUS_AWAY, + + /** + * User is busy. Signals to other clients that this client does not + * currently wish to communicate. + */ + TOX_USER_STATUS_BUSY, + +} Tox_User_Status; + + +/** + * @brief Represents message types for tox_friend_send_message and conference + * messages. + */ +typedef enum Tox_Message_Type { + + /** + * Normal text message. Similar to PRIVMSG on IRC. + */ + TOX_MESSAGE_TYPE_NORMAL = 0, + + /** + * A message describing an user action. This is similar to /me (CTCP ACTION) + * on IRC. + */ + TOX_MESSAGE_TYPE_ACTION = 1, + + /** + * A high level ACK for MSG ID (MSG V3 functionality) + */ + TOX_MESSAGE_TYPE_HIGH_LEVEL_ACK = 2, + +} Tox_Message_Type; + +/** @} */ + + +/** @{ + * @name Startup options + */ + +/** + * @brief Type of proxy used to connect to TCP relays. + */ +typedef enum Tox_Proxy_Type { + + /** + * Don't use a proxy. + */ + TOX_PROXY_TYPE_NONE, + + /** + * HTTP proxy using CONNECT. + */ + TOX_PROXY_TYPE_HTTP, + + /** + * SOCKS proxy for simple socket pipes. + */ + TOX_PROXY_TYPE_SOCKS5, + +} Tox_Proxy_Type; + + +/** + * @brief Type of savedata to create the Tox instance from. + */ +typedef enum Tox_Savedata_Type { + + /** + * No savedata. + */ + TOX_SAVEDATA_TYPE_NONE, + + /** + * Savedata is one that was obtained from tox_get_savedata. + */ + TOX_SAVEDATA_TYPE_TOX_SAVE, + + /** + * Savedata is a secret key of length TOX_SECRET_KEY_SIZE. + */ + TOX_SAVEDATA_TYPE_SECRET_KEY, + +} Tox_Savedata_Type; + + +/** + * @brief Severity level of log messages. + */ +typedef enum Tox_Log_Level { + + /** + * Very detailed traces including all network activity. + */ + TOX_LOG_LEVEL_TRACE, + + /** + * Debug messages such as which port we bind to. + */ + TOX_LOG_LEVEL_DEBUG, + + /** + * Informational log messages such as video call status changes. + */ + TOX_LOG_LEVEL_INFO, + + /** + * Warnings about events_alloc inconsistency or logic errors. + */ + TOX_LOG_LEVEL_WARNING, + + /** + * Severe unexpected errors caused by external or events_alloc inconsistency. + */ + TOX_LOG_LEVEL_ERROR, + +} Tox_Log_Level; + + +/** + * @brief This event is triggered when the toxcore library logs an events_alloc message. + * + * This is mostly useful for debugging. This callback can be called from any + * function, not just tox_iterate. This means the user data lifetime must at + * least extend between registering and unregistering it or tox_kill. + * + * Other toxcore modules such as toxav may concurrently call this callback at + * any time. Thus, user code must make sure it is equipped to handle concurrent + * execution, e.g. by employing appropriate mutex locking. + * + * @param level The severity of the log message. + * @param file The source file from which the message originated. + * @param line The source line from which the message originated. + * @param func The function from which the message originated. + * @param message The log message. + * @param user_data The user data pointer passed to tox_new in options. + */ +typedef void tox_log_cb(Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, const char *func, + const char *message, void *user_data); + + +/** + * @brief Operating system functions used by Tox. + * + * This struct is opaque and generally shouldn't be used in clients, but in + * combination with tox_private.h, it allows tests to inject non-IO (hermetic) + * versions of low level network, RNG, and time keeping functions. + */ +typedef struct Tox_System Tox_System; + + +/** + * @brief This struct contains all the startup options for Tox. + * + * You must tox_options_new to allocate an object of this type. + * + * WARNING: Although this struct happens to be visible in the API, it is + * effectively private. Do not allocate this yourself or access members + * directly, as it *will* break binary compatibility frequently. + * + * @deprecated The memory layout of this struct (size, alignment, and field + * order) is not part of the ABI. To remain compatible, prefer to use + * tox_options_new to allocate the object and accessor functions to set the + * members. The struct will become opaque (i.e. the definition will become + * private) in v0.3.0. + */ +struct Tox_Options { + + /** + * The type of socket to create. + * + * If this is set to false, an IPv4 socket is created, which subsequently + * only allows IPv4 communication. + * If it is set to true, an IPv6 socket is created, allowing both IPv4 and + * IPv6 communication. + */ + bool ipv6_enabled; + + + /** + * Enable the use of UDP communication when available. + * + * Setting this to false will force Tox to use TCP only. Communications will + * need to be relayed through a TCP relay node, potentially slowing them down. + * + * If a proxy is enabled, UDP will be disabled if either toxcore or the + * proxy don't support proxying UDP messages. + */ + bool udp_enabled; + + + /** + * Enable local network peer discovery. + * + * Disabling this will cause Tox to not look for peers on the local network. + */ + bool local_discovery_enabled; + + + /** + * Enable storing DHT announcements and forwarding corresponding requests. + * + * Disabling this will cause Tox to ignore the relevant packets. + */ + bool dht_announcements_enabled; + + /** + * Pass communications through a proxy. + */ + Tox_Proxy_Type proxy_type; + + + /** + * The IP address or DNS name of the proxy to be used. + * + * If used, this must be non-NULL and be a valid DNS name. The name must not + * exceed TOX_MAX_HOSTNAME_LENGTH characters, and be in a NUL-terminated C string + * format (TOX_MAX_HOSTNAME_LENGTH includes the NUL byte). + * + * This member is ignored (it can be NULL) if proxy_type is TOX_PROXY_TYPE_NONE. + * + * The data pointed at by this member is owned by the user, so must + * outlive the options object. + */ + const char *proxy_host; + + + /** + * The port to use to connect to the proxy server. + * + * Ports must be in the range (1, 65535). The value is ignored if + * proxy_type is TOX_PROXY_TYPE_NONE. + */ + uint16_t proxy_port; + + + /** + * The start port of the inclusive port range to attempt to use. + * + * If both start_port and end_port are 0, the default port range will be + * used: `[33445, 33545]`. + * + * If either start_port or end_port is 0 while the other is non-zero, the + * non-zero port will be the only port in the range. + * + * Having start_port > end_port will yield the same behavior as if start_port + * and end_port were swapped. + */ + uint16_t start_port; + + + /** + * The end port of the inclusive port range to attempt to use. + */ + uint16_t end_port; + + + /** + * The port to use for the TCP server (relay). If 0, the TCP server is + * disabled. + * + * Enabling it is not required for Tox to function properly. + * + * When enabled, your Tox instance can act as a TCP relay for other Tox + * instance. This leads to increased traffic, thus when writing a client + * it is recommended to enable TCP server only if the user has an option + * to disable it. + */ + uint16_t tcp_port; + + + /** + * Enables or disables UDP hole-punching in toxcore. (Default: enabled). + */ + bool hole_punching_enabled; + + + /** + * The type of savedata to load from. + */ + Tox_Savedata_Type savedata_type; + + + /** + * The savedata. + * + * The data pointed at by this member is owned by the user, so must + * outlive the options object. + */ + const uint8_t *savedata_data; + + + /** + * The length of the savedata. + */ + size_t savedata_length; + + + /** + * Logging callback for the new tox instance. + */ + tox_log_cb *log_callback; + + + /** + * User data pointer passed to the logging callback. + */ + void *log_user_data; + + + /** + * These options are experimental, so avoid writing code that depends on + * them. Options marked "experimental" may change their behaviour or go away + * entirely in the future, or may be renamed to something non-experimental + * if they become part of the supported API. + */ + /** + * Make public API functions thread-safe using a per-instance lock. + * + * Default: false. + */ + bool experimental_thread_safety; + + /** + * Low level operating system functionality such as send/recv and random + * number generation. + */ + const Tox_System *operating_system; + +}; + + +bool tox_options_get_ipv6_enabled(const struct Tox_Options *options); + +void tox_options_set_ipv6_enabled(struct Tox_Options *options, bool ipv6_enabled); + +bool tox_options_get_udp_enabled(const struct Tox_Options *options); + +void tox_options_set_udp_enabled(struct Tox_Options *options, bool udp_enabled); + +bool tox_options_get_local_discovery_enabled(const struct Tox_Options *options); + +void tox_options_set_local_discovery_enabled(struct Tox_Options *options, bool local_discovery_enabled); + +bool tox_options_get_dht_announcements_enabled(const struct Tox_Options *options); + +void tox_options_set_dht_announcements_enabled(struct Tox_Options *options, bool dht_announcements_enabled); + +Tox_Proxy_Type tox_options_get_proxy_type(const struct Tox_Options *options); + +void tox_options_set_proxy_type(struct Tox_Options *options, Tox_Proxy_Type type); + +const char *tox_options_get_proxy_host(const struct Tox_Options *options); + +void tox_options_set_proxy_host(struct Tox_Options *options, const char *host); + +uint16_t tox_options_get_proxy_port(const struct Tox_Options *options); + +void tox_options_set_proxy_port(struct Tox_Options *options, uint16_t port); + +uint16_t tox_options_get_start_port(const struct Tox_Options *options); + +void tox_options_set_start_port(struct Tox_Options *options, uint16_t start_port); + +uint16_t tox_options_get_end_port(const struct Tox_Options *options); + +void tox_options_set_end_port(struct Tox_Options *options, uint16_t end_port); + +uint16_t tox_options_get_tcp_port(const struct Tox_Options *options); + +void tox_options_set_tcp_port(struct Tox_Options *options, uint16_t tcp_port); + +bool tox_options_get_hole_punching_enabled(const struct Tox_Options *options); + +void tox_options_set_hole_punching_enabled(struct Tox_Options *options, bool hole_punching_enabled); + +Tox_Savedata_Type tox_options_get_savedata_type(const struct Tox_Options *options); + +void tox_options_set_savedata_type(struct Tox_Options *options, Tox_Savedata_Type type); + +const uint8_t *tox_options_get_savedata_data(const struct Tox_Options *options); + +void tox_options_set_savedata_data(struct Tox_Options *options, const uint8_t *data, size_t length); + +size_t tox_options_get_savedata_length(const struct Tox_Options *options); + +void tox_options_set_savedata_length(struct Tox_Options *options, size_t length); + +tox_log_cb *tox_options_get_log_callback(const struct Tox_Options *options); + +void tox_options_set_log_callback(struct Tox_Options *options, tox_log_cb *callback); + +void *tox_options_get_log_user_data(const struct Tox_Options *options); + +void tox_options_set_log_user_data(struct Tox_Options *options, void *user_data); + +bool tox_options_get_experimental_thread_safety(const struct Tox_Options *options); + +void tox_options_set_experimental_thread_safety(struct Tox_Options *options, bool experimental_thread_safety); + +const Tox_System *tox_options_get_operating_system(const struct Tox_Options *options); + +void tox_options_set_operating_system(struct Tox_Options *options, const Tox_System *operating_system); + +/** + * @brief Initialises a Tox_Options object with the default options. + * + * The result of this function is independent of the original options. All + * values will be overwritten, no values will be read (so it is permissible + * to pass an uninitialised object). + * + * If options is NULL, this function has no effect. + * + * @param options An options object to be filled with default options. + */ +void tox_options_default(struct Tox_Options *options); + +typedef enum Tox_Err_Options_New { + + /** + * The function returned successfully. + */ + TOX_ERR_OPTIONS_NEW_OK, + + /** + * The function failed to allocate enough memory for the options struct. + */ + TOX_ERR_OPTIONS_NEW_MALLOC, + +} Tox_Err_Options_New; + + +/** + * @brief Allocates a new Tox_Options object and initialises it with the default + * options. + * + * This function can be used to preserve long term ABI compatibility by + * giving the responsibility of allocation and deallocation to the Tox library. + * + * Objects returned from this function must be freed using the tox_options_free + * function. + * + * @return A new Tox_Options object with default options or NULL on failure. + */ +struct Tox_Options *tox_options_new(Tox_Err_Options_New *error); + +/** + * @brief Releases all resources associated with an options objects. + * + * Passing a pointer that was not returned by tox_options_new results in + * undefined behaviour. + */ +void tox_options_free(struct Tox_Options *options); + +/** @} */ + + +/** @{ + * @name Creation and destruction + */ + +typedef enum Tox_Err_New { + + /** + * The function returned successfully. + */ + TOX_ERR_NEW_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_NEW_NULL, + + /** + * The function was unable to allocate enough memory to store the events_alloc + * structures for the Tox object. + */ + TOX_ERR_NEW_MALLOC, + + /** + * The function was unable to bind to a port. This may mean that all ports + * have already been bound, e.g. by other Tox instances, or it may mean + * a permission error. You may be able to gather more information from errno. + */ + TOX_ERR_NEW_PORT_ALLOC, + + /** + * proxy_type was invalid. + */ + TOX_ERR_NEW_PROXY_BAD_TYPE, + + /** + * proxy_type was valid but the proxy_host passed had an invalid format + * or was NULL. + */ + TOX_ERR_NEW_PROXY_BAD_HOST, + + /** + * proxy_type was valid, but the proxy_port was invalid. + */ + TOX_ERR_NEW_PROXY_BAD_PORT, + + /** + * The proxy address passed could not be resolved. + */ + TOX_ERR_NEW_PROXY_NOT_FOUND, + + /** + * The byte array to be loaded contained an encrypted save. + */ + TOX_ERR_NEW_LOAD_ENCRYPTED, + + /** + * The data format was invalid. This can happen when loading data that was + * saved by an older version of Tox, or when the data has been corrupted. + * When loading from badly formatted data, some data may have been loaded, + * and the rest is discarded. Passing an invalid length parameter also + * causes this error. + */ + TOX_ERR_NEW_LOAD_BAD_FORMAT, + +} Tox_Err_New; + + +/** + * @brief Creates and initialises a new Tox instance with the options passed. + * + * This function will bring the instance into a valid state. Running the event + * loop with a new instance will operate correctly. + * + * If loading failed or succeeded only partially, the new or partially loaded + * instance is returned and an error code is set. + * + * @param options An options object as described above. If this parameter is + * NULL, the default options are used. + * + * @see tox_iterate for the event loop. + * + * @return A new Tox instance pointer on success or NULL on failure. + */ +Tox *tox_new(const struct Tox_Options *options, Tox_Err_New *error); + +/** + * @brief Releases all resources associated with the Tox instance and + * disconnects from the network. + * + * After calling this function, the Tox pointer becomes invalid. No other + * functions can be called, and the pointer value can no longer be read. + */ +void tox_kill(Tox *tox); + +/** + * @brief Calculates the number of bytes required to store the tox instance with + * tox_get_savedata. + * + * This function cannot fail. The result is always greater than 0. + * + * @see threading for concurrency implications. + */ +size_t tox_get_savedata_size(const Tox *tox); + +/** + * @brief Store all information associated with the tox instance to a byte array. + * + * @param savedata A memory region large enough to store the tox instance + * data. Call tox_get_savedata_size to find the number of bytes required. If this parameter + * is NULL, this function has no effect. + */ +void tox_get_savedata(const Tox *tox, uint8_t *savedata); + +/** @} */ + + +/** @{ + * @name Connection lifecycle and event loop + */ + +typedef enum Tox_Err_Bootstrap { + + /** + * The function returned successfully. + */ + TOX_ERR_BOOTSTRAP_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_BOOTSTRAP_NULL, + + /** + * The hostname could not be resolved to an IP address, the IP address + * passed was invalid, or the function failed to send the initial request + * packet to the bootstrap node or TCP relay. + */ + TOX_ERR_BOOTSTRAP_BAD_HOST, + + /** + * The port passed was invalid. The valid port range is (1, 65535). + */ + TOX_ERR_BOOTSTRAP_BAD_PORT, + +} Tox_Err_Bootstrap; + + +/** + * @brief Sends a "get nodes" request to the given bootstrap node with IP, port, + * and public key to setup connections. + * + * This function will attempt to connect to the node using UDP. You must use + * this function even if Tox_Options.udp_enabled was set to false. + * + * @param host The hostname or IP address (IPv4 or IPv6) of the node. Must be + * at most TOX_MAX_HOSTNAME_LENGTH chars, including the NUL byte. + * @param port The port on the host on which the bootstrap Tox instance is + * listening. + * @param public_key The long term public key of the bootstrap node + * (TOX_PUBLIC_KEY_SIZE bytes). + * @return true on success. + */ +bool tox_bootstrap(Tox *tox, const char *host, uint16_t port, const uint8_t *public_key, Tox_Err_Bootstrap *error); + +/** + * @brief Adds additional host:port pair as TCP relay. + * + * This function can be used to initiate TCP connections to different ports on + * the same bootstrap node, or to add TCP relays without using them as + * bootstrap nodes. + * + * @param host The hostname or IP address (IPv4 or IPv6) of the TCP relay. + * Must be at most TOX_MAX_HOSTNAME_LENGTH chars, including the NUL byte. + * @param port The port on the host on which the TCP relay is listening. + * @param public_key The long term public key of the TCP relay + * (TOX_PUBLIC_KEY_SIZE bytes). + * @return true on success. + */ +bool tox_add_tcp_relay(Tox *tox, const char *host, uint16_t port, const uint8_t *public_key, Tox_Err_Bootstrap *error); + +/** + * @brief Protocols that can be used to connect to the network or friends. + */ +typedef enum Tox_Connection { + + /** + * @brief There is no connection. + * + * This instance, or the friend the state change is about, is now offline. + */ + TOX_CONNECTION_NONE, + + /** + * @brief A TCP connection has been established. + * + * For the own instance, this means it is connected through a TCP relay, + * only. For a friend, this means that the connection to that particular + * friend goes through a TCP relay. + */ + TOX_CONNECTION_TCP, + + /** + * @brief A UDP connection has been established. + * + * For the own instance, this means it is able to send UDP packets to DHT + * nodes, but may still be connected to a TCP relay. For a friend, this + * means that the connection to that particular friend was built using + * direct UDP packets. + */ + TOX_CONNECTION_UDP, + +} Tox_Connection; + +/** + * @brief Return whether we are connected to the DHT. + * + * The return value is equal to the last value received through the + * `self_connection_status` callback. + * + * @deprecated This getter is deprecated. Use the event and store the status + * in the client state. + */ +Tox_Connection tox_self_get_connection_status(const Tox *tox); + +/** + * @param connection_status Whether we are connected to the DHT. + */ +typedef void tox_self_connection_status_cb(Tox *tox, Tox_Connection connection_status, void *user_data); + + +/** + * @brief Set the callback for the `self_connection_status` event. + * + * Pass NULL to unset. + * + * This event is triggered whenever there is a change in the DHT connection + * state. When disconnected, a client may choose to call tox_bootstrap again, to + * reconnect to the DHT. Note that this state may frequently change for short + * amounts of time. Clients should therefore not immediately bootstrap on + * receiving a disconnect. + * + * TODO(iphydf): how long should a client wait before bootstrapping again? + */ +void tox_callback_self_connection_status(Tox *tox, tox_self_connection_status_cb *callback); + +/** + * @brief Return the time in milliseconds before `tox_iterate()` should be called again + * for optimal performance. + */ +uint32_t tox_iteration_interval(const Tox *tox); + +/** + * @brief The main loop that needs to be run in intervals of `tox_iteration_interval()` + * milliseconds. + */ +void tox_iterate(Tox *tox, void *user_data); + +/** @} */ + + +/** @{ + * @name Internal client information (Tox address/id) + */ + +/** + * @brief Writes the Tox friend address of the client to a byte array. + * + * The address is not in human-readable format. If a client wants to display + * the address, formatting is required. + * + * @param address A memory region of at least TOX_ADDRESS_SIZE bytes. If this + * parameter is NULL, this function has no effect. + * @see TOX_ADDRESS_SIZE for the address format. + */ +void tox_self_get_address(const Tox *tox, uint8_t *address); + +/** + * @brief Set the 4-byte nospam part of the address. + * + * This value is expected in host byte order. I.e. 0x12345678 will form the + * bytes `[12, 34, 56, 78]` in the nospam part of the Tox friend address. + * + * @param nospam Any 32 bit unsigned integer. + */ +void tox_self_set_nospam(Tox *tox, uint32_t nospam); + +/** + * @brief Get the 4-byte nospam part of the address. + * + * This value is returned in host byte order. + */ +uint32_t tox_self_get_nospam(const Tox *tox); + +/** + * @brief Copy the Tox Public Key (long term) from the Tox object. + * + * @param public_key A memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If + * this parameter is NULL, this function has no effect. + */ +void tox_self_get_public_key(const Tox *tox, uint8_t *public_key); + +/** + * @brief Copy the Tox Secret Key from the Tox object. + * + * @param secret_key A memory region of at least TOX_SECRET_KEY_SIZE bytes. If + * this parameter is NULL, this function has no effect. + */ +void tox_self_get_secret_key(const Tox *tox, uint8_t *secret_key); + +/** + * Return the capabilities flags for this tox instance. + */ +uint64_t tox_self_get_capabilities(void); +/** @} */ + +/** @{ + * @name User-visible client information (nickname/status) + */ + +/** + * @brief Common error codes for all functions that set a piece of user-visible + * client information. + */ +typedef enum Tox_Err_Set_Info { + + /** + * The function returned successfully. + */ + TOX_ERR_SET_INFO_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_SET_INFO_NULL, + + /** + * Information length exceeded maximum permissible size. + */ + TOX_ERR_SET_INFO_TOO_LONG, + +} Tox_Err_Set_Info; + + +/** + * @brief Set the nickname for the Tox client. + * + * Nickname length cannot exceed TOX_MAX_NAME_LENGTH. If length is 0, the name + * parameter is ignored (it can be NULL), and the nickname is set back to empty. + * + * @param name A byte array containing the new nickname. + * @param length The size of the name byte array. + * + * @return true on success. + */ +bool tox_self_set_name(Tox *tox, const uint8_t *name, size_t length, Tox_Err_Set_Info *error); + +/** + * @brief Return the length of the current nickname as passed to tox_self_set_name. + * + * If no nickname was set before calling this function, the name is empty, + * and this function returns 0. + * + * @see threading for concurrency implications. + */ +size_t tox_self_get_name_size(const Tox *tox); + +/** + * @brief Write the nickname set by tox_self_set_name to a byte array. + * + * If no nickname was set before calling this function, the name is empty, + * and this function has no effect. + * + * Call tox_self_get_name_size to find out how much memory to allocate for + * the result. + * + * @param name A valid memory location large enough to hold the nickname. + * If this parameter is NULL, the function has no effect. + */ +void tox_self_get_name(const Tox *tox, uint8_t *name); + +/** + * Write new message ID to a byte array. + * + * @param msg_id A valid memory location at least TOX_HASH_LENGTH bytes in size. + * + * @return true on success. + */ +bool tox_messagev3_get_new_message_id(uint8_t *msg_id); + +/** + * @brief Set the client's status message. + * + * Status message length cannot exceed TOX_MAX_STATUS_MESSAGE_LENGTH. If + * length is 0, the status parameter is ignored (it can be NULL), and the + * user status is set back to empty. + */ +bool tox_self_set_status_message(Tox *tox, const uint8_t *status_message, size_t length, Tox_Err_Set_Info *error); + +/** + * @brief Return the length of the current status message as passed to tox_self_set_status_message. + * + * If no status message was set before calling this function, the status + * is empty, and this function returns 0. + * + * @see threading for concurrency implications. + */ +size_t tox_self_get_status_message_size(const Tox *tox); + +/** + * @brief Write the status message set by tox_self_set_status_message to a byte array. + * + * If no status message was set before calling this function, the status is + * empty, and this function has no effect. + * + * Call tox_self_get_status_message_size to find out how much memory to allocate for + * the result. + * + * @param status_message A valid memory location large enough to hold the + * status message. If this parameter is NULL, the function has no effect. + */ +void tox_self_get_status_message(const Tox *tox, uint8_t *status_message); + +/** + * @brief Set the client's user status. + * + * @param status One of the user statuses listed in the enumeration above. + */ +void tox_self_set_status(Tox *tox, Tox_User_Status status); + +/** + * @brief Returns the client's user status. + */ +Tox_User_Status tox_self_get_status(const Tox *tox); + +/** @} */ + + +/** @{ + * @name Friend list management + */ + +typedef enum Tox_Err_Friend_Add { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_ADD_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_FRIEND_ADD_NULL, + + /** + * The length of the friend request message exceeded + * TOX_MAX_FRIEND_REQUEST_LENGTH. + */ + TOX_ERR_FRIEND_ADD_TOO_LONG, + + /** + * The friend request message was empty. This, and the TOO_LONG code will + * never be returned from tox_friend_add_norequest. + */ + TOX_ERR_FRIEND_ADD_NO_MESSAGE, + + /** + * The friend address belongs to the sending client. + */ + TOX_ERR_FRIEND_ADD_OWN_KEY, + + /** + * A friend request has already been sent, or the address belongs to a friend + * that is already on the friend list. + */ + TOX_ERR_FRIEND_ADD_ALREADY_SENT, + + /** + * The friend address checksum failed. + */ + TOX_ERR_FRIEND_ADD_BAD_CHECKSUM, + + /** + * The friend was already there, but the nospam value was different. + */ + TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM, + + /** + * A memory allocation failed when trying to increase the friend list size. + */ + TOX_ERR_FRIEND_ADD_MALLOC, + +} Tox_Err_Friend_Add; + + +/** + * @brief Add a friend to the friend list and send a friend request. + * + * A friend request message must be at least 1 byte long and at most + * TOX_MAX_FRIEND_REQUEST_LENGTH. + * + * Friend numbers are unique identifiers used in all functions that operate on + * friends. Once added, a friend number is stable for the lifetime of the Tox + * object. After saving the state and reloading it, the friend numbers may not + * be the same as before. Deleting a friend creates a gap in the friend number + * set, which is filled by the next adding of a friend. Any pattern in friend + * numbers should not be relied on. + * + * If more than INT32_MAX friends are added, this function causes undefined + * behaviour. + * + * @param address The address of the friend (returned by tox_self_get_address of + * the friend you wish to add) it must be TOX_ADDRESS_SIZE bytes. + * @param message The message that will be sent along with the friend request. + * @param length The length of the data byte array. + * + * @return the friend number on success, an unspecified value on failure. + */ +uint32_t tox_friend_add(Tox *tox, const uint8_t *address, const uint8_t *message, size_t length, + Tox_Err_Friend_Add *error); + +/** + * @brief Add a friend without sending a friend request. + * + * This function is used to add a friend in response to a friend request. If the + * client receives a friend request, it can be reasonably sure that the other + * client added this client as a friend, eliminating the need for a friend + * request. + * + * This function is also useful in a situation where both instances are + * controlled by the same entity, so that this entity can perform the mutual + * friend adding. In this case, there is no need for a friend request, either. + * + * @param public_key A byte array of length TOX_PUBLIC_KEY_SIZE containing the + * Public Key (not the Address) of the friend to add. + * + * @return the friend number on success, an unspecified value on failure. + * @see tox_friend_add for a more detailed description of friend numbers. + */ +uint32_t tox_friend_add_norequest(Tox *tox, const uint8_t *public_key, Tox_Err_Friend_Add *error); + +typedef enum Tox_Err_Friend_Delete { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_DELETE_OK, + + /** + * There was no friend with the given friend number. No friends were deleted. + */ + TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND, + +} Tox_Err_Friend_Delete; + + +/** + * @brief Remove a friend from the friend list. + * + * This does not notify the friend of their deletion. After calling this + * function, this client will appear offline to the friend and no communication + * can occur between the two. + * + * @param friend_number Friend number for the friend to be deleted. + * + * @return true on success. + */ +bool tox_friend_delete(Tox *tox, uint32_t friend_number, Tox_Err_Friend_Delete *error); + +/** @} */ + + +/** @{ + * @name Friend list queries + */ + +typedef enum Tox_Err_Friend_By_Public_Key { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL, + + /** + * No friend with the given Public Key exists on the friend list. + */ + TOX_ERR_FRIEND_BY_PUBLIC_KEY_NOT_FOUND, + +} Tox_Err_Friend_By_Public_Key; + + +/** + * @brief Return the friend number associated with that Public Key. + * + * @return the friend number on success, an unspecified value on failure. + * @param public_key A byte array containing the Public Key. + */ +uint32_t tox_friend_by_public_key(const Tox *tox, const uint8_t *public_key, Tox_Err_Friend_By_Public_Key *error); + +/** + * @brief Checks if a friend with the given friend number exists and returns true if + * it does. + */ +bool tox_friend_exists(const Tox *tox, uint32_t friend_number); + +/** + * @brief Return the number of friends on the friend list. + * + * This function can be used to determine how much memory to allocate for + * tox_self_get_friend_list. + */ +size_t tox_self_get_friend_list_size(const Tox *tox); + +/** + * @brief Copy a list of valid friend numbers into an array. + * + * Call tox_self_get_friend_list_size to determine the number of elements to allocate. + * + * @param friend_list A memory region with enough space to hold the friend + * list. If this parameter is NULL, this function has no effect. + */ +void tox_self_get_friend_list(const Tox *tox, uint32_t *friend_list); + +typedef enum Tox_Err_Friend_Get_Public_Key { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK, + + /** + * No friend with the given number exists on the friend list. + */ + TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND, + +} Tox_Err_Friend_Get_Public_Key; + + +/** + * @brief Copies the Public Key associated with a given friend number to a byte array. + * + * @param friend_number The friend number you want the Public Key of. + * @param public_key A memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If + * this parameter is NULL, this function has no effect. + * + * @return true on success. + */ +bool tox_friend_get_public_key(const Tox *tox, uint32_t friend_number, uint8_t *public_key, + Tox_Err_Friend_Get_Public_Key *error); + +typedef enum Tox_Err_Friend_Get_Last_Online { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_GET_LAST_ONLINE_OK, + + /** + * No friend with the given number exists on the friend list. + */ + TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND, + +} Tox_Err_Friend_Get_Last_Online; + + +/** + * @brief Return a unix-time timestamp of the last time the friend associated with a given + * friend number was seen online. + * + * This function will return UINT64_MAX on error. + * + * @param friend_number The friend number you want to query. + */ +uint64_t tox_friend_get_last_online(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Get_Last_Online *error); + +/** @} */ + + +/** @{ + * @name Friend-specific state queries (can also be received through callbacks) + */ + +/** + * @brief Common error codes for friend state query functions. + */ +typedef enum Tox_Err_Friend_Query { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_QUERY_OK, + + /** + * The pointer parameter for storing the query result (name, message) was + * NULL. Unlike the `_self_` variants of these functions, which have no effect + * when a parameter is NULL, these functions return an error in that case. + */ + TOX_ERR_FRIEND_QUERY_NULL, + + /** + * The friend_number did not designate a valid friend. + */ + TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND, + +} Tox_Err_Friend_Query; + +/** + * Return the capabilities flags for a friend. If the friend number is invalid, the + * return value is unspecified. + */ +uint64_t tox_friend_get_capabilities(const Tox *tox, uint32_t friend_number); + +/** + * @brief Return the length of the friend's name. + * + * If the friend number is invalid, the return value is unspecified. + * + * The return value is equal to the `length` argument received by the last + * `friend_name` callback. + */ +size_t tox_friend_get_name_size(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error); + +/** + * @brief Write the name of the friend designated by the given friend number to a byte + * array. + * + * Call tox_friend_get_name_size to determine the allocation size for the `name` + * parameter. + * + * The data written to `name` is equal to the data received by the last + * `friend_name` callback. + * + * @param name A valid memory region large enough to store the friend's name. + * + * @return true on success. + */ +bool tox_friend_get_name(const Tox *tox, uint32_t friend_number, uint8_t *name, Tox_Err_Friend_Query *error); + +/** + * @param friend_number The friend number of the friend whose name changed. + * @param name A byte array containing the same data as + * tox_friend_get_name would write to its `name` parameter. + * @param length A value equal to the return value of + * tox_friend_get_name_size. + */ +typedef void tox_friend_name_cb(Tox *tox, uint32_t friend_number, const uint8_t *name, size_t length, void *user_data); + + +/** + * @brief Set the callback for the `friend_name` event. + * + * Pass NULL to unset. + * + * This event is triggered when a friend changes their name. + */ +void tox_callback_friend_name(Tox *tox, tox_friend_name_cb *callback); + +/** + * @brief Return the length of the friend's status message. + * + * If the friend number isinvalid, the return value is SIZE_MAX. + */ +size_t tox_friend_get_status_message_size(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error); + +/** + * @brief Write the status message of the friend designated by the given friend number to a byte + * array. + * + * Call tox_friend_get_status_message_size to determine the allocation size for the `status_message` + * parameter. + * + * The data written to `status_message` is equal to the data received by the last + * `friend_status_message` callback. + * + * @param status_message A valid memory region large enough to store the friend's status message. + */ +bool tox_friend_get_status_message(const Tox *tox, uint32_t friend_number, uint8_t *status_message, + Tox_Err_Friend_Query *error); + +/** + * @param friend_number The friend number of the friend whose status message + * changed. + * @param message A byte array containing the same data as + * tox_friend_get_status_message would write to its `status_message` parameter. + * @param length A value equal to the return value of + * tox_friend_get_status_message_size. + */ +typedef void tox_friend_status_message_cb(Tox *tox, uint32_t friend_number, const uint8_t *message, size_t length, + void *user_data); + + +/** + * @brief Set the callback for the `friend_status_message` event. + * + * Pass NULL to unset. + * + * This event is triggered when a friend changes their status message. + */ +void tox_callback_friend_status_message(Tox *tox, tox_friend_status_message_cb *callback); + +/** + * @brief Return the friend's user status (away/busy/...). + * + * If the friend number is invalid, the return value is unspecified. + * + * The status returned is equal to the last status received through the + * `friend_status` callback. + * + * @deprecated This getter is deprecated. Use the event and store the status + * in the client state. + */ +Tox_User_Status tox_friend_get_status(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error); + +/** + * @param friend_number The friend number of the friend whose user status + * changed. + * @param status The new user status. + */ +typedef void tox_friend_status_cb(Tox *tox, uint32_t friend_number, Tox_User_Status status, void *user_data); + + +/** + * @brief Set the callback for the `friend_status` event. + * + * Pass NULL to unset. + * + * This event is triggered when a friend changes their user status. + */ +void tox_callback_friend_status(Tox *tox, tox_friend_status_cb *callback); + +/** + * @brief Check whether a friend is currently connected to this client. + * + * The result of this function is equal to the last value received by the + * `friend_connection_status` callback. + * + * @param friend_number The friend number for which to query the connection + * status. + * + * @return the friend's connection status as it was received through the + * `friend_connection_status` event. + * + * @deprecated This getter is deprecated. Use the event and store the status + * in the client state. + */ +Tox_Connection tox_friend_get_connection_status(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error); + +/** + * @param friend_number The friend number of the friend whose connection status + * changed. + * @param connection_status The result of calling + * tox_friend_get_connection_status on the passed friend_number. + */ +typedef void tox_friend_connection_status_cb(Tox *tox, uint32_t friend_number, Tox_Connection connection_status, + void *user_data); + + +/** + * @brief Set the callback for the `friend_connection_status` event. + * + * Pass NULL to unset. + * + * This event is triggered when a friend goes offline after having been online, + * or when a friend goes online. + * + * This callback is not called when adding friends. It is assumed that when + * adding friends, their connection status is initially offline. + */ +void tox_callback_friend_connection_status(Tox *tox, tox_friend_connection_status_cb *callback); + +/** + * @brief Check whether a friend is currently typing a message. + * + * @param friend_number The friend number for which to query the typing status. + * + * @return true if the friend is typing. + * @return false if the friend is not typing, or the friend number was + * invalid. Inspect the error code to determine which case it is. + * + * @deprecated This getter is deprecated. Use the event and store the status + * in the client state. + */ +bool tox_friend_get_typing(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error); + +/** + * @param friend_number The friend number of the friend who started or stopped + * typing. + * @param typing The result of calling tox_friend_get_typing on the passed + * friend_number. + */ +typedef void tox_friend_typing_cb(Tox *tox, uint32_t friend_number, bool typing, void *user_data); + + +/** + * @brief Set the callback for the `friend_typing` event. + * + * Pass NULL to unset. + * + * This event is triggered when a friend starts or stops typing. + */ +void tox_callback_friend_typing(Tox *tox, tox_friend_typing_cb *callback); + +/** @} */ + + +/** @{ + * @name Sending private messages + */ + +typedef enum Tox_Err_Set_Typing { + + /** + * The function returned successfully. + */ + TOX_ERR_SET_TYPING_OK, + + /** + * The friend number did not designate a valid friend. + */ + TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND, + +} Tox_Err_Set_Typing; + + +/** + * @brief Set the client's typing status for a friend. + * + * The client is responsible for turning it on or off. + * + * @param friend_number The friend to which the client is typing a message. + * @param typing The typing status. True means the client is typing. + * + * @return true on success. + */ +bool tox_self_set_typing(Tox *tox, uint32_t friend_number, bool typing, Tox_Err_Set_Typing *error); + +typedef enum Tox_Err_Friend_Send_Message { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_NULL, + + /** + * The friend number did not designate a valid friend. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND, + + /** + * This client is currently not connected to the friend. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED, + + /** + * An allocation error occurred while increasing the send queue size. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ, + + /** + * Message length exceeded TOX_MAX_MESSAGE_LENGTH. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG, + + /** + * Attempted to send a zero-length message. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY, + +} Tox_Err_Friend_Send_Message; + + +/** + * @brief Send a text chat message to an online friend. + * + * This function creates a chat message packet and pushes it into the send + * queue. + * + * The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages + * must be split by the client and sent as separate messages. Other clients can + * then reassemble the fragments. Messages may not be empty. + * + * The return value of this function is the message ID. If a read receipt is + * received, the triggered `friend_read_receipt` event will be passed this message ID. + * + * Message IDs are unique per friend. The first message ID is 0. Message IDs are + * incremented by 1 each time a message is sent. If UINT32_MAX messages were + * sent, the next message ID is 0. + * + * @param type Message type (normal, action, ...). + * @param friend_number The friend number of the friend to send the message to. + * @param message A non-NULL pointer to the first element of a byte array + * containing the message text. + * @param length Length of the message to be sent. + */ +uint32_t tox_friend_send_message(Tox *tox, uint32_t friend_number, Tox_Message_Type type, const uint8_t *message, + size_t length, Tox_Err_Friend_Send_Message *error); + +/** + * @param friend_number The friend number of the friend who received the message. + * @param message_id The message ID as returned from tox_friend_send_message + * corresponding to the message sent. + */ +typedef void tox_friend_read_receipt_cb(Tox *tox, uint32_t friend_number, uint32_t message_id, void *user_data); + + +/** + * @brief Set the callback for the `friend_read_receipt` event. + * + * Pass NULL to unset. + * + * This event is triggered when the friend receives the message sent with + * tox_friend_send_message with the corresponding message ID. + */ +void tox_callback_friend_read_receipt(Tox *tox, tox_friend_read_receipt_cb *callback); + +/** @} */ + + +/** @{ + * @name Receiving private messages and friend requests + */ + +/** + * @param public_key The Public Key of the user who sent the friend request. + * @param message The message they sent along with the request. + * @param length The size of the message byte array. + */ +typedef void tox_friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, + void *user_data); + + +/** + * @brief Set the callback for the `friend_request` event. + * + * Pass NULL to unset. + * + * This event is triggered when a friend request is received. + */ +void tox_callback_friend_request(Tox *tox, tox_friend_request_cb *callback); + +/** + * @param friend_number The friend number of the friend who sent the message. + * @param message The message data they sent. + * @param length The size of the message byte array. + */ +typedef void tox_friend_message_cb(Tox *tox, uint32_t friend_number, Tox_Message_Type type, const uint8_t *message, + size_t length, void *user_data); + + +/** + * @brief Set the callback for the `friend_message` event. + * + * Pass NULL to unset. + * + * This event is triggered when a message from a friend is received. + */ +void tox_callback_friend_message(Tox *tox, tox_friend_message_cb *callback); + +/** @} */ + + +/** @{ + * @name File transmission: common between sending and receiving + */ + +/** + * @brief Generates a cryptographic hash of the given data. + * + * This function may be used by clients for any purpose, but is provided + * primarily for validating cached avatars. This use is highly recommended to + * avoid unnecessary avatar updates. + * + * If hash is NULL or data is NULL while length is not 0 the function returns false, + * otherwise it returns true. + * + * This function is a wrapper to events_alloc message-digest functions. + * + * @param hash A valid memory location the hash data. It must be at least + * TOX_HASH_LENGTH bytes in size. + * @param data Data to be hashed or NULL. + * @param length Size of the data array or 0. + * + * @return true if hash was not NULL. + */ +bool tox_hash(uint8_t *hash, const uint8_t *data, size_t length); + +/** + * @brief A list of pre-defined file kinds. + * + * Toxcore itself does not behave differently for different file kinds. These + * are a hint to the client telling it what use the sender intended for the + * file. The `kind` parameter in the send function and recv callback are + * `uint32_t`, not Tox_File_Kind, because clients can invent their own file + * kind. Unknown file kinds should be treated as TOX_FILE_KIND_DATA. + */ +enum Tox_File_Kind { + + /** + * Arbitrary file data. Clients can choose to handle it based on the file name + * or magic or any other way they choose. + */ + TOX_FILE_KIND_DATA, + + /** + * Avatar file_id. This consists of tox_hash(image). + * Avatar data. This consists of the image data. + * + * Avatars can be sent at any time the client wishes. Generally, a client will + * send the avatar to a friend when that friend comes online, and to all + * friends when the avatar changed. A client can save some traffic by + * remembering which friend received the updated avatar already and only send + * it if the friend has an out of date avatar. + * + * Clients who receive avatar send requests can reject it (by sending + * TOX_FILE_CONTROL_CANCEL before any other controls), or accept it (by + * sending TOX_FILE_CONTROL_RESUME). The file_id of length TOX_HASH_LENGTH bytes + * (same length as TOX_FILE_ID_LENGTH) will contain the hash. A client can compare + * this hash with a saved hash and send TOX_FILE_CONTROL_CANCEL to terminate the avatar + * transfer if it matches. + * + * When file_size is set to 0 in the transfer request it means that the client + * has no avatar. + */ + TOX_FILE_KIND_AVATAR, + +}; + + +typedef enum Tox_File_Control { + + /** + * Sent by the receiving side to accept a file send request. Also sent after a + * TOX_FILE_CONTROL_PAUSE command to continue sending or receiving. + */ + TOX_FILE_CONTROL_RESUME, + + /** + * Sent by clients to pause the file transfer. The initial state of a file + * transfer is always paused on the receiving side and running on the sending + * side. If both the sending and receiving side pause the transfer, then both + * need to send TOX_FILE_CONTROL_RESUME for the transfer to resume. + */ + TOX_FILE_CONTROL_PAUSE, + + /** + * Sent by the receiving side to reject a file send request before any other + * commands are sent. Also sent by either side to terminate a file transfer. + */ + TOX_FILE_CONTROL_CANCEL, + +} Tox_File_Control; + + +typedef enum Tox_Err_File_Control { + + /** + * The function returned successfully. + */ + TOX_ERR_FILE_CONTROL_OK, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND, + + /** + * This client is currently not connected to the friend. + */ + TOX_ERR_FILE_CONTROL_FRIEND_NOT_CONNECTED, + + /** + * No file transfer with the given file number was found for the given friend. + */ + TOX_ERR_FILE_CONTROL_NOT_FOUND, + + /** + * A RESUME control was sent, but the file transfer is running normally. + */ + TOX_ERR_FILE_CONTROL_NOT_PAUSED, + + /** + * A RESUME control was sent, but the file transfer was paused by the other + * party. Only the party that paused the transfer can resume it. + */ + TOX_ERR_FILE_CONTROL_DENIED, + + /** + * A PAUSE control was sent, but the file transfer was already paused. + */ + TOX_ERR_FILE_CONTROL_ALREADY_PAUSED, + + /** + * Packet queue is full. + */ + TOX_ERR_FILE_CONTROL_SENDQ, + +} Tox_Err_File_Control; + + +/** + * @brief Sends a file control command to a friend for a given file transfer. + * + * @param friend_number The friend number of the friend the file is being + * transferred to or received from. + * @param file_number The friend-specific identifier for the file transfer. + * @param control The control command to send. + * + * @return true on success. + */ +bool tox_file_control(Tox *tox, uint32_t friend_number, uint32_t file_number, Tox_File_Control control, + Tox_Err_File_Control *error); + +/** + * @brief When receiving TOX_FILE_CONTROL_CANCEL, the client should release the + * resources associated with the file number and consider the transfer failed. + * + * @param friend_number The friend number of the friend who is sending the file. + * @param file_number The friend-specific file number the data received is + * associated with. + * @param control The file control command received. + */ +typedef void tox_file_recv_control_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, Tox_File_Control control, + void *user_data); + + +/** + * @brief Set the callback for the `file_recv_control` event. + * + * Pass NULL to unset. + * + * This event is triggered when a file control command is received from a + * friend. + */ +void tox_callback_file_recv_control(Tox *tox, tox_file_recv_control_cb *callback); + +typedef enum Tox_Err_File_Seek { + + /** + * The function returned successfully. + */ + TOX_ERR_FILE_SEEK_OK, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOX_ERR_FILE_SEEK_FRIEND_NOT_FOUND, + + /** + * This client is currently not connected to the friend. + */ + TOX_ERR_FILE_SEEK_FRIEND_NOT_CONNECTED, + + /** + * No file transfer with the given file number was found for the given friend. + */ + TOX_ERR_FILE_SEEK_NOT_FOUND, + + /** + * File was not in a state where it could be seeked. + */ + TOX_ERR_FILE_SEEK_DENIED, + + /** + * Seek position was invalid + */ + TOX_ERR_FILE_SEEK_INVALID_POSITION, + + /** + * Packet queue is full. + */ + TOX_ERR_FILE_SEEK_SENDQ, + +} Tox_Err_File_Seek; + + +/** + * @brief Sends a file seek control command to a friend for a given file transfer. + * + * This function can only be called to resume a file transfer right before + * TOX_FILE_CONTROL_RESUME is sent. + * + * @param friend_number The friend number of the friend the file is being + * received from. + * @param file_number The friend-specific identifier for the file transfer. + * @param position The position that the file should be seeked to. + */ +bool tox_file_seek(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, Tox_Err_File_Seek *error); + +typedef enum Tox_Err_File_Get { + + /** + * The function returned successfully. + */ + TOX_ERR_FILE_GET_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_FILE_GET_NULL, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, + + /** + * No file transfer with the given file number was found for the given friend. + */ + TOX_ERR_FILE_GET_NOT_FOUND, + +} Tox_Err_File_Get; + + +/** + * @brief Copy the file id associated to the file transfer to a byte array. + * + * @param friend_number The friend number of the friend the file is being + * transferred to or received from. + * @param file_number The friend-specific identifier for the file transfer. + * @param file_id A memory region of at least TOX_FILE_ID_LENGTH bytes. If + * this parameter is NULL, this function has no effect. + * + * @return true on success. + */ +bool tox_file_get_file_id(const Tox *tox, uint32_t friend_number, uint32_t file_number, uint8_t *file_id, + Tox_Err_File_Get *error); + +/** @} */ + + +/** @{ + * @name File transmission: sending + */ + +typedef enum Tox_Err_File_Send { + + /** + * The function returned successfully. + */ + TOX_ERR_FILE_SEND_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_FILE_SEND_NULL, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND, + + /** + * This client is currently not connected to the friend. + */ + TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED, + + /** + * Filename length exceeded TOX_MAX_FILENAME_LENGTH bytes. + */ + TOX_ERR_FILE_SEND_NAME_TOO_LONG, + + /** + * Too many ongoing transfers. The maximum number of concurrent file transfers + * is 256 per friend per direction (sending and receiving). + */ + TOX_ERR_FILE_SEND_TOO_MANY, + +} Tox_Err_File_Send; + + +/** + * @brief Send a file transmission request. + * + * Maximum filename length is TOX_MAX_FILENAME_LENGTH bytes. The filename + * should generally just be a file name, not a path with directory names. + * + * If a non-UINT64_MAX file size is provided, it can be used by both sides to + * determine the sending progress. File size can be set to UINT64_MAX for + * streaming data of unknown size. + * + * File transmission occurs in chunks, which are requested through the + * `file_chunk_request` event. + * + * When a friend goes offline, all file transfers associated with the friend are + * purged from core. + * + * If the file contents change during a transfer, the behaviour is unspecified + * in general. What will actually happen depends on the mode in which the file + * was modified and how the client determines the file size. + * + * - If the file size was increased + * - and sending mode was streaming (file_size = UINT64_MAX), the behaviour + * will be as expected. + * - and sending mode was file (file_size != UINT64_MAX), the file_chunk_request + * callback will receive length = 0 when Core thinks the file transfer has + * finished. If the client remembers the file size as it was when sending the + * request, it will terminate the transfer normally. If the client re-reads the + * size, it will think the friend cancelled the transfer. + * - If the file size was decreased + * - and sending mode was streaming, the behaviour is as expected. + * - and sending mode was file, the callback will return 0 at the new + * (earlier) end-of-file, signalling to the friend that the transfer was + * cancelled. + * - If the file contents were modified + * - at a position before the current read, the two files (local and remote) + * will differ after the transfer terminates. + * - at a position after the current read, the file transfer will succeed as + * expected. + * - In either case, both sides will regard the transfer as complete and + * successful. + * + * @param friend_number The friend number of the friend the file send request + * should be sent to. + * @param kind The meaning of the file to be sent. + * @param file_size Size in bytes of the file the client wants to send, UINT64_MAX if + * unknown or streaming. + * @param file_id A file identifier of length TOX_FILE_ID_LENGTH that can be used to + * uniquely identify file transfers across core restarts. If NULL, a random one will + * be generated by core. It can then be obtained by using `tox_file_get_file_id()`. + * @param filename Name of the file. Does not need to be the actual name. This + * name will be sent along with the file send request. + * @param filename_length Size in bytes of the filename. + * + * @return A file number used as an identifier in subsequent callbacks. This + * number is per friend. File numbers are reused after a transfer terminates. + * On failure, this function returns an unspecified value. Any pattern in file numbers + * should not be relied on. + */ +uint32_t tox_file_send(Tox *tox, uint32_t friend_number, uint32_t kind, uint64_t file_size, const uint8_t *file_id, + const uint8_t *filename, size_t filename_length, Tox_Err_File_Send *error); + +typedef enum Tox_Err_File_Send_Chunk { + + /** + * The function returned successfully. + */ + TOX_ERR_FILE_SEND_CHUNK_OK, + + /** + * The length parameter was non-zero, but data was NULL. + */ + TOX_ERR_FILE_SEND_CHUNK_NULL, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_FOUND, + + /** + * This client is currently not connected to the friend. + */ + TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_CONNECTED, + + /** + * No file transfer with the given file number was found for the given friend. + */ + TOX_ERR_FILE_SEND_CHUNK_NOT_FOUND, + + /** + * File transfer was found but isn't in a transferring state: (paused, done, + * broken, etc...) (happens only when not called from the request chunk callback). + */ + TOX_ERR_FILE_SEND_CHUNK_NOT_TRANSFERRING, + + /** + * Attempted to send more or less data than requested. The requested data size is + * adjusted according to maximum transmission unit and the expected end of + * the file. Trying to send less or more than requested will return this error. + */ + TOX_ERR_FILE_SEND_CHUNK_INVALID_LENGTH, + + /** + * Packet queue is full. + */ + TOX_ERR_FILE_SEND_CHUNK_SENDQ, + + /** + * Position parameter was wrong. + */ + TOX_ERR_FILE_SEND_CHUNK_WRONG_POSITION, + +} Tox_Err_File_Send_Chunk; + + +/** + * @brief Send a chunk of file data to a friend. + * + * This function is called in response to the `file_chunk_request` callback. The + * length parameter should be equal to the one received though the callback. + * If it is zero, the transfer is assumed complete. For files with known size, + * Core will know that the transfer is complete after the last byte has been + * received, so it is not necessary (though not harmful) to send a zero-length + * chunk to terminate. For streams, core will know that the transfer is finished + * if a chunk with length less than the length requested in the callback is sent. + * + * @param friend_number The friend number of the receiving friend for this file. + * @param file_number The file transfer identifier returned by tox_file_send. + * @param position The file or stream position from which to continue reading. + * @return true on success. + */ +bool tox_file_send_chunk(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, + size_t length, Tox_Err_File_Send_Chunk *error); + +/** + * If the length parameter is 0, the file transfer is finished, and the client's + * resources associated with the file number should be released. After a call + * with zero length, the file number can be reused for future file transfers. + * + * If the requested position is not equal to the client's idea of the current + * file or stream position, it will need to seek. In case of read-once streams, + * the client should keep the last read chunk so that a seek back can be + * supported. A seek-back only ever needs to read from the last requested chunk. + * This happens when a chunk was requested, but the send failed. A seek-back + * request can occur an arbitrary number of times for any given chunk. + * + * In response to receiving this callback, the client should call the function + * `tox_file_send_chunk` with the requested chunk. If the number of bytes sent + * through that function is zero, the file transfer is assumed complete. A + * client must send the full length of data requested with this callback. + * + * @param friend_number The friend number of the receiving friend for this file. + * @param file_number The file transfer identifier returned by tox_file_send. + * @param position The file or stream position from which to continue reading. + * @param length The number of bytes requested for the current chunk. + */ +typedef void tox_file_chunk_request_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, + size_t length, void *user_data); + + +/** + * @brief Set the callback for the `file_chunk_request` event. + * + * Pass NULL to unset. + * + * This event is triggered when Core is ready to send more file data. + */ +void tox_callback_file_chunk_request(Tox *tox, tox_file_chunk_request_cb *callback); + +/** @} */ + + +/** @{ + * @name File transmission: receiving + */ + +/** + * The client should acquire resources to be associated with the file transfer. + * Incoming file transfers start in the PAUSED state. After this callback + * returns, a transfer can be rejected by sending a TOX_FILE_CONTROL_CANCEL + * control command before any other control commands. It can be accepted by + * sending TOX_FILE_CONTROL_RESUME. + * + * @param friend_number The friend number of the friend who is sending the file + * transfer request. + * @param file_number The friend-specific file number the data received is + * associated with. + * @param kind The meaning of the file that was sent. + * @param file_size Size in bytes of the file the client wants to send, + * UINT64_MAX if unknown or streaming. + * @param filename Name of the file. Does not need to be the actual name. This + * name will be sent along with the file send request. + * @param filename_length Size in bytes of the filename. + */ +typedef void tox_file_recv_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t file_size, + const uint8_t *filename, size_t filename_length, void *user_data); + + +/** + * @brief Set the callback for the `file_recv` event. + * + * Pass NULL to unset. + * + * This event is triggered when a file transfer request is received. + */ +void tox_callback_file_recv(Tox *tox, tox_file_recv_cb *callback); + +/** + * When length is 0, the transfer is finished and the client should release the + * resources it acquired for the transfer. After a call with length = 0, the + * file number can be reused for new file transfers. + * + * If position is equal to file_size (received in the file_receive callback) + * when the transfer finishes, the file was received completely. Otherwise, if + * file_size was UINT64_MAX, streaming ended successfully when length is 0. + * + * @param friend_number The friend number of the friend who is sending the file. + * @param file_number The friend-specific file number the data received is + * associated with. + * @param position The file position of the first byte in data. + * @param data A byte array containing the received chunk. + * @param length The length of the received chunk. + */ +typedef void tox_file_recv_chunk_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, + const uint8_t *data, size_t length, void *user_data); + + +/** + * @brief Set the callback for the `file_recv_chunk` event. + * + * Pass NULL to unset. + * + * This event is first triggered when a file transfer request is received, and + * subsequently when a chunk of file data for an accepted request was received. + */ +void tox_callback_file_recv_chunk(Tox *tox, tox_file_recv_chunk_cb *callback); + +/** @} */ + + +/** @{ + * @name Conference management + */ + +/** + * @brief Conference types for the conference_invite event. + */ +typedef enum Tox_Conference_Type { + + /** + * Text-only conferences that must be accepted with the tox_conference_join function. + */ + TOX_CONFERENCE_TYPE_TEXT, + + /** + * Video conference. The function to accept these is in toxav. + */ + TOX_CONFERENCE_TYPE_AV, + +} Tox_Conference_Type; + + +/** + * The invitation will remain valid until the inviting friend goes offline + * or exits the conference. + * + * @param friend_number The friend who invited us. + * @param type The conference type (text only or audio/video). + * @param cookie A piece of data of variable length required to join the + * conference. + * @param length The length of the cookie. + */ +typedef void tox_conference_invite_cb(Tox *tox, uint32_t friend_number, Tox_Conference_Type type, const uint8_t *cookie, + size_t length, void *user_data); + + +/** + * @brief Set the callback for the `conference_invite` event. + * + * Pass NULL to unset. + * + * This event is triggered when the client is invited to join a conference. + */ +void tox_callback_conference_invite(Tox *tox, tox_conference_invite_cb *callback); + +/** + * @param conference_number The conference number of the conference to which we have connected. + */ +typedef void tox_conference_connected_cb(Tox *tox, uint32_t conference_number, void *user_data); + + +/** + * @brief Set the callback for the `conference_connected` event. + * + * Pass NULL to unset. + * + * This event is triggered when the client successfully connects to a + * conference after joining it with the tox_conference_join function. + */ +void tox_callback_conference_connected(Tox *tox, tox_conference_connected_cb *callback); + +/** + * @param conference_number The conference number of the conference the message + * is intended for. + * @param peer_number The ID of the peer who sent the message. + * @param type The type of message (normal, action, ...). + * @param message The message data. + * @param length The length of the message. + */ +typedef void tox_conference_message_cb(Tox *tox, uint32_t conference_number, uint32_t peer_number, + Tox_Message_Type type, const uint8_t *message, size_t length, void *user_data); + + +/** + * @brief Set the callback for the `conference_message` event. + * + * Pass NULL to unset. + * + * This event is triggered when the client receives a conference message. + */ +void tox_callback_conference_message(Tox *tox, tox_conference_message_cb *callback); + +/** + * @param conference_number The conference number of the conference the title + * change is intended for. + * @param peer_number The ID of the peer who changed the title. + * @param title The title data. + * @param length The title length. + */ +typedef void tox_conference_title_cb(Tox *tox, uint32_t conference_number, uint32_t peer_number, const uint8_t *title, + size_t length, void *user_data); + + +/** + * @brief Set the callback for the `conference_title` event. + * + * Pass NULL to unset. + * + * This event is triggered when a peer changes the conference title. + * + * If peer_number == UINT32_MAX, then author is unknown (e.g. initial joining the conference). + */ +void tox_callback_conference_title(Tox *tox, tox_conference_title_cb *callback); + +/** + * @param conference_number The conference number of the conference the + * peer is in. + * @param peer_number The ID of the peer who changed their nickname. + * @param name A byte array containing the new nickname. + * @param length The size of the name byte array. + */ +typedef void tox_conference_peer_name_cb(Tox *tox, uint32_t conference_number, uint32_t peer_number, + const uint8_t *name, size_t length, void *user_data); + + +/** + * @brief Set the callback for the `conference_peer_name` event. + * + * Pass NULL to unset. + * + * This event is triggered when a peer changes their name. + */ +void tox_callback_conference_peer_name(Tox *tox, tox_conference_peer_name_cb *callback); + +/** + * @param conference_number The conference number of the conference the + * peer is in. + */ +typedef void tox_conference_peer_list_changed_cb(Tox *tox, uint32_t conference_number, void *user_data); + + +/** + * @brief Set the callback for the `conference_peer_list_changed` event. + * + * Pass NULL to unset. + * + * This event is triggered when a peer joins or leaves the conference. + */ +void tox_callback_conference_peer_list_changed(Tox *tox, tox_conference_peer_list_changed_cb *callback); + +typedef enum Tox_Err_Conference_New { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_NEW_OK, + + /** + * The conference instance failed to initialize. + */ + TOX_ERR_CONFERENCE_NEW_INIT, + +} Tox_Err_Conference_New; + + +/** + * @brief Creates a new conference. + * + * This function creates and connects to a new text conference. + * + * @return + * - conference number on success + * - an unspecified value on failure + */ +uint32_t tox_conference_new(Tox *tox, Tox_Err_Conference_New *error); + +typedef enum Tox_Err_Conference_Delete { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_DELETE_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_DELETE_CONFERENCE_NOT_FOUND, + +} Tox_Err_Conference_Delete; + + +/** + * @brief This function deletes a conference. + * + * @param conference_number The conference number of the conference to be deleted. + * + * @return true on success. + */ +bool tox_conference_delete(Tox *tox, uint32_t conference_number, Tox_Err_Conference_Delete *error); + +/** + * @brief Error codes for peer info queries. + */ +typedef enum Tox_Err_Conference_Peer_Query { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_PEER_QUERY_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND, + + /** + * The peer number passed did not designate a valid peer. + */ + TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND, + + /** + * The client is not connected to the conference. + */ + TOX_ERR_CONFERENCE_PEER_QUERY_NO_CONNECTION, + +} Tox_Err_Conference_Peer_Query; + + +/** + * @brief Return the number of online peers in the conference. + * + * The unsigned integers less than this number are the valid values of + * peer_number for the functions querying these peers. Return value is + * unspecified on failure. + */ +uint32_t tox_conference_peer_count(const Tox *tox, uint32_t conference_number, Tox_Err_Conference_Peer_Query *error); + +/** + * @brief Return the length of the peer's name. + * + * Return value is unspecified on failure. + */ +size_t tox_conference_peer_get_name_size(const Tox *tox, uint32_t conference_number, uint32_t peer_number, + Tox_Err_Conference_Peer_Query *error); + +/** + * @brief Copy the name of peer_number who is in conference_number to name. + * + * Call tox_conference_peer_get_name_size to determine the allocation size for the `name` parameter. + * + * @param name A valid memory region large enough to store the peer's name. + * + * @return true on success. + */ +bool tox_conference_peer_get_name(const Tox *tox, uint32_t conference_number, uint32_t peer_number, uint8_t *name, + Tox_Err_Conference_Peer_Query *error); + +/** + * @brief Copy the public key of peer_number who is in conference_number to public_key. + * + * public_key must be TOX_PUBLIC_KEY_SIZE long. + * + * @return true on success. + */ +bool tox_conference_peer_get_public_key(const Tox *tox, uint32_t conference_number, uint32_t peer_number, + uint8_t *public_key, Tox_Err_Conference_Peer_Query *error); + +/** + * @brief Return true if passed peer_number corresponds to our own. + */ +bool tox_conference_peer_number_is_ours(const Tox *tox, uint32_t conference_number, uint32_t peer_number, + Tox_Err_Conference_Peer_Query *error); + +/** + * @brief Return the number of offline peers in the conference. + * + * The unsigned integers less than this number are the valid values of + * offline_peer_number for the functions querying these peers. + * + * Return value is unspecified on failure. + */ +uint32_t tox_conference_offline_peer_count(const Tox *tox, uint32_t conference_number, + Tox_Err_Conference_Peer_Query *error); + +/** + * @brief Return the length of the offline peer's name. + * + * Return value is unspecified on failure. + */ +size_t tox_conference_offline_peer_get_name_size(const Tox *tox, uint32_t conference_number, + uint32_t offline_peer_number, Tox_Err_Conference_Peer_Query *error); + +/** + * @brief Copy the name of offline_peer_number who is in conference_number to name. + * + * Call tox_conference_offline_peer_get_name_size to determine the allocation + * size for the `name` parameter. + * + * @param name A valid memory region large enough to store the peer's name. + * + * @return true on success. + */ +bool tox_conference_offline_peer_get_name(const Tox *tox, uint32_t conference_number, uint32_t offline_peer_number, + uint8_t *name, Tox_Err_Conference_Peer_Query *error); + +/** + * @brief Copy the public key of offline_peer_number who is in conference_number to public_key. + * + * public_key must be TOX_PUBLIC_KEY_SIZE long. + * + * @return true on success. + */ +bool tox_conference_offline_peer_get_public_key(const Tox *tox, uint32_t conference_number, + uint32_t offline_peer_number, uint8_t *public_key, Tox_Err_Conference_Peer_Query *error); + +/** + * @brief Return a unix-time timestamp of the last time offline_peer_number was seen to be active. + */ +uint64_t tox_conference_offline_peer_get_last_active(const Tox *tox, uint32_t conference_number, + uint32_t offline_peer_number, Tox_Err_Conference_Peer_Query *error); + +typedef enum Tox_Err_Conference_Set_Max_Offline { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_SET_MAX_OFFLINE_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_SET_MAX_OFFLINE_CONFERENCE_NOT_FOUND, + +} Tox_Err_Conference_Set_Max_Offline; + + +/** + * @brief Set maximum number of offline peers to store, overriding the default. + */ +bool tox_conference_set_max_offline(Tox *tox, uint32_t conference_number, uint32_t max_offline_peers, + Tox_Err_Conference_Set_Max_Offline *error); + +typedef enum Tox_Err_Conference_Invite { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_INVITE_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_INVITE_CONFERENCE_NOT_FOUND, + + /** + * The invite packet failed to send. + */ + TOX_ERR_CONFERENCE_INVITE_FAIL_SEND, + + /** + * The client is not connected to the conference. + */ + TOX_ERR_CONFERENCE_INVITE_NO_CONNECTION, + +} Tox_Err_Conference_Invite; + + +/** + * @brief Invites a friend to a conference. + * + * @param friend_number The friend number of the friend we want to invite. + * @param conference_number The conference number of the conference we want to invite the friend to. + * + * @return true on success. + */ +bool tox_conference_invite(Tox *tox, uint32_t friend_number, uint32_t conference_number, + Tox_Err_Conference_Invite *error); + +typedef enum Tox_Err_Conference_Join { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_JOIN_OK, + + /** + * The cookie passed has an invalid length. + */ + TOX_ERR_CONFERENCE_JOIN_INVALID_LENGTH, + + /** + * The conference is not the expected type. This indicates an invalid cookie. + */ + TOX_ERR_CONFERENCE_JOIN_WRONG_TYPE, + + /** + * The friend number passed does not designate a valid friend. + */ + TOX_ERR_CONFERENCE_JOIN_FRIEND_NOT_FOUND, + + /** + * Client is already in this conference. + */ + TOX_ERR_CONFERENCE_JOIN_DUPLICATE, + + /** + * Conference instance failed to initialize. + */ + TOX_ERR_CONFERENCE_JOIN_INIT_FAIL, + + /** + * The join packet failed to send. + */ + TOX_ERR_CONFERENCE_JOIN_FAIL_SEND, + +} Tox_Err_Conference_Join; + + +/** + * @brief Joins a conference that the client has been invited to. + * + * After successfully joining the conference, the client will not be "connected" + * to it until a handshaking procedure has been completed. A + * `conference_connected` event will then occur for the conference. The client + * will then remain connected to the conference until the conference is deleted, + * even across core restarts. Many operations on a conference will fail with a + * corresponding error if attempted on a conference to which the client is not + * yet connected. + * + * @param friend_number The friend number of the friend who sent the invite. + * @param cookie Received via the `conference_invite` event. + * @param length The size of cookie. + * + * @return conference number on success, an unspecified value on failure. + */ +uint32_t tox_conference_join(Tox *tox, uint32_t friend_number, const uint8_t *cookie, size_t length, + Tox_Err_Conference_Join *error); + +typedef enum Tox_Err_Conference_Send_Message { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_SEND_MESSAGE_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_SEND_MESSAGE_CONFERENCE_NOT_FOUND, + + /** + * The message is too long. + */ + TOX_ERR_CONFERENCE_SEND_MESSAGE_TOO_LONG, + + /** + * The client is not connected to the conference. + */ + TOX_ERR_CONFERENCE_SEND_MESSAGE_NO_CONNECTION, + + /** + * The message packet failed to send. + */ + TOX_ERR_CONFERENCE_SEND_MESSAGE_FAIL_SEND, + +} Tox_Err_Conference_Send_Message; + + +/** + * @brief Send a text chat message to the conference. + * + * This function creates a conference message packet and pushes it into the send + * queue. + * + * The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages + * must be split by the client and sent as separate messages. Other clients can + * then reassemble the fragments. + * + * @param conference_number The conference number of the conference the message + * is intended for. + * @param type Message type (normal, action, ...). + * @param message A non-NULL pointer to the first element of a byte array + * containing the message text. + * @param length Length of the message to be sent. + * + * @return true on success. + */ +bool tox_conference_send_message(Tox *tox, uint32_t conference_number, Tox_Message_Type type, const uint8_t *message, + size_t length, Tox_Err_Conference_Send_Message *error); + +typedef enum Tox_Err_Conference_Title { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_TITLE_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND, + + /** + * The title is too long or empty. + */ + TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH, + + /** + * The title packet failed to send. + */ + TOX_ERR_CONFERENCE_TITLE_FAIL_SEND, + +} Tox_Err_Conference_Title; + + +/** + * @brief Return the length of the conference title. + * + * Return value is unspecified on failure. + * + * The return value is equal to the `length` argument received by the last + * `conference_title` callback. + */ +size_t tox_conference_get_title_size(const Tox *tox, uint32_t conference_number, Tox_Err_Conference_Title *error); + +/** + * @brief Write the title designated by the given conference number to a byte array. + * + * Call tox_conference_get_title_size to determine the allocation size for the `title` parameter. + * + * The data written to `title` is equal to the data received by the last + * `conference_title` callback. + * + * @param title A valid memory region large enough to store the title. + * If this parameter is NULL, this function has no effect. + * + * @return true on success. + */ +bool tox_conference_get_title(const Tox *tox, uint32_t conference_number, uint8_t *title, + Tox_Err_Conference_Title *error); + +/** + * @brief Set the conference title and broadcast it to the rest of the conference. + * + * Title length cannot be longer than TOX_MAX_NAME_LENGTH. + * + * @return true on success. + */ +bool tox_conference_set_title(Tox *tox, uint32_t conference_number, const uint8_t *title, size_t length, + Tox_Err_Conference_Title *error); + +/** + * @brief Return the number of conferences in the Tox instance. + * + * This should be used to determine how much memory to allocate for `tox_conference_get_chatlist`. + */ +size_t tox_conference_get_chatlist_size(const Tox *tox); + +/** + * @brief Copy a list of valid conference numbers into the array chatlist. + * + * Determine how much space to allocate for the array with the + * `tox_conference_get_chatlist_size` function. + * + * Note that `tox_get_savedata` saves all connected conferences; + * when toxcore is created from savedata in which conferences were saved, those + * conferences will be connected at startup, and will be listed by + * `tox_conference_get_chatlist`. + * + * The conference number of a loaded conference may differ from the conference + * number it had when it was saved. + */ +void tox_conference_get_chatlist(const Tox *tox, uint32_t *chatlist); + +/** + * @brief Returns the type of conference (Tox_Conference_Type) that conference_number is. + * + * Return value is unspecified on failure. + */ +typedef enum Tox_Err_Conference_Get_Type { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_GET_TYPE_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_GET_TYPE_CONFERENCE_NOT_FOUND, + +} Tox_Err_Conference_Get_Type; + + +/** + * @brief Get the type (text or A/V) for the conference. + */ +Tox_Conference_Type tox_conference_get_type(const Tox *tox, uint32_t conference_number, + Tox_Err_Conference_Get_Type *error); + +/** + * @brief Get the conference unique ID. + * + * If id is NULL, this function has no effect. + * + * @param id A memory region large enough to store TOX_CONFERENCE_ID_SIZE bytes. + * + * @return true on success. + */ +bool tox_conference_get_id(const Tox *tox, uint32_t conference_number, uint8_t *id); + +typedef enum Tox_Err_Conference_By_Id { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_BY_ID_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_CONFERENCE_BY_ID_NULL, + + /** + * No conference with the given id exists on the conference list. + */ + TOX_ERR_CONFERENCE_BY_ID_NOT_FOUND, + +} Tox_Err_Conference_By_Id; + + +/** + * @brief Return the conference number associated with the specified id. + * + * @param id A byte array containing the conference id (TOX_CONFERENCE_ID_SIZE). + * + * @return the conference number on success, an unspecified value on failure. + */ +uint32_t tox_conference_by_id(const Tox *tox, const uint8_t *id, Tox_Err_Conference_By_Id *error); + +/** + * @brief Get the conference unique ID. + * + * If uid is NULL, this function has no effect. + * + * @param uid A memory region large enough to store TOX_CONFERENCE_UID_SIZE bytes. + * + * @return true on success. + * @deprecated use tox_conference_get_id instead (exactly the same function, just renamed). + */ +bool tox_conference_get_uid(const Tox *tox, uint32_t conference_number, uint8_t *uid); + +typedef enum Tox_Err_Conference_By_Uid { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_BY_UID_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_CONFERENCE_BY_UID_NULL, + + /** + * No conference with the given uid exists on the conference list. + */ + TOX_ERR_CONFERENCE_BY_UID_NOT_FOUND, + +} Tox_Err_Conference_By_Uid; + + +/** + * @brief Return the conference number associated with the specified uid. + * + * @param uid A byte array containing the conference id (TOX_CONFERENCE_UID_SIZE). + * + * @return the conference number on success, an unspecified value on failure. + * @deprecated use tox_conference_by_id instead (exactly the same function, just renamed). + */ +uint32_t tox_conference_by_uid(const Tox *tox, const uint8_t *uid, Tox_Err_Conference_By_Uid *error); + +/** @} */ + + +/** @{ + * @name Low-level custom packet sending and receiving + */ + +typedef enum Tox_Err_Friend_Custom_Packet { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_NULL, + + /** + * The friend number did not designate a valid friend. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_FOUND, + + /** + * This client is currently not connected to the friend. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_CONNECTED, + + /** + * The first byte of data was not in the specified range for the packet type. + * This range is 192-254 for lossy, and 69, 160-191 for lossless packets. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_INVALID, + + /** + * Attempted to send an empty packet. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_EMPTY, + + /** + * Packet data length exceeded TOX_MAX_CUSTOM_PACKET_SIZE. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_TOO_LONG, + + /** + * Packet queue is full. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_SENDQ, + +} Tox_Err_Friend_Custom_Packet; + + +/** + * @brief Send a custom lossy packet to a friend. + * + * The first byte of data must be in the range 192-254. Maximum length of a + * custom packet is TOX_MAX_CUSTOM_PACKET_SIZE. + * + * Lossy packets behave like UDP packets, meaning they might never reach the + * other side or might arrive more than once (if someone is messing with the + * connection) or might arrive in the wrong order. + * + * Unless latency is an issue, it is recommended that you use lossless custom + * packets instead. + * + * @param friend_number The friend number of the friend this lossy packet + * should be sent to. + * @param data A byte array containing the packet data. + * @param length The length of the packet data byte array. + * + * @return true on success. + */ +bool tox_friend_send_lossy_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + Tox_Err_Friend_Custom_Packet *error); + +/** + * @brief Send a custom lossless packet to a friend. + * + * The first byte of data must be in the range 69, 160-191. Maximum length of a + * custom packet is TOX_MAX_CUSTOM_PACKET_SIZE. + * + * Lossless packet behaviour is comparable to TCP (reliability, arrive in order) + * but with packets instead of a stream. + * + * @param friend_number The friend number of the friend this lossless packet + * should be sent to. + * @param data A byte array containing the packet data. + * @param length The length of the packet data byte array. + * + * @return true on success. + */ +bool tox_friend_send_lossless_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + Tox_Err_Friend_Custom_Packet *error); + +/** + * @param friend_number The friend number of the friend who sent a lossy packet. + * @param data A byte array containing the received packet data. + * @param length The length of the packet data byte array. + */ +typedef void tox_friend_lossy_packet_cb(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + void *user_data); + + +/** + * @brief Set the callback for the `friend_lossy_packet` event. + * + * Pass NULL to unset. + */ +void tox_callback_friend_lossy_packet(Tox *tox, tox_friend_lossy_packet_cb *callback); + +/** + * @param friend_number The friend number of the friend who sent the packet. + * @param data A byte array containing the received packet data. + * @param length The length of the packet data byte array. + */ +typedef void tox_friend_lossless_packet_cb(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + void *user_data); + + +/** + * @brief Set the callback for the `friend_lossless_packet` event. + * + * Pass NULL to unset. + */ +void tox_callback_friend_lossless_packet(Tox *tox, tox_friend_lossless_packet_cb *callback); + +/** @} */ + + +/** @{ + * @name Low-level network information + */ + +typedef enum Tox_Err_Get_Port { + + /** + * The function returned successfully. + */ + TOX_ERR_GET_PORT_OK, + + /** + * The instance was not bound to any port. + */ + TOX_ERR_GET_PORT_NOT_BOUND, + +} Tox_Err_Get_Port; + + +/** + * @brief Writes the temporary DHT public key of this instance to a byte array. + * + * This can be used in combination with an externally accessible IP address and + * the bound port (from tox_self_get_udp_port) to run a temporary bootstrap node. + * + * Be aware that every time a new instance is created, the DHT public key + * changes, meaning this cannot be used to run a permanent bootstrap node. + * + * @param dht_id A memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If this + * parameter is NULL, this function has no effect. + */ +void tox_self_get_dht_id(const Tox *tox, uint8_t *dht_id); + +/** + * @brief Return the UDP port this Tox instance is bound to. + */ +uint16_t tox_self_get_udp_port(const Tox *tox, Tox_Err_Get_Port *error); + +/** + * @brief Return the TCP port this Tox instance is bound to. + * + * This is only relevant if the instance is acting as a TCP relay. + */ +uint16_t tox_self_get_tcp_port(const Tox *tox, Tox_Err_Get_Port *error); + +/** @} */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +//!TOKSTYLE- +#ifndef DOXYGEN_IGNORE + +typedef Tox_Err_Options_New TOX_ERR_OPTIONS_NEW; +typedef Tox_Err_New TOX_ERR_NEW; +typedef Tox_Err_Bootstrap TOX_ERR_BOOTSTRAP; +typedef Tox_Err_Set_Info TOX_ERR_SET_INFO; +typedef Tox_Err_Friend_Add TOX_ERR_FRIEND_ADD; +typedef Tox_Err_Friend_Delete TOX_ERR_FRIEND_DELETE; +typedef Tox_Err_Friend_By_Public_Key TOX_ERR_FRIEND_BY_PUBLIC_KEY; +typedef Tox_Err_Friend_Get_Public_Key TOX_ERR_FRIEND_GET_PUBLIC_KEY; +typedef Tox_Err_Friend_Get_Last_Online TOX_ERR_FRIEND_GET_LAST_ONLINE; +typedef Tox_Err_Friend_Query TOX_ERR_FRIEND_QUERY; +typedef Tox_Err_Set_Typing TOX_ERR_SET_TYPING; +typedef Tox_Err_Friend_Send_Message TOX_ERR_FRIEND_SEND_MESSAGE; +typedef Tox_Err_File_Control TOX_ERR_FILE_CONTROL; +typedef Tox_Err_File_Seek TOX_ERR_FILE_SEEK; +typedef Tox_Err_File_Get TOX_ERR_FILE_GET; +typedef Tox_Err_File_Send TOX_ERR_FILE_SEND; +typedef Tox_Err_File_Send_Chunk TOX_ERR_FILE_SEND_CHUNK; +typedef Tox_Err_Conference_New TOX_ERR_CONFERENCE_NEW; +typedef Tox_Err_Conference_Delete TOX_ERR_CONFERENCE_DELETE; +typedef Tox_Err_Conference_Peer_Query TOX_ERR_CONFERENCE_PEER_QUERY; +typedef Tox_Err_Conference_Set_Max_Offline TOX_ERR_CONFERENCE_SET_MAX_OFFLINE; +typedef Tox_Err_Conference_By_Id TOX_ERR_CONFERENCE_BY_ID; +typedef Tox_Err_Conference_By_Uid TOX_ERR_CONFERENCE_BY_UID; +typedef Tox_Err_Conference_Invite TOX_ERR_CONFERENCE_INVITE; +typedef Tox_Err_Conference_Join TOX_ERR_CONFERENCE_JOIN; +typedef Tox_Err_Conference_Send_Message TOX_ERR_CONFERENCE_SEND_MESSAGE; +typedef Tox_Err_Conference_Title TOX_ERR_CONFERENCE_TITLE; +typedef Tox_Err_Conference_Get_Type TOX_ERR_CONFERENCE_GET_TYPE; +typedef Tox_Err_Friend_Custom_Packet TOX_ERR_FRIEND_CUSTOM_PACKET; +typedef Tox_Err_Get_Port TOX_ERR_GET_PORT; +typedef Tox_User_Status TOX_USER_STATUS; +typedef Tox_Message_Type TOX_MESSAGE_TYPE; +typedef Tox_Proxy_Type TOX_PROXY_TYPE; +typedef Tox_Savedata_Type TOX_SAVEDATA_TYPE; +typedef Tox_Log_Level TOX_LOG_LEVEL; +typedef Tox_Connection TOX_CONNECTION; +typedef Tox_File_Control TOX_FILE_CONTROL; +typedef Tox_Conference_Type TOX_CONFERENCE_TYPE; +typedef enum Tox_File_Kind TOX_FILE_KIND; + +#endif +//!TOKSTYLE+ + +#endif // C_TOXCORE_TOXCORE_TOX_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/tox.m b/local_pod_repo/toxcore/toxcore/toxcore/tox.m new file mode 100644 index 0000000..b37a4dd --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/tox.m @@ -0,0 +1,2607 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * The Tox public API. + */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include "tox.h" + +#include +#include +#include + +#include "Messenger.h" +#include "ccompat.h" +#include "group.h" +#include "logger.h" +#include "mono_time.h" +#include "network.h" +#include "tox_private.h" +#include "tox_struct.h" + +#include "../toxencryptsave/defines.h" + +#define SET_ERROR_PARAMETER(param, x) \ + do { \ + if (param != nullptr) { \ + *param = x; \ + } \ + } while (0) + +static_assert(TOX_HASH_LENGTH == CRYPTO_SHA256_SIZE, + "TOX_HASH_LENGTH is assumed to be equal to CRYPTO_SHA256_SIZE"); +static_assert(FILE_ID_LENGTH == CRYPTO_SYMMETRIC_KEY_SIZE, + "FILE_ID_LENGTH is assumed to be equal to CRYPTO_SYMMETRIC_KEY_SIZE"); +static_assert(TOX_DHT_NODE_IP_STRING_SIZE == IP_NTOA_LEN, + "TOX_DHT_NODE_IP_STRING_SIZE is assumed to be equal to IP_NTOA_LEN"); +static_assert(TOX_DHT_NODE_PUBLIC_KEY_SIZE == CRYPTO_PUBLIC_KEY_SIZE, + "TOX_DHT_NODE_PUBLIC_KEY_SIZE is assumed to be equal to CRYPTO_PUBLIC_KEY_SIZE"); +static_assert(TOX_FILE_ID_LENGTH == CRYPTO_SYMMETRIC_KEY_SIZE, + "TOX_FILE_ID_LENGTH is assumed to be equal to CRYPTO_SYMMETRIC_KEY_SIZE"); +static_assert(TOX_FILE_ID_LENGTH == TOX_HASH_LENGTH, + "TOX_FILE_ID_LENGTH is assumed to be equal to TOX_HASH_LENGTH"); +static_assert(TOX_PUBLIC_KEY_SIZE == CRYPTO_PUBLIC_KEY_SIZE, + "TOX_PUBLIC_KEY_SIZE is assumed to be equal to CRYPTO_PUBLIC_KEY_SIZE"); +static_assert(TOX_SECRET_KEY_SIZE == CRYPTO_SECRET_KEY_SIZE, + "TOX_SECRET_KEY_SIZE is assumed to be equal to CRYPTO_SECRET_KEY_SIZE"); +static_assert(TOX_MAX_NAME_LENGTH == MAX_NAME_LENGTH, + "TOX_MAX_NAME_LENGTH is assumed to be equal to MAX_NAME_LENGTH"); +static_assert(TOX_MAX_STATUS_MESSAGE_LENGTH == MAX_STATUSMESSAGE_LENGTH, + "TOX_MAX_STATUS_MESSAGE_LENGTH is assumed to be equal to MAX_STATUSMESSAGE_LENGTH"); + +struct Tox_Userdata { + Tox *tox; + void *user_data; +}; + +static logger_cb tox_log_handler; +non_null(1, 3, 5, 6) nullable(7) +static void tox_log_handler(void *context, Logger_Level level, const char *file, int line, const char *func, + const char *message, void *userdata) +{ + Tox *tox = (Tox *)context; + assert(tox != nullptr); + + if (tox->log_callback != nullptr) { + tox->log_callback(tox, (Tox_Log_Level)level, file, line, func, message, userdata); + } +} + +static m_self_connection_status_cb tox_self_connection_status_handler; +non_null(1) nullable(3) +static void tox_self_connection_status_handler(Messenger *m, Onion_Connection_Status connection_status, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->self_connection_status_callback != nullptr) { + tox_data->tox->self_connection_status_callback(tox_data->tox, (Tox_Connection)connection_status, tox_data->user_data); + } +} + +static m_friend_name_cb tox_friend_name_handler; +non_null(1, 3) nullable(5) +static void tox_friend_name_handler(Messenger *m, uint32_t friend_number, const uint8_t *name, size_t length, + void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->friend_name_callback != nullptr) { + tox_data->tox->friend_name_callback(tox_data->tox, friend_number, name, length, tox_data->user_data); + } +} + +static m_friend_status_message_cb tox_friend_status_message_handler; +non_null(1, 3) nullable(5) +static void tox_friend_status_message_handler(Messenger *m, uint32_t friend_number, const uint8_t *message, + size_t length, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->friend_status_message_callback != nullptr) { + tox_data->tox->friend_status_message_callback(tox_data->tox, friend_number, message, length, tox_data->user_data); + } +} + +static m_friend_status_cb tox_friend_status_handler; +non_null(1) nullable(4) +static void tox_friend_status_handler(Messenger *m, uint32_t friend_number, unsigned int status, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->friend_status_callback != nullptr) { + tox_data->tox->friend_status_callback(tox_data->tox, friend_number, (Tox_User_Status)status, tox_data->user_data); + } +} + +static m_friend_connection_status_cb tox_friend_connection_status_handler; +non_null(1) nullable(4) +static void tox_friend_connection_status_handler(Messenger *m, uint32_t friend_number, unsigned int connection_status, + void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->friend_connection_status_callback != nullptr) { + tox_data->tox->friend_connection_status_callback(tox_data->tox, friend_number, (Tox_Connection)connection_status, + tox_data->user_data); + } +} + +static m_friend_typing_cb tox_friend_typing_handler; +non_null(1) nullable(4) +static void tox_friend_typing_handler(Messenger *m, uint32_t friend_number, bool is_typing, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->friend_typing_callback != nullptr) { + tox_data->tox->friend_typing_callback(tox_data->tox, friend_number, is_typing, tox_data->user_data); + } +} + +static m_friend_read_receipt_cb tox_friend_read_receipt_handler; +non_null(1) nullable(4) +static void tox_friend_read_receipt_handler(Messenger *m, uint32_t friend_number, uint32_t message_id, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->friend_read_receipt_callback != nullptr) { + tox_data->tox->friend_read_receipt_callback(tox_data->tox, friend_number, message_id, tox_data->user_data); + } +} + +static m_friend_request_cb tox_friend_request_handler; +non_null(1, 2, 3) nullable(5) +static void tox_friend_request_handler(Messenger *m, const uint8_t *public_key, const uint8_t *message, size_t length, + void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->friend_request_callback != nullptr) { + tox_data->tox->friend_request_callback(tox_data->tox, public_key, message, length, tox_data->user_data); + } +} + +static m_friend_message_cb tox_friend_message_handler; +non_null(1, 4) nullable(6) +static void tox_friend_message_handler(Messenger *m, uint32_t friend_number, unsigned int message_type, + const uint8_t *message, size_t length, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->friend_message_callback != nullptr) { + tox_data->tox->friend_message_callback(tox_data->tox, friend_number, (Tox_Message_Type)message_type, message, length, + tox_data->user_data); + } +} + +static m_file_recv_control_cb tox_file_recv_control_handler; +non_null(1) nullable(5) +static void tox_file_recv_control_handler(Messenger *m, uint32_t friend_number, uint32_t file_number, + unsigned int control, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->file_recv_control_callback != nullptr) { + tox_data->tox->file_recv_control_callback(tox_data->tox, friend_number, file_number, (Tox_File_Control)control, + tox_data->user_data); + } +} + +static m_file_chunk_request_cb tox_file_chunk_request_handler; +non_null(1) nullable(6) +static void tox_file_chunk_request_handler(Messenger *m, uint32_t friend_number, uint32_t file_number, + uint64_t position, size_t length, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->file_chunk_request_callback != nullptr) { + tox_data->tox->file_chunk_request_callback(tox_data->tox, friend_number, file_number, position, length, + tox_data->user_data); + } +} + +static m_file_recv_cb tox_file_recv_handler; +non_null(1, 6) nullable(8) +static void tox_file_recv_handler(Messenger *m, uint32_t friend_number, uint32_t file_number, uint32_t kind, + uint64_t file_size, const uint8_t *filename, size_t filename_length, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->file_recv_callback != nullptr) { + tox_data->tox->file_recv_callback(tox_data->tox, friend_number, file_number, kind, file_size, filename, filename_length, + tox_data->user_data); + } +} + +static m_file_recv_chunk_cb tox_file_recv_chunk_handler; +non_null(1, 5) nullable(7) +static void tox_file_recv_chunk_handler(Messenger *m, uint32_t friend_number, uint32_t file_number, uint64_t position, + const uint8_t *data, size_t length, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->file_recv_chunk_callback != nullptr) { + tox_data->tox->file_recv_chunk_callback(tox_data->tox, friend_number, file_number, position, data, length, + tox_data->user_data); + } +} + +static g_conference_invite_cb tox_conference_invite_handler; +non_null(1, 4) nullable(6) +static void tox_conference_invite_handler(Messenger *m, uint32_t friend_number, int type, const uint8_t *cookie, + size_t length, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->conference_invite_callback != nullptr) { + tox_data->tox->conference_invite_callback(tox_data->tox, friend_number, (Tox_Conference_Type)type, cookie, length, + tox_data->user_data); + } +} + +static g_conference_connected_cb tox_conference_connected_handler; +non_null(1) nullable(3) +static void tox_conference_connected_handler(Messenger *m, uint32_t conference_number, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->conference_connected_callback != nullptr) { + tox_data->tox->conference_connected_callback(tox_data->tox, conference_number, tox_data->user_data); + } +} + +static g_conference_message_cb tox_conference_message_handler; +non_null(1, 5) nullable(7) +static void tox_conference_message_handler(Messenger *m, uint32_t conference_number, uint32_t peer_number, int type, + const uint8_t *message, size_t length, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->conference_message_callback != nullptr) { + tox_data->tox->conference_message_callback(tox_data->tox, conference_number, peer_number, (Tox_Message_Type)type, + message, length, tox_data->user_data); + } +} + +static title_cb tox_conference_title_handler; +non_null(1, 4) nullable(6) +static void tox_conference_title_handler(Messenger *m, uint32_t conference_number, uint32_t peer_number, + const uint8_t *title, size_t length, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->conference_title_callback != nullptr) { + tox_data->tox->conference_title_callback(tox_data->tox, conference_number, peer_number, title, length, + tox_data->user_data); + } +} + +static peer_name_cb tox_conference_peer_name_handler; +non_null(1, 4) nullable(6) +static void tox_conference_peer_name_handler(Messenger *m, uint32_t conference_number, uint32_t peer_number, + const uint8_t *name, size_t length, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->conference_peer_name_callback != nullptr) { + tox_data->tox->conference_peer_name_callback(tox_data->tox, conference_number, peer_number, name, length, + tox_data->user_data); + } +} + +static peer_list_changed_cb tox_conference_peer_list_changed_handler; +non_null(1) nullable(3) +static void tox_conference_peer_list_changed_handler(Messenger *m, uint32_t conference_number, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->conference_peer_list_changed_callback != nullptr) { + tox_data->tox->conference_peer_list_changed_callback(tox_data->tox, conference_number, tox_data->user_data); + } +} + +static dht_get_nodes_response_cb tox_dht_get_nodes_response_handler; +non_null(1, 2) nullable(3) +static void tox_dht_get_nodes_response_handler(const DHT *dht, const Node_format *node, void *user_data) +{ + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->dht_get_nodes_response_callback == nullptr) { + return; + } + + Ip_Ntoa ip_str; + tox_data->tox->dht_get_nodes_response_callback( + tox_data->tox, node->public_key, net_ip_ntoa(&node->ip_port.ip, &ip_str), net_ntohs(node->ip_port.port), + tox_data->user_data); +} + +static m_friend_lossy_packet_cb tox_friend_lossy_packet_handler; +non_null(1, 4) nullable(6) +static void tox_friend_lossy_packet_handler(Messenger *m, uint32_t friend_number, uint8_t packet_id, + const uint8_t *data, size_t length, void *user_data) +{ + assert(data != nullptr); + assert(length > 0); + + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->friend_lossy_packet_callback_per_pktid[packet_id] != nullptr) { + tox_data->tox->friend_lossy_packet_callback_per_pktid[packet_id](tox_data->tox, friend_number, data, length, + tox_data->user_data); + } +} + +static m_friend_lossless_packet_cb tox_friend_lossless_packet_handler; +non_null(1, 4) nullable(6) +static void tox_friend_lossless_packet_handler(Messenger *m, uint32_t friend_number, uint8_t packet_id, + const uint8_t *data, size_t length, void *user_data) +{ + assert(data != nullptr); + assert(length > 0); + + struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; + + if (tox_data->tox->friend_lossless_packet_callback_per_pktid[packet_id] != nullptr) { + tox_data->tox->friend_lossless_packet_callback_per_pktid[packet_id](tox_data->tox, friend_number, data, length, + tox_data->user_data); + } +} + + +bool tox_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch) +{ + return TOX_VERSION_IS_API_COMPATIBLE(major, minor, patch); +} + +non_null() +static State_Load_Status state_load_callback(void *outer, const uint8_t *data, uint32_t length, uint16_t type) +{ + const Tox *tox = (const Tox *)outer; + State_Load_Status status = STATE_LOAD_STATUS_CONTINUE; + + if (messenger_load_state_section(tox->m, data, length, type, &status) + || conferences_load_state_section(tox->m->conferences_object, data, length, type, &status)) { + return status; + } + + if (type == STATE_TYPE_END) { + if (length != 0) { + return STATE_LOAD_STATUS_ERROR; + } + + return STATE_LOAD_STATUS_END; + } + + LOGGER_ERROR(tox->m->log, "Load state: contains unrecognized part (len %u, type %u)", + length, type); + + return STATE_LOAD_STATUS_CONTINUE; +} + +/** Load tox from data of size length. */ +non_null() +static int tox_load(Tox *tox, const uint8_t *data, uint32_t length) +{ + uint32_t data32[2]; + const uint32_t cookie_len = sizeof(data32); + + if (length < cookie_len) { + return -1; + } + + memcpy(data32, data, sizeof(uint32_t)); + lendian_bytes_to_host32(data32 + 1, data + sizeof(uint32_t)); + + if (data32[0] != 0 || data32[1] != STATE_COOKIE_GLOBAL) { + return -1; + } + + return state_load(tox->m->log, state_load_callback, tox, data + cookie_len, + length - cookie_len, STATE_COOKIE_TYPE); +} + +Tox *tox_new(const struct Tox_Options *options, Tox_Err_New *error) +{ + Tox *tox = (Tox *)calloc(1, sizeof(Tox)); + + if (tox == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); + return nullptr; + } + + Messenger_Options m_options = {0}; + + bool load_savedata_sk = false; + bool load_savedata_tox = false; + + struct Tox_Options *default_options = nullptr; + + if (options == nullptr) { + Tox_Err_Options_New err; + default_options = tox_options_new(&err); + + switch (err) { + case TOX_ERR_OPTIONS_NEW_OK: { + break; + } + + case TOX_ERR_OPTIONS_NEW_MALLOC: { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); + free(tox); + return nullptr; + } + } + } + + const struct Tox_Options *const opts = options != nullptr ? options : default_options; + assert(opts != nullptr); + + if (tox_options_get_savedata_type(opts) != TOX_SAVEDATA_TYPE_NONE) { + if (tox_options_get_savedata_data(opts) == nullptr || tox_options_get_savedata_length(opts) == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); + tox_options_free(default_options); + free(tox); + return nullptr; + } + } + + if (tox_options_get_savedata_type(opts) == TOX_SAVEDATA_TYPE_SECRET_KEY) { + if (tox_options_get_savedata_length(opts) != TOX_SECRET_KEY_SIZE) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); + tox_options_free(default_options); + free(tox); + return nullptr; + } + + load_savedata_sk = true; + } else if (tox_options_get_savedata_type(opts) == TOX_SAVEDATA_TYPE_TOX_SAVE) { + if (tox_options_get_savedata_length(opts) < TOX_ENC_SAVE_MAGIC_LENGTH) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); + tox_options_free(default_options); + free(tox); + return nullptr; + } + + if (memcmp(tox_options_get_savedata_data(opts), TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_ENCRYPTED); + tox_options_free(default_options); + free(tox); + return nullptr; + } + + load_savedata_tox = true; + } + + m_options.ipv6enabled = tox_options_get_ipv6_enabled(opts); + m_options.udp_disabled = !tox_options_get_udp_enabled(opts); + m_options.port_range[0] = tox_options_get_start_port(opts); + m_options.port_range[1] = tox_options_get_end_port(opts); + m_options.tcp_server_port = tox_options_get_tcp_port(opts); + m_options.hole_punching_enabled = tox_options_get_hole_punching_enabled(opts); + m_options.local_discovery_enabled = tox_options_get_local_discovery_enabled(opts); + m_options.dht_announcements_enabled = tox_options_get_dht_announcements_enabled(opts); + + if (m_options.udp_disabled) { + m_options.local_discovery_enabled = false; + } + + tox->log_callback = tox_options_get_log_callback(opts); + m_options.log_callback = tox_log_handler; + m_options.log_context = tox; + m_options.log_user_data = tox_options_get_log_user_data(opts); + + switch (tox_options_get_proxy_type(opts)) { + case TOX_PROXY_TYPE_HTTP: { + m_options.proxy_info.proxy_type = TCP_PROXY_HTTP; + break; + } + + case TOX_PROXY_TYPE_SOCKS5: { + m_options.proxy_info.proxy_type = TCP_PROXY_SOCKS5; + break; + } + + case TOX_PROXY_TYPE_NONE: { + m_options.proxy_info.proxy_type = TCP_PROXY_NONE; + break; + } + + default: { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_TYPE); + tox_options_free(default_options); + free(tox); + return nullptr; + } + } + + const Tox_System *sys = tox_options_get_operating_system(opts); + const Tox_System default_system = tox_default_system(); + if (sys == nullptr) { + sys = &default_system; + } + + if (sys->rng == nullptr || sys->ns == nullptr) { + // TODO(iphydf): Not quite right, but similar. + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); + tox_options_free(default_options); + free(tox); + return nullptr; + } + + tox->rng = *sys->rng; + tox->ns = *sys->ns; + + if (m_options.proxy_info.proxy_type != TCP_PROXY_NONE) { + if (tox_options_get_proxy_port(opts) == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_PORT); + tox_options_free(default_options); + free(tox); + return nullptr; + } + + ip_init(&m_options.proxy_info.ip_port.ip, m_options.ipv6enabled); + + if (m_options.ipv6enabled) { + m_options.proxy_info.ip_port.ip.family = net_family_unspec(); + } + + const char *const proxy_host = tox_options_get_proxy_host(opts); + + if (proxy_host == nullptr || !addr_resolve_or_parse_ip(&tox->ns, proxy_host, &m_options.proxy_info.ip_port.ip, nullptr)) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_HOST); + // TODO(irungentoo): TOX_ERR_NEW_PROXY_NOT_FOUND if domain. + tox_options_free(default_options); + free(tox); + return nullptr; + } + + m_options.proxy_info.ip_port.port = net_htons(tox_options_get_proxy_port(opts)); + } + + tox->mono_time = mono_time_new(sys->mono_time_callback, sys->mono_time_user_data); + + if (tox->mono_time == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); + tox_options_free(default_options); + free(tox); + return nullptr; + } + + if (tox_options_get_experimental_thread_safety(opts)) { + tox->mutex = (pthread_mutex_t *)calloc(1, sizeof(pthread_mutex_t)); + + if (tox->mutex == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); + tox_options_free(default_options); + free(tox); + return nullptr; + } + + + pthread_mutexattr_t attr; + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(tox->mutex, &attr); + } else { + tox->mutex = nullptr; + } + + tox_lock(tox); + + Messenger_Error m_error; + tox->m = new_messenger(tox->mono_time, &tox->rng, &tox->ns, &m_options, &m_error); + + if (tox->m == nullptr) { + if (m_error == MESSENGER_ERROR_PORT) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PORT_ALLOC); + } else if (m_error == MESSENGER_ERROR_TCP_SERVER) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PORT_ALLOC); + } else { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); + } + + mono_time_free(tox->mono_time); + tox_options_free(default_options); + tox_unlock(tox); + + if (tox->mutex != nullptr) { + pthread_mutex_destroy(tox->mutex); + } + + free(tox->mutex); + free(tox); + return nullptr; + } + + if (new_groupchats(tox->mono_time, tox->m) == nullptr) { + kill_messenger(tox->m); + + mono_time_free(tox->mono_time); + tox_options_free(default_options); + tox_unlock(tox); + + if (tox->mutex != nullptr) { + pthread_mutex_destroy(tox->mutex); + } + + free(tox->mutex); + free(tox); + + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); + return nullptr; + } + + if (load_savedata_tox + && tox_load(tox, tox_options_get_savedata_data(opts), tox_options_get_savedata_length(opts)) == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); + } else if (load_savedata_sk) { + load_secret_key(tox->m->net_crypto, tox_options_get_savedata_data(opts)); + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_OK); + } else { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_OK); + } + + m_callback_namechange(tox->m, tox_friend_name_handler); + m_callback_core_connection(tox->m, tox_self_connection_status_handler); + m_callback_statusmessage(tox->m, tox_friend_status_message_handler); + m_callback_userstatus(tox->m, tox_friend_status_handler); + m_callback_connectionstatus(tox->m, tox_friend_connection_status_handler); + m_callback_typingchange(tox->m, tox_friend_typing_handler); + m_callback_read_receipt(tox->m, tox_friend_read_receipt_handler); + m_callback_friendrequest(tox->m, tox_friend_request_handler); + m_callback_friendmessage(tox->m, tox_friend_message_handler); + callback_file_control(tox->m, tox_file_recv_control_handler); + callback_file_reqchunk(tox->m, tox_file_chunk_request_handler); + callback_file_sendrequest(tox->m, tox_file_recv_handler); + callback_file_data(tox->m, tox_file_recv_chunk_handler); + dht_callback_get_nodes_response(tox->m->dht, tox_dht_get_nodes_response_handler); + g_callback_group_invite(tox->m->conferences_object, tox_conference_invite_handler); + g_callback_group_connected(tox->m->conferences_object, tox_conference_connected_handler); + g_callback_group_message(tox->m->conferences_object, tox_conference_message_handler); + g_callback_group_title(tox->m->conferences_object, tox_conference_title_handler); + g_callback_peer_name(tox->m->conferences_object, tox_conference_peer_name_handler); + g_callback_peer_list_changed(tox->m->conferences_object, tox_conference_peer_list_changed_handler); + custom_lossy_packet_registerhandler(tox->m, tox_friend_lossy_packet_handler); + custom_lossless_packet_registerhandler(tox->m, tox_friend_lossless_packet_handler); + + tox_options_free(default_options); + + tox_unlock(tox); + return tox; +} + +void tox_kill(Tox *tox) +{ + if (tox == nullptr) { + return; + } + + tox_lock(tox); + LOGGER_ASSERT(tox->m->log, tox->m->msi_packet == nullptr, "Attempted to kill tox while toxav is still alive"); + kill_groupchats(tox->m->conferences_object); + kill_messenger(tox->m); + mono_time_free(tox->mono_time); + tox_unlock(tox); + + if (tox->mutex != nullptr) { + pthread_mutex_destroy(tox->mutex); + free(tox->mutex); + } + + free(tox); +} + +static uint32_t end_size(void) +{ + return 2 * sizeof(uint32_t); +} + +non_null() +static void end_save(uint8_t *data) +{ + state_write_section_header(data, STATE_COOKIE_TYPE, 0, STATE_TYPE_END); +} + +size_t tox_get_savedata_size(const Tox *tox) +{ + assert(tox != nullptr); + tox_lock(tox); + const size_t ret = 2 * sizeof(uint32_t) + + messenger_size(tox->m) + + conferences_size(tox->m->conferences_object) + + end_size(); + tox_unlock(tox); + return ret; +} + +void tox_get_savedata(const Tox *tox, uint8_t *savedata) +{ + assert(tox != nullptr); + + if (savedata == nullptr) { + return; + } + + memset(savedata, 0, tox_get_savedata_size(tox)); + + tox_lock(tox); + + const uint32_t size32 = sizeof(uint32_t); + + // write cookie + memset(savedata, 0, size32); + savedata += size32; + host_to_lendian_bytes32(savedata, STATE_COOKIE_GLOBAL); + savedata += size32; + + savedata = messenger_save(tox->m, savedata); + savedata = conferences_save(tox->m->conferences_object, savedata); + end_save(savedata); + + tox_unlock(tox); +} + +non_null(5) nullable(1, 2, 4, 6) +static int32_t resolve_bootstrap_node(Tox *tox, const char *host, uint16_t port, const uint8_t *public_key, IP_Port **root, Tox_Err_Bootstrap *error) +{ + assert(tox != nullptr); + assert(root != nullptr); + + if (host == nullptr || public_key == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_NULL); + return -1; + } + + if (port == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_PORT); + return -1; + } + + const int32_t count = net_getipport(host, root, TOX_SOCK_DGRAM); + + if (count == -1) { + LOGGER_DEBUG(tox->m->log, "could not resolve bootstrap node '%s'", host); + net_freeipport(*root); + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); + return -1; + } + + assert(*root != nullptr); + return count; +} + +bool tox_bootstrap(Tox *tox, const char *host, uint16_t port, const uint8_t *public_key, Tox_Err_Bootstrap *error) +{ + IP_Port *root; + const int32_t count = resolve_bootstrap_node(tox, host, port, public_key, &root, error); + + if (count == -1) { + return false; + } + + tox_lock(tox); + assert(count >= 0); + bool onion_success = false; + // UDP bootstrap is default success if it's disabled (because we don't even try). + bool udp_success = tox->m->options.udp_disabled; + + for (int32_t i = 0; i < count; ++i) { + root[i].port = net_htons(port); + + if (onion_add_bs_path_node(tox->m->onion_c, &root[i], public_key)) { + // If UDP is enabled, the caller cares about whether any of the + // bootstrap calls below will succeed. In TCP-only mode, adding + // onion path nodes successfully is sufficient. + onion_success = true; + } + + if (!tox->m->options.udp_disabled) { + if (dht_bootstrap(tox->m->dht, &root[i], public_key)) { + // If any of the bootstrap calls worked, we call it success. + udp_success = true; + } + } + } + + tox_unlock(tox); + + net_freeipport(root); + + if (count == 0 || !onion_success || !udp_success) { + LOGGER_DEBUG(tox->m->log, "bootstrap node '%s' resolved to %d IP_Ports%s (onion: %s, UDP: %s)", + host, count, + count > 0 ? ", but failed to bootstrap with any of them" : "", + onion_success ? "success" : "FAILURE", + tox->m->options.udp_disabled ? "disabled" : (udp_success ? "success" : "FAILURE")); + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_OK); + return true; +} + +bool tox_add_tcp_relay(Tox *tox, const char *host, uint16_t port, const uint8_t *public_key, + Tox_Err_Bootstrap *error) +{ + IP_Port *root; + const int32_t count = resolve_bootstrap_node(tox, host, port, public_key, &root, error); + + if (count == -1) { + return false; + } + + tox_lock(tox); + assert(count >= 0); + + for (int32_t i = 0; i < count; ++i) { + root[i].port = net_htons(port); + + add_tcp_relay(tox->m->net_crypto, &root[i], public_key); + } + + tox_unlock(tox); + + net_freeipport(root); + + if (count == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_OK); + return true; +} + +Tox_Connection tox_self_get_connection_status(const Tox *tox) +{ + assert(tox != nullptr); + tox_lock(tox); + const Onion_Connection_Status ret = onion_connection_status(tox->m->onion_c); + tox_unlock(tox); + + switch (ret) { + case ONION_CONNECTION_STATUS_NONE: + return TOX_CONNECTION_NONE; + + case ONION_CONNECTION_STATUS_TCP: + return TOX_CONNECTION_TCP; + + case ONION_CONNECTION_STATUS_UDP: + return TOX_CONNECTION_UDP; + } + + LOGGER_FATAL(tox->m->log, "impossible return value: %d", ret); +} + + +void tox_callback_self_connection_status(Tox *tox, tox_self_connection_status_cb *callback) +{ + assert(tox != nullptr); + tox->self_connection_status_callback = callback; +} + +uint32_t tox_iteration_interval(const Tox *tox) +{ + assert(tox != nullptr); + tox_lock(tox); + uint32_t ret = messenger_run_interval(tox->m); + + if (m_is_receiving_file(tox->m)) { + ret = 1; + } + + tox_unlock(tox); + return ret; +} + +void tox_iterate(Tox *tox, void *user_data) +{ + assert(tox != nullptr); + tox_lock(tox); + + mono_time_update(tox->mono_time); + + struct Tox_Userdata tox_data = { tox, user_data }; + do_messenger(tox->m, &tox_data); + do_groupchats(tox->m->conferences_object, &tox_data); + + tox_unlock(tox); +} + +void tox_self_get_address(const Tox *tox, uint8_t *address) +{ + assert(tox != nullptr); + + if (address != nullptr) { + tox_lock(tox); + getaddress(tox->m, address); + tox_unlock(tox); + } +} + +void tox_self_set_nospam(Tox *tox, uint32_t nospam) +{ + assert(tox != nullptr); + tox_lock(tox); + set_nospam(tox->m->fr, net_htonl(nospam)); + tox_unlock(tox); +} + +uint32_t tox_self_get_nospam(const Tox *tox) +{ + assert(tox != nullptr); + tox_lock(tox); + const uint32_t ret = net_ntohl(get_nospam(tox->m->fr)); + tox_unlock(tox); + return ret; +} + +void tox_self_get_public_key(const Tox *tox, uint8_t *public_key) +{ + assert(tox != nullptr); + + if (public_key != nullptr) { + tox_lock(tox); + memcpy(public_key, nc_get_self_public_key(tox->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE); + tox_unlock(tox); + } +} + +void tox_self_get_secret_key(const Tox *tox, uint8_t *secret_key) +{ + assert(tox != nullptr); + + if (secret_key != nullptr) { + tox_lock(tox); + memcpy(secret_key, nc_get_self_secret_key(tox->m->net_crypto), CRYPTO_SECRET_KEY_SIZE); + tox_unlock(tox); + } +} + +bool tox_self_set_name(Tox *tox, const uint8_t *name, size_t length, Tox_Err_Set_Info *error) +{ + assert(tox != nullptr); + + if (name == nullptr && length != 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_NULL); + return false; + } + + tox_lock(tox); + + if (setname(tox->m, name, length) == 0) { + // TODO(irungentoo): function to set different per group names? + send_name_all_groups(tox->m->conferences_object); + SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_OK); + tox_unlock(tox); + return true; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_TOO_LONG); + tox_unlock(tox); + return false; +} + +bool tox_messagev3_get_new_message_id(uint8_t *msg_id) +{ + if (msg_id == nullptr) { + return false; + } + + /* Tox keys are 32 bytes like TOX_MSGV3_MSGID_LENGTH. */ + new_symmetric_key_implicit_random(msg_id); + return true; +} + +size_t tox_self_get_name_size(const Tox *tox) +{ + assert(tox != nullptr); + tox_lock(tox); + const size_t ret = m_get_self_name_size(tox->m); + tox_unlock(tox); + return ret; +} + +void tox_self_get_name(const Tox *tox, uint8_t *name) +{ + assert(tox != nullptr); + + if (name != nullptr) { + tox_lock(tox); + getself_name(tox->m, name); + tox_unlock(tox); + } +} + +bool tox_self_set_status_message(Tox *tox, const uint8_t *status_message, size_t length, Tox_Err_Set_Info *error) +{ + assert(tox != nullptr); + + if (status_message == nullptr && length != 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_NULL); + return false; + } + + tox_lock(tox); + + if (m_set_statusmessage(tox->m, status_message, length) == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_OK); + tox_unlock(tox); + return true; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_TOO_LONG); + tox_unlock(tox); + return false; +} + +size_t tox_self_get_status_message_size(const Tox *tox) +{ + assert(tox != nullptr); + tox_lock(tox); + const size_t ret = m_get_self_statusmessage_size(tox->m); + tox_unlock(tox); + return ret; +} + +void tox_self_get_status_message(const Tox *tox, uint8_t *status_message) +{ + assert(tox != nullptr); + + if (status_message != nullptr) { + tox_lock(tox); + m_copy_self_statusmessage(tox->m, status_message); + tox_unlock(tox); + } +} + +void tox_self_set_status(Tox *tox, Tox_User_Status status) +{ + assert(tox != nullptr); + tox_lock(tox); + m_set_userstatus(tox->m, status); + tox_unlock(tox); +} + +Tox_User_Status tox_self_get_status(const Tox *tox) +{ + assert(tox != nullptr); + tox_lock(tox); + const uint8_t status = m_get_self_userstatus(tox->m); + tox_unlock(tox); + return (Tox_User_Status)status; +} + +non_null(1) nullable(3) +static void set_friend_error(const Logger *log, int32_t ret, Tox_Err_Friend_Add *error) +{ + switch (ret) { + case FAERR_TOOLONG: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_TOO_LONG); + break; + } + + case FAERR_NOMESSAGE: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_NO_MESSAGE); + break; + } + + case FAERR_OWNKEY: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_OWN_KEY); + break; + } + + case FAERR_ALREADYSENT: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_ALREADY_SENT); + break; + } + + case FAERR_BADCHECKSUM: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_BAD_CHECKSUM); + break; + } + + case FAERR_SETNEWNOSPAM: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM); + break; + } + + case FAERR_NOMEM: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_MALLOC); + break; + } + + default: { + /* can't happen */ + LOGGER_FATAL(log, "impossible return value: %d", ret); + break; + } + } +} + +uint32_t tox_friend_add(Tox *tox, const uint8_t *address, const uint8_t *message, size_t length, + Tox_Err_Friend_Add *error) +{ + assert(tox != nullptr); + + if (address == nullptr || message == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_NULL); + return UINT32_MAX; + } + + tox_lock(tox); + const int32_t ret = m_addfriend(tox->m, address, message, length); + + if (ret >= 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_OK); + tox_unlock(tox); + return (uint32_t)ret; + } + + set_friend_error(tox->m->log, ret, error); + tox_unlock(tox); + return UINT32_MAX; +} + +uint32_t tox_friend_add_norequest(Tox *tox, const uint8_t *public_key, Tox_Err_Friend_Add *error) +{ + assert(tox != nullptr); + + if (public_key == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_NULL); + return UINT32_MAX; + } + + tox_lock(tox); + const int32_t ret = m_addfriend_norequest(tox->m, public_key); + + if (ret >= 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_OK); + tox_unlock(tox); + return (uint32_t)ret; + } + + set_friend_error(tox->m->log, ret, error); + tox_unlock(tox); + return UINT32_MAX; +} + +bool tox_friend_delete(Tox *tox, uint32_t friend_number, Tox_Err_Friend_Delete *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = m_delfriend(tox->m, friend_number); + tox_unlock(tox); + + // TODO(irungentoo): handle if realloc fails? + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_DELETE_OK); + return true; +} + +uint32_t tox_friend_by_public_key(const Tox *tox, const uint8_t *public_key, Tox_Err_Friend_By_Public_Key *error) +{ + assert(tox != nullptr); + + if (public_key == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL); + return UINT32_MAX; + } + + tox_lock(tox); + const int32_t ret = getfriend_id(tox->m, public_key); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_BY_PUBLIC_KEY_NOT_FOUND); + return UINT32_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK); + assert(ret >= 0); + return (uint32_t)ret; +} + +bool tox_friend_get_public_key(const Tox *tox, uint32_t friend_number, uint8_t *public_key, + Tox_Err_Friend_Get_Public_Key *error) +{ + assert(tox != nullptr); + + if (public_key == nullptr) { + return false; + } + + tox_lock(tox); + + if (get_real_pk(tox->m, friend_number, public_key) == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND); + tox_unlock(tox); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK); + tox_unlock(tox); + return true; +} + +bool tox_friend_exists(const Tox *tox, uint32_t friend_number) +{ + assert(tox != nullptr); + tox_lock(tox); + const bool ret = m_friend_exists(tox->m, friend_number); + tox_unlock(tox); + return ret; +} + +uint64_t tox_friend_get_last_online(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Get_Last_Online *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const uint64_t timestamp = m_get_last_online(tox->m, friend_number); + tox_unlock(tox); + + if (timestamp == UINT64_MAX) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND); + return UINT64_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_LAST_ONLINE_OK); + return timestamp; +} + +uint64_t tox_friend_get_capabilities(const Tox *tox, uint32_t friend_number) +{ + tox_lock(tox); + const uint64_t capabilities = m_get_friend_toxcore_capabilities(tox->m, friend_number); + tox_unlock(tox); + + return capabilities; +} + +uint64_t tox_self_get_capabilities(void) +{ + return (TOX_CAPABILITIES_CURRENT); +} + +size_t tox_self_get_friend_list_size(const Tox *tox) +{ + assert(tox != nullptr); + tox_lock(tox); + const size_t ret = count_friendlist(tox->m); + tox_unlock(tox); + return ret; +} + +void tox_self_get_friend_list(const Tox *tox, uint32_t *friend_list) +{ + assert(tox != nullptr); + + if (friend_list != nullptr) { + tox_lock(tox); + // TODO(irungentoo): size parameter? + copy_friendlist(tox->m, friend_list, count_friendlist(tox->m)); + tox_unlock(tox); + } +} + +size_t tox_friend_get_name_size(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = m_get_name_size(tox->m, friend_number); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return SIZE_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return ret; +} + +bool tox_friend_get_name(const Tox *tox, uint32_t friend_number, uint8_t *name, Tox_Err_Friend_Query *error) +{ + assert(tox != nullptr); + + if (name == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_NULL); + return false; + } + + tox_lock(tox); + const int ret = getname(tox->m, friend_number, name); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return true; +} + +void tox_callback_friend_name(Tox *tox, tox_friend_name_cb *callback) +{ + assert(tox != nullptr); + tox->friend_name_callback = callback; +} + +size_t tox_friend_get_status_message_size(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = m_get_statusmessage_size(tox->m, friend_number); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return SIZE_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return ret; +} + +bool tox_friend_get_status_message(const Tox *tox, uint32_t friend_number, uint8_t *status_message, + Tox_Err_Friend_Query *error) +{ + assert(tox != nullptr); + + if (status_message == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_NULL); + return false; + } + + tox_lock(tox); + const int size = m_get_statusmessage_size(tox->m, friend_number); + + if (size == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + tox_unlock(tox); + return false; + } + + const int ret = m_copy_statusmessage(tox->m, friend_number, status_message, size); + LOGGER_ASSERT(tox->m->log, ret == size, "concurrency problem: friend status message changed"); + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + tox_unlock(tox); + return ret == size; +} + +void tox_callback_friend_status_message(Tox *tox, tox_friend_status_message_cb *callback) +{ + assert(tox != nullptr); + tox->friend_status_message_callback = callback; +} + +Tox_User_Status tox_friend_get_status(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = m_get_userstatus(tox->m, friend_number); + tox_unlock(tox); + + if (ret == USERSTATUS_INVALID) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return TOX_USER_STATUS_NONE; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return (Tox_User_Status)ret; +} + +void tox_callback_friend_status(Tox *tox, tox_friend_status_cb *callback) +{ + assert(tox != nullptr); + tox->friend_status_callback = callback; +} + +Tox_Connection tox_friend_get_connection_status(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = m_get_friend_connectionstatus(tox->m, friend_number); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return TOX_CONNECTION_NONE; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return (Tox_Connection)ret; +} + +void tox_callback_friend_connection_status(Tox *tox, tox_friend_connection_status_cb *callback) +{ + assert(tox != nullptr); + tox->friend_connection_status_callback = callback; +} + +bool tox_friend_get_typing(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = m_get_istyping(tox->m, friend_number); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return ret != 0; +} + +void tox_callback_friend_typing(Tox *tox, tox_friend_typing_cb *callback) +{ + assert(tox != nullptr); + tox->friend_typing_callback = callback; +} + +bool tox_self_set_typing(Tox *tox, uint32_t friend_number, bool typing, Tox_Err_Set_Typing *error) +{ + assert(tox != nullptr); + tox_lock(tox); + + if (m_set_usertyping(tox->m, friend_number, typing) == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND); + tox_unlock(tox); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_SET_TYPING_OK); + tox_unlock(tox); + return true; +} + +non_null(1) nullable(3) +static void set_message_error(const Logger *log, int ret, Tox_Err_Friend_Send_Message *error) +{ + switch (ret) { + case 0: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_OK); + break; + } + + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND); + break; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG); + break; + } + + case -3: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED); + break; + } + + case -4: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ); + break; + } + + case -5: { + LOGGER_FATAL(log, "impossible: Messenger and Tox disagree on message types"); + break; + } + + default: { + /* can't happen */ + LOGGER_FATAL(log, "impossible return value: %d", ret); + break; + } + } +} + +uint32_t tox_friend_send_message(Tox *tox, uint32_t friend_number, Tox_Message_Type type, const uint8_t *message, + size_t length, Tox_Err_Friend_Send_Message *error) +{ + assert(tox != nullptr); + + if (message == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_NULL); + return 0; + } + + if (length == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY); + return 0; + } + + uint32_t message_id = 0; + tox_lock(tox); + set_message_error(tox->m->log, m_send_message_generic(tox->m, friend_number, type, message, length, &message_id), + error); + tox_unlock(tox); + return message_id; +} + +void tox_callback_friend_read_receipt(Tox *tox, tox_friend_read_receipt_cb *callback) +{ + assert(tox != nullptr); + tox->friend_read_receipt_callback = callback; +} + +void tox_callback_friend_request(Tox *tox, tox_friend_request_cb *callback) +{ + assert(tox != nullptr); + tox->friend_request_callback = callback; +} + +void tox_callback_friend_message(Tox *tox, tox_friend_message_cb *callback) +{ + assert(tox != nullptr); + tox->friend_message_callback = callback; +} + +bool tox_hash(uint8_t *hash, const uint8_t *data, size_t length) +{ + if (hash == nullptr || (data == nullptr && length != 0)) { + return false; + } + + crypto_sha256(hash, data, length); + return true; +} + +bool tox_file_control(Tox *tox, uint32_t friend_number, uint32_t file_number, Tox_File_Control control, + Tox_Err_File_Control *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = file_control(tox->m, friend_number, file_number, control); + tox_unlock(tox); + + if (ret == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_OK); + return true; + } + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND); + return false; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_FRIEND_NOT_CONNECTED); + return false; + } + + case -3: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_NOT_FOUND); + return false; + } + + case -4: { + /* can't happen (this code is returned if `control` is invalid type) */ + LOGGER_FATAL(tox->m->log, "impossible return value: %d", ret); + return false; + } + + case -5: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_ALREADY_PAUSED); + return false; + } + + case -6: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_DENIED); + return false; + } + + case -7: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_NOT_PAUSED); + return false; + } + + case -8: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_SENDQ); + return false; + } + } + + /* can't happen */ + LOGGER_FATAL(tox->m->log, "impossible return value: %d", ret); + + return false; +} + +bool tox_file_seek(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, + Tox_Err_File_Seek *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = file_seek(tox->m, friend_number, file_number, position); + tox_unlock(tox); + + if (ret == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_OK); + return true; + } + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_FRIEND_NOT_FOUND); + return false; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_FRIEND_NOT_CONNECTED); + return false; + } + + case -3: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_NOT_FOUND); + return false; + } + + case -4: // fall-through + case -5: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_DENIED); + return false; + } + + case -6: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_INVALID_POSITION); + return false; + } + + case -8: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_SENDQ); + return false; + } + } + + /* can't happen */ + LOGGER_FATAL(tox->m->log, "impossible return value: %d", ret); + + return false; +} + +void tox_callback_file_recv_control(Tox *tox, tox_file_recv_control_cb *callback) +{ + assert(tox != nullptr); + tox->file_recv_control_callback = callback; +} + +bool tox_file_get_file_id(const Tox *tox, uint32_t friend_number, uint32_t file_number, uint8_t *file_id, + Tox_Err_File_Get *error) +{ + assert(tox != nullptr); + + if (file_id == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_NULL); + return false; + } + + tox_lock(tox); + const int ret = file_get_id(tox->m, friend_number, file_number, file_id); + tox_unlock(tox); + + if (ret == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_OK); + return true; + } + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_FRIEND_NOT_FOUND); + } else { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_NOT_FOUND); + } + + return false; +} + +uint32_t tox_file_send(Tox *tox, uint32_t friend_number, uint32_t kind, uint64_t file_size, const uint8_t *file_id, + const uint8_t *filename, size_t filename_length, Tox_Err_File_Send *error) +{ + assert(tox != nullptr); + + if (filename == nullptr && filename_length != 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_NULL); + return UINT32_MAX; + } + + uint8_t f_id[FILE_ID_LENGTH]; + + if (file_id == nullptr) { + /* Tox keys are 32 bytes like FILE_ID_LENGTH. */ + new_symmetric_key(&tox->rng, f_id); + file_id = f_id; + } + + tox_lock(tox); + const long int file_num = new_filesender(tox->m, friend_number, kind, file_size, file_id, filename, filename_length); + tox_unlock(tox); + + if (file_num >= 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_OK); + return file_num; + } + + switch (file_num) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND); + return UINT32_MAX; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_NAME_TOO_LONG); + return UINT32_MAX; + } + + case -3: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_TOO_MANY); + return UINT32_MAX; + } + + case -4: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED); + return UINT32_MAX; + } + } + + /* can't happen */ + LOGGER_FATAL(tox->m->log, "impossible return value: %ld", file_num); + + return UINT32_MAX; +} + +bool tox_file_send_chunk(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, + size_t length, Tox_Err_File_Send_Chunk *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = send_file_data(tox->m, friend_number, file_number, position, data, length); + tox_unlock(tox); + + if (ret == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_OK); + return true; + } + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_FOUND); + return false; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_CONNECTED); + return false; + } + + case -3: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_NOT_FOUND); + return false; + } + + case -4: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_NOT_TRANSFERRING); + return false; + } + + case -5: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_INVALID_LENGTH); + return false; + } + + case -6: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_SENDQ); + return false; + } + + case -7: { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_WRONG_POSITION); + return false; + } + } + + /* can't happen */ + LOGGER_FATAL(tox->m->log, "impossible return value: %d", ret); + + return false; +} + +void tox_callback_file_chunk_request(Tox *tox, tox_file_chunk_request_cb *callback) +{ + assert(tox != nullptr); + tox->file_chunk_request_callback = callback; +} + +void tox_callback_file_recv(Tox *tox, tox_file_recv_cb *callback) +{ + assert(tox != nullptr); + tox->file_recv_callback = callback; +} + +void tox_callback_file_recv_chunk(Tox *tox, tox_file_recv_chunk_cb *callback) +{ + assert(tox != nullptr); + tox->file_recv_chunk_callback = callback; +} + +void tox_callback_conference_invite(Tox *tox, tox_conference_invite_cb *callback) +{ + assert(tox != nullptr); + tox->conference_invite_callback = callback; +} + +void tox_callback_conference_connected(Tox *tox, tox_conference_connected_cb *callback) +{ + assert(tox != nullptr); + tox->conference_connected_callback = callback; +} + +void tox_callback_conference_message(Tox *tox, tox_conference_message_cb *callback) +{ + assert(tox != nullptr); + tox->conference_message_callback = callback; +} + +void tox_callback_conference_title(Tox *tox, tox_conference_title_cb *callback) +{ + assert(tox != nullptr); + tox->conference_title_callback = callback; +} + +void tox_callback_conference_peer_name(Tox *tox, tox_conference_peer_name_cb *callback) +{ + assert(tox != nullptr); + tox->conference_peer_name_callback = callback; +} + +void tox_callback_conference_peer_list_changed(Tox *tox, tox_conference_peer_list_changed_cb *callback) +{ + assert(tox != nullptr); + tox->conference_peer_list_changed_callback = callback; +} + +uint32_t tox_conference_new(Tox *tox, Tox_Err_Conference_New *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = add_groupchat(tox->m->conferences_object, &tox->rng, GROUPCHAT_TYPE_TEXT); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_NEW_INIT); + return UINT32_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_NEW_OK); + return ret; +} + +bool tox_conference_delete(Tox *tox, uint32_t conference_number, Tox_Err_Conference_Delete *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const bool ret = del_groupchat(tox->m->conferences_object, conference_number, true); + tox_unlock(tox); + + if (!ret) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_DELETE_CONFERENCE_NOT_FOUND); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_DELETE_OK); + return true; +} + +uint32_t tox_conference_peer_count(const Tox *tox, uint32_t conference_number, Tox_Err_Conference_Peer_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_number_peers(tox->m->conferences_object, conference_number, false); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return UINT32_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return ret; +} + +size_t tox_conference_peer_get_name_size(const Tox *tox, uint32_t conference_number, uint32_t peer_number, + Tox_Err_Conference_Peer_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_peername_size(tox->m->conferences_object, conference_number, peer_number, false); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return -1; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); + return -1; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return ret; +} + +bool tox_conference_peer_get_name(const Tox *tox, uint32_t conference_number, uint32_t peer_number, uint8_t *name, + Tox_Err_Conference_Peer_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_peername(tox->m->conferences_object, conference_number, peer_number, name, false); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return false; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); + return false; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return true; +} + +bool tox_conference_peer_get_public_key(const Tox *tox, uint32_t conference_number, uint32_t peer_number, + uint8_t *public_key, Tox_Err_Conference_Peer_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_peer_pubkey(tox->m->conferences_object, conference_number, peer_number, public_key, false); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return false; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); + return false; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return true; +} + +bool tox_conference_peer_number_is_ours(const Tox *tox, uint32_t conference_number, uint32_t peer_number, + Tox_Err_Conference_Peer_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_peernumber_is_ours(tox->m->conferences_object, conference_number, peer_number); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return false; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); + return false; + } + + case -3: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_NO_CONNECTION); + return false; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return ret != 0; +} + +uint32_t tox_conference_offline_peer_count(const Tox *tox, uint32_t conference_number, + Tox_Err_Conference_Peer_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_number_peers(tox->m->conferences_object, conference_number, true); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return UINT32_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return ret; +} + +size_t tox_conference_offline_peer_get_name_size(const Tox *tox, uint32_t conference_number, + uint32_t offline_peer_number, + Tox_Err_Conference_Peer_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_peername_size(tox->m->conferences_object, conference_number, offline_peer_number, true); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return -1; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); + return -1; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return ret; +} + +bool tox_conference_offline_peer_get_name(const Tox *tox, uint32_t conference_number, uint32_t offline_peer_number, + uint8_t *name, + Tox_Err_Conference_Peer_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_peername(tox->m->conferences_object, conference_number, offline_peer_number, name, true); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return false; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); + return false; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return true; +} + +bool tox_conference_offline_peer_get_public_key(const Tox *tox, uint32_t conference_number, + uint32_t offline_peer_number, + uint8_t *public_key, Tox_Err_Conference_Peer_Query *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_peer_pubkey(tox->m->conferences_object, conference_number, offline_peer_number, public_key, true); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return false; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); + return false; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return true; +} + +uint64_t tox_conference_offline_peer_get_last_active(const Tox *tox, uint32_t conference_number, + uint32_t offline_peer_number, + Tox_Err_Conference_Peer_Query *error) +{ + assert(tox != nullptr); + uint64_t last_active = UINT64_MAX; + tox_lock(tox); + const int ret = group_frozen_last_active(tox->m->conferences_object, conference_number, offline_peer_number, + &last_active); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return UINT64_MAX; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); + return UINT64_MAX; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return last_active; +} + +bool tox_conference_set_max_offline(Tox *tox, uint32_t conference_number, + uint32_t max_offline_peers, + Tox_Err_Conference_Set_Max_Offline *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_set_max_frozen(tox->m->conferences_object, conference_number, max_offline_peers); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SET_MAX_OFFLINE_CONFERENCE_NOT_FOUND); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SET_MAX_OFFLINE_OK); + return true; +} + +bool tox_conference_invite(Tox *tox, uint32_t friend_number, uint32_t conference_number, + Tox_Err_Conference_Invite *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = invite_friend(tox->m->conferences_object, friend_number, conference_number); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_CONFERENCE_NOT_FOUND); + return false; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_FAIL_SEND); + return false; + } + + case -3: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_NO_CONNECTION); + return false; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_OK); + return true; +} + +uint32_t tox_conference_join(Tox *tox, uint32_t friend_number, const uint8_t *cookie, size_t length, + Tox_Err_Conference_Join *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = join_groupchat(tox->m->conferences_object, friend_number, GROUPCHAT_TYPE_TEXT, cookie, length); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_INVALID_LENGTH); + return UINT32_MAX; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_WRONG_TYPE); + return UINT32_MAX; + } + + case -3: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_FRIEND_NOT_FOUND); + return UINT32_MAX; + } + + case -4: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_DUPLICATE); + return UINT32_MAX; + } + + case -5: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_INIT_FAIL); + return UINT32_MAX; + } + + case -6: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_FAIL_SEND); + return UINT32_MAX; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_OK); + return ret; +} + +bool tox_conference_send_message(Tox *tox, uint32_t conference_number, Tox_Message_Type type, const uint8_t *message, + size_t length, Tox_Err_Conference_Send_Message *error) +{ + assert(tox != nullptr); + tox_lock(tox); + int ret = 0; + + if (type == TOX_MESSAGE_TYPE_NORMAL) { + ret = group_message_send(tox->m->conferences_object, conference_number, message, length); + } else { + ret = group_action_send(tox->m->conferences_object, conference_number, message, length); + } + + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_CONFERENCE_NOT_FOUND); + return false; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_TOO_LONG); + return false; + } + + case -3: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_NO_CONNECTION); + return false; + } + + case -4: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_FAIL_SEND); + return false; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_OK); + return true; +} + +size_t tox_conference_get_title_size(const Tox *tox, uint32_t conference_number, Tox_Err_Conference_Title *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_title_get_size(tox->m->conferences_object, conference_number); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND); + return -1; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH); + return -1; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_OK); + return ret; +} + +bool tox_conference_get_title(const Tox *tox, uint32_t conference_number, uint8_t *title, + Tox_Err_Conference_Title *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_title_get(tox->m->conferences_object, conference_number, title); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND); + return false; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH); + return false; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_OK); + return true; +} + +bool tox_conference_set_title(Tox *tox, uint32_t conference_number, const uint8_t *title, size_t length, + Tox_Err_Conference_Title *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_title_send(tox->m->conferences_object, conference_number, title, length); + tox_unlock(tox); + + switch (ret) { + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND); + return false; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH); + return false; + } + + case -3: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_FAIL_SEND); + return false; + } + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_OK); + return true; +} + +size_t tox_conference_get_chatlist_size(const Tox *tox) +{ + assert(tox != nullptr); + tox_lock(tox); + const size_t ret = count_chatlist(tox->m->conferences_object); + tox_unlock(tox); + return ret; +} + +void tox_conference_get_chatlist(const Tox *tox, uint32_t *chatlist) +{ + assert(tox != nullptr); + tox_lock(tox); + const size_t list_size = count_chatlist(tox->m->conferences_object); + copy_chatlist(tox->m->conferences_object, chatlist, list_size); + tox_unlock(tox); +} + +Tox_Conference_Type tox_conference_get_type(const Tox *tox, uint32_t conference_number, + Tox_Err_Conference_Get_Type *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const int ret = group_get_type(tox->m->conferences_object, conference_number); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_GET_TYPE_CONFERENCE_NOT_FOUND); + return (Tox_Conference_Type)ret; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_GET_TYPE_OK); + return (Tox_Conference_Type)ret; +} + +bool tox_conference_get_id(const Tox *tox, uint32_t conference_number, uint8_t *id) +{ + assert(tox != nullptr); + tox_lock(tox); + const bool ret = conference_get_id(tox->m->conferences_object, conference_number, id); + tox_unlock(tox); + return ret; +} + +// TODO(iphydf): Delete in 0.3.0. +bool tox_conference_get_uid(const Tox *tox, uint32_t conference_number, uint8_t *uid) +{ + assert(tox != nullptr); + return tox_conference_get_id(tox, conference_number, uid); +} + +uint32_t tox_conference_by_id(const Tox *tox, const uint8_t *id, Tox_Err_Conference_By_Id *error) +{ + assert(tox != nullptr); + + if (id == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_ID_NULL); + return UINT32_MAX; + } + + tox_lock(tox); + const int32_t ret = conference_by_id(tox->m->conferences_object, id); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_ID_NOT_FOUND); + return UINT32_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_ID_OK); + assert(ret >= 0); + return (uint32_t)ret; +} + +// TODO(iphydf): Delete in 0.3.0. +uint32_t tox_conference_by_uid(const Tox *tox, const uint8_t *uid, Tox_Err_Conference_By_Uid *error) +{ + assert(tox != nullptr); + Tox_Err_Conference_By_Id id_error; + const uint32_t res = tox_conference_by_id(tox, uid, &id_error); + + switch (id_error) { + case TOX_ERR_CONFERENCE_BY_ID_OK: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_UID_OK); + break; + } + + case TOX_ERR_CONFERENCE_BY_ID_NULL: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_UID_NULL); + break; + } + + case TOX_ERR_CONFERENCE_BY_ID_NOT_FOUND: { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_UID_NOT_FOUND); + break; + } + } + + return res; +} + +nullable(2) +static void set_custom_packet_error(int ret, Tox_Err_Friend_Custom_Packet *error) +{ + switch (ret) { + case 0: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_OK); + break; + } + + case -1: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_FOUND); + break; + } + + case -2: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_TOO_LONG); + break; + } + + case -3: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_INVALID); + break; + } + + case -4: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_CONNECTED); + break; + } + + case -5: { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_SENDQ); + break; + } + } +} + +bool tox_friend_send_lossy_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + Tox_Err_Friend_Custom_Packet *error) +{ + assert(tox != nullptr); + + if (data == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_NULL); + return false; + } + + if (length == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_EMPTY); + return false; + } + + if (data[0] < PACKET_ID_RANGE_LOSSY_START || data[0] > PACKET_ID_RANGE_LOSSY_END) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_INVALID); + return false; + } + + tox_lock(tox); + const int ret = m_send_custom_lossy_packet(tox->m, friend_number, data, length); + tox_unlock(tox); + + set_custom_packet_error(ret, error); + + return ret == 0; +} + +void tox_callback_friend_lossy_packet(Tox *tox, tox_friend_lossy_packet_cb *callback) +{ + assert(tox != nullptr); + + /* start at PACKET_ID_RANGE_LOSSY_CUSTOM_START so ToxAV Packets are excluded */ + for (uint8_t i = PACKET_ID_RANGE_LOSSY_CUSTOM_START; i <= PACKET_ID_RANGE_LOSSY_END; ++i) { + tox->friend_lossy_packet_callback_per_pktid[i] = callback; + } +} + +bool tox_friend_send_lossless_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + Tox_Err_Friend_Custom_Packet *error) +{ + assert(tox != nullptr); + + if (data == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_NULL); + return false; + } + + if (length == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_EMPTY); + return false; + } + + tox_lock(tox); + const int ret = send_custom_lossless_packet(tox->m, friend_number, data, length); + tox_unlock(tox); + + set_custom_packet_error(ret, error); + + return ret == 0; +} + +void tox_callback_friend_lossless_packet(Tox *tox, tox_friend_lossless_packet_cb *callback) +{ + assert(tox != nullptr); + + for (uint8_t i = PACKET_ID_RANGE_LOSSLESS_CUSTOM_START; i <= PACKET_ID_RANGE_LOSSLESS_CUSTOM_END; ++i) { + tox->friend_lossless_packet_callback_per_pktid[i] = callback; + } +} + +void tox_self_get_dht_id(const Tox *tox, uint8_t *dht_id) +{ + assert(tox != nullptr); + + if (dht_id != nullptr) { + tox_lock(tox); + memcpy(dht_id, dht_get_self_public_key(tox->m->dht), CRYPTO_PUBLIC_KEY_SIZE); + tox_unlock(tox); + } +} + +uint16_t tox_self_get_udp_port(const Tox *tox, Tox_Err_Get_Port *error) +{ + assert(tox != nullptr); + tox_lock(tox); + const uint16_t port = net_htons(net_port(tox->m->net)); + tox_unlock(tox); + + if (port == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_NOT_BOUND); + return 0; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_OK); + return port; +} + +uint16_t tox_self_get_tcp_port(const Tox *tox, Tox_Err_Get_Port *error) +{ + assert(tox != nullptr); + tox_lock(tox); + + if (tox->m->tcp_server != nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_OK); + const uint16_t ret = tox->m->options.tcp_server_port; + tox_unlock(tox); + return ret; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_NOT_BOUND); + tox_unlock(tox); + return 0; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/tox_api.m b/local_pod_repo/toxcore/toxcore/toxcore/tox_api.m new file mode 100644 index 0000000..00051e4 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/tox_api.m @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2021 The TokTok team. + */ +#include "tox.h" + +#include +#include + +#include "ccompat.h" +#include "tox_private.h" + +#define SET_ERROR_PARAMETER(param, x) \ + do { \ + if (param != nullptr) { \ + *param = x; \ + } \ + } while (0) + +uint32_t tox_version_major(void) +{ + return TOX_VERSION_MAJOR; +} +uint32_t tox_version_minor(void) +{ + return TOX_VERSION_MINOR; +} +uint32_t tox_version_patch(void) +{ + return TOX_VERSION_PATCH; +} +uint32_t tox_public_key_size(void) +{ + return TOX_PUBLIC_KEY_SIZE; +} +uint32_t tox_secret_key_size(void) +{ + return TOX_SECRET_KEY_SIZE; +} +uint32_t tox_conference_uid_size(void) +{ + return TOX_CONFERENCE_UID_SIZE; +} +uint32_t tox_conference_id_size(void) +{ + return TOX_CONFERENCE_ID_SIZE; +} +uint32_t tox_nospam_size(void) +{ + return TOX_NOSPAM_SIZE; +} +uint32_t tox_address_size(void) +{ + return TOX_ADDRESS_SIZE; +} +uint32_t tox_max_name_length(void) +{ + return TOX_MAX_NAME_LENGTH; +} +uint32_t tox_max_status_message_length(void) +{ + return TOX_MAX_STATUS_MESSAGE_LENGTH; +} +uint32_t tox_max_friend_request_length(void) +{ + return TOX_MAX_FRIEND_REQUEST_LENGTH; +} +uint32_t tox_max_message_length(void) +{ + return TOX_MAX_MESSAGE_LENGTH; +} +uint32_t tox_max_custom_packet_size(void) +{ + return TOX_MAX_CUSTOM_PACKET_SIZE; +} +uint32_t tox_hash_length(void) +{ + return TOX_HASH_LENGTH; +} +uint32_t tox_file_id_length(void) +{ + return TOX_FILE_ID_LENGTH; +} +uint32_t tox_max_filename_length(void) +{ + return TOX_MAX_FILENAME_LENGTH; +} +uint32_t tox_max_hostname_length(void) +{ + return TOX_MAX_HOSTNAME_LENGTH; +} +uint32_t tox_dht_node_ip_string_size(void) +{ + return TOX_DHT_NODE_IP_STRING_SIZE; +} +uint32_t tox_dht_node_public_key_size(void) +{ + return TOX_DHT_NODE_PUBLIC_KEY_SIZE; +} + +//!TOKSTYLE- + +#define ACCESSORS(type, ns, name) \ +type tox_options_get_##ns##name(const struct Tox_Options *options) \ +{ \ + return options->ns##name; \ +} \ +void tox_options_set_##ns##name(struct Tox_Options *options, type name) \ +{ \ + options->ns##name = name; \ +} + +ACCESSORS(bool,, ipv6_enabled) +ACCESSORS(bool,, udp_enabled) +ACCESSORS(Tox_Proxy_Type, proxy_, type) +ACCESSORS(const char *, proxy_, host) +ACCESSORS(uint16_t, proxy_, port) +ACCESSORS(uint16_t,, start_port) +ACCESSORS(uint16_t,, end_port) +ACCESSORS(uint16_t,, tcp_port) +ACCESSORS(bool,, hole_punching_enabled) +ACCESSORS(Tox_Savedata_Type, savedata_, type) +ACCESSORS(size_t, savedata_, length) +ACCESSORS(tox_log_cb *, log_, callback) +ACCESSORS(void *, log_, user_data) +ACCESSORS(bool,, local_discovery_enabled) +ACCESSORS(bool,, dht_announcements_enabled) +ACCESSORS(bool,, experimental_thread_safety) +ACCESSORS(const Tox_System *,, operating_system) + +//!TOKSTYLE+ + +const uint8_t *tox_options_get_savedata_data(const struct Tox_Options *options) +{ + return options->savedata_data; +} + +void tox_options_set_savedata_data(struct Tox_Options *options, const uint8_t *data, size_t length) +{ + options->savedata_data = data; + options->savedata_length = length; +} + +void tox_options_default(struct Tox_Options *options) +{ + if (options != nullptr) { + const struct Tox_Options default_options = {0}; + *options = default_options; + tox_options_set_ipv6_enabled(options, true); + tox_options_set_udp_enabled(options, true); + tox_options_set_proxy_type(options, TOX_PROXY_TYPE_NONE); + tox_options_set_hole_punching_enabled(options, true); + tox_options_set_local_discovery_enabled(options, true); + tox_options_set_dht_announcements_enabled(options, true); + tox_options_set_experimental_thread_safety(options, false); + } +} + +struct Tox_Options *tox_options_new(Tox_Err_Options_New *error) +{ + struct Tox_Options *options = (struct Tox_Options *)calloc(1, sizeof(struct Tox_Options)); + + if (options != nullptr) { + tox_options_default(options); + SET_ERROR_PARAMETER(error, TOX_ERR_OPTIONS_NEW_OK); + return options; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_OPTIONS_NEW_MALLOC); + return nullptr; +} + +void tox_options_free(struct Tox_Options *options) +{ + free(options); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/tox_dispatch.h b/local_pod_repo/toxcore/toxcore/toxcore/tox_dispatch.h new file mode 100644 index 0000000..7e8fbba --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/tox_dispatch.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#ifndef C_TOXCORE_TOXCORE_TOX_DISPATCH_H +#define C_TOXCORE_TOXCORE_TOX_DISPATCH_H + +#include "tox_events.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The events dispatch table. + * + * This holds all the callbacks registered with `tox_events_callback_*` + * functions below. + */ +typedef struct Tox_Dispatch Tox_Dispatch; + +typedef enum Tox_Err_Dispatch_New { + /** + * The function returned successfully. + */ + TOX_ERR_DISPATCH_NEW_OK, + + /** + * The function failed to allocate memory for the dispatch table. + */ + TOX_ERR_DISPATCH_NEW_MALLOC, +} Tox_Err_Dispatch_New; + +/** + * @brief Creates a new empty event dispatch table. + */ +Tox_Dispatch *tox_dispatch_new(Tox_Err_Dispatch_New *error); + +/** + * @brief Deallocate an event dispatch table. + */ +void tox_dispatch_free(Tox_Dispatch *dispatch); + +/** + * @brief Invoke registered callbacks for each of the events. + * + * @param dispatch The events dispatch table. + * @param events The events object received from @ref tox_events_iterate. + * @param tox The tox object to pass down to the callbacks. + * @param user_data User data pointer to pass down to the callbacks. + */ +void tox_dispatch_invoke(const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data); + +typedef void tox_events_conference_connected_cb( + Tox *tox, const Tox_Event_Conference_Connected *event, void *user_data); +typedef void tox_events_conference_invite_cb( + Tox *tox, const Tox_Event_Conference_Invite *event, void *user_data); +typedef void tox_events_conference_message_cb( + Tox *tox, const Tox_Event_Conference_Message *event, void *user_data); +typedef void tox_events_conference_peer_list_changed_cb( + Tox *tox, const Tox_Event_Conference_Peer_List_Changed *event, void *user_data); +typedef void tox_events_conference_peer_name_cb( + Tox *tox, const Tox_Event_Conference_Peer_Name *event, void *user_data); +typedef void tox_events_conference_title_cb( + Tox *tox, const Tox_Event_Conference_Title *event, void *user_data); +typedef void tox_events_file_chunk_request_cb( + Tox *tox, const Tox_Event_File_Chunk_Request *event, void *user_data); +typedef void tox_events_file_recv_cb( + Tox *tox, const Tox_Event_File_Recv *event, void *user_data); +typedef void tox_events_file_recv_chunk_cb( + Tox *tox, const Tox_Event_File_Recv_Chunk *event, void *user_data); +typedef void tox_events_file_recv_control_cb( + Tox *tox, const Tox_Event_File_Recv_Control *event, void *user_data); +typedef void tox_events_friend_connection_status_cb( + Tox *tox, const Tox_Event_Friend_Connection_Status *event, void *user_data); +typedef void tox_events_friend_lossless_packet_cb( + Tox *tox, const Tox_Event_Friend_Lossless_Packet *event, void *user_data); +typedef void tox_events_friend_lossy_packet_cb( + Tox *tox, const Tox_Event_Friend_Lossy_Packet *event, void *user_data); +typedef void tox_events_friend_message_cb( + Tox *tox, const Tox_Event_Friend_Message *event, void *user_data); +typedef void tox_events_friend_name_cb( + Tox *tox, const Tox_Event_Friend_Name *event, void *user_data); +typedef void tox_events_friend_read_receipt_cb( + Tox *tox, const Tox_Event_Friend_Read_Receipt *event, void *user_data); +typedef void tox_events_friend_request_cb( + Tox *tox, const Tox_Event_Friend_Request *event, void *user_data); +typedef void tox_events_friend_status_cb( + Tox *tox, const Tox_Event_Friend_Status *event, void *user_data); +typedef void tox_events_friend_status_message_cb( + Tox *tox, const Tox_Event_Friend_Status_Message *event, void *user_data); +typedef void tox_events_friend_typing_cb( + Tox *tox, const Tox_Event_Friend_Typing *event, void *user_data); +typedef void tox_events_self_connection_status_cb( + Tox *tox, const Tox_Event_Self_Connection_Status *event, void *user_data); + +void tox_events_callback_conference_connected( + Tox_Dispatch *dispatch, tox_events_conference_connected_cb *callback); +void tox_events_callback_conference_invite( + Tox_Dispatch *dispatch, tox_events_conference_invite_cb *callback); +void tox_events_callback_conference_message( + Tox_Dispatch *dispatch, tox_events_conference_message_cb *callback); +void tox_events_callback_conference_peer_list_changed( + Tox_Dispatch *dispatch, tox_events_conference_peer_list_changed_cb *callback); +void tox_events_callback_conference_peer_name( + Tox_Dispatch *dispatch, tox_events_conference_peer_name_cb *callback); +void tox_events_callback_conference_title( + Tox_Dispatch *dispatch, tox_events_conference_title_cb *callback); +void tox_events_callback_file_chunk_request( + Tox_Dispatch *dispatch, tox_events_file_chunk_request_cb *callback); +void tox_events_callback_file_recv( + Tox_Dispatch *dispatch, tox_events_file_recv_cb *callback); +void tox_events_callback_file_recv_chunk( + Tox_Dispatch *dispatch, tox_events_file_recv_chunk_cb *callback); +void tox_events_callback_file_recv_control( + Tox_Dispatch *dispatch, tox_events_file_recv_control_cb *callback); +void tox_events_callback_friend_connection_status( + Tox_Dispatch *dispatch, tox_events_friend_connection_status_cb *callback); +void tox_events_callback_friend_lossless_packet( + Tox_Dispatch *dispatch, tox_events_friend_lossless_packet_cb *callback); +void tox_events_callback_friend_lossy_packet( + Tox_Dispatch *dispatch, tox_events_friend_lossy_packet_cb *callback); +void tox_events_callback_friend_message( + Tox_Dispatch *dispatch, tox_events_friend_message_cb *callback); +void tox_events_callback_friend_name( + Tox_Dispatch *dispatch, tox_events_friend_name_cb *callback); +void tox_events_callback_friend_read_receipt( + Tox_Dispatch *dispatch, tox_events_friend_read_receipt_cb *callback); +void tox_events_callback_friend_request( + Tox_Dispatch *dispatch, tox_events_friend_request_cb *callback); +void tox_events_callback_friend_status( + Tox_Dispatch *dispatch, tox_events_friend_status_cb *callback); +void tox_events_callback_friend_status_message( + Tox_Dispatch *dispatch, tox_events_friend_status_message_cb *callback); +void tox_events_callback_friend_typing( + Tox_Dispatch *dispatch, tox_events_friend_typing_cb *callback); +void tox_events_callback_self_connection_status( + Tox_Dispatch *dispatch, tox_events_self_connection_status_cb *callback); + +#ifdef __cplusplus +} +#endif + +#endif // C_TOXCORE_TOXCORE_TOX_DISPATCH_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/tox_dispatch.m b/local_pod_repo/toxcore/toxcore/toxcore/tox_dispatch.m new file mode 100644 index 0000000..4b4546e --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/tox_dispatch.m @@ -0,0 +1,484 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "tox_dispatch.h" + +#include + +#include "ccompat.h" + +struct Tox_Dispatch { + tox_events_conference_connected_cb *conference_connected_callback; + tox_events_conference_invite_cb *conference_invite_callback; + tox_events_conference_message_cb *conference_message_callback; + tox_events_conference_peer_list_changed_cb *conference_peer_list_changed_callback; + tox_events_conference_peer_name_cb *conference_peer_name_callback; + tox_events_conference_title_cb *conference_title_callback; + tox_events_file_chunk_request_cb *file_chunk_request_callback; + tox_events_file_recv_cb *file_recv_callback; + tox_events_file_recv_chunk_cb *file_recv_chunk_callback; + tox_events_file_recv_control_cb *file_recv_control_callback; + tox_events_friend_connection_status_cb *friend_connection_status_callback; + tox_events_friend_lossless_packet_cb *friend_lossless_packet_callback; + tox_events_friend_lossy_packet_cb *friend_lossy_packet_callback; + tox_events_friend_message_cb *friend_message_callback; + tox_events_friend_name_cb *friend_name_callback; + tox_events_friend_read_receipt_cb *friend_read_receipt_callback; + tox_events_friend_request_cb *friend_request_callback; + tox_events_friend_status_cb *friend_status_callback; + tox_events_friend_status_message_cb *friend_status_message_callback; + tox_events_friend_typing_cb *friend_typing_callback; + tox_events_self_connection_status_cb *self_connection_status_callback; +}; + +Tox_Dispatch *tox_dispatch_new(Tox_Err_Dispatch_New *error) +{ + Tox_Dispatch *dispatch = (Tox_Dispatch *)calloc(1, sizeof(Tox_Dispatch)); + + if (dispatch == nullptr) { + if (error != nullptr) { + *error = TOX_ERR_DISPATCH_NEW_MALLOC; + } + + return nullptr; + } + + *dispatch = (Tox_Dispatch) { + nullptr + }; + if (error != nullptr) { + *error = TOX_ERR_DISPATCH_NEW_OK; + } + return dispatch; +} + +void tox_dispatch_free(Tox_Dispatch *dispatch) +{ + free(dispatch); +} + +void tox_events_callback_conference_connected( + Tox_Dispatch *dispatch, tox_events_conference_connected_cb *callback) +{ + dispatch->conference_connected_callback = callback; +} +void tox_events_callback_conference_invite( + Tox_Dispatch *dispatch, tox_events_conference_invite_cb *callback) +{ + dispatch->conference_invite_callback = callback; +} +void tox_events_callback_conference_message( + Tox_Dispatch *dispatch, tox_events_conference_message_cb *callback) +{ + dispatch->conference_message_callback = callback; +} +void tox_events_callback_conference_peer_list_changed( + Tox_Dispatch *dispatch, tox_events_conference_peer_list_changed_cb *callback) +{ + dispatch->conference_peer_list_changed_callback = callback; +} +void tox_events_callback_conference_peer_name( + Tox_Dispatch *dispatch, tox_events_conference_peer_name_cb *callback) +{ + dispatch->conference_peer_name_callback = callback; +} +void tox_events_callback_conference_title( + Tox_Dispatch *dispatch, tox_events_conference_title_cb *callback) +{ + dispatch->conference_title_callback = callback; +} +void tox_events_callback_file_chunk_request( + Tox_Dispatch *dispatch, tox_events_file_chunk_request_cb *callback) +{ + dispatch->file_chunk_request_callback = callback; +} +void tox_events_callback_file_recv( + Tox_Dispatch *dispatch, tox_events_file_recv_cb *callback) +{ + dispatch->file_recv_callback = callback; +} +void tox_events_callback_file_recv_chunk( + Tox_Dispatch *dispatch, tox_events_file_recv_chunk_cb *callback) +{ + dispatch->file_recv_chunk_callback = callback; +} +void tox_events_callback_file_recv_control( + Tox_Dispatch *dispatch, tox_events_file_recv_control_cb *callback) +{ + dispatch->file_recv_control_callback = callback; +} +void tox_events_callback_friend_connection_status( + Tox_Dispatch *dispatch, tox_events_friend_connection_status_cb *callback) +{ + dispatch->friend_connection_status_callback = callback; +} +void tox_events_callback_friend_lossless_packet( + Tox_Dispatch *dispatch, tox_events_friend_lossless_packet_cb *callback) +{ + dispatch->friend_lossless_packet_callback = callback; +} +void tox_events_callback_friend_lossy_packet( + Tox_Dispatch *dispatch, tox_events_friend_lossy_packet_cb *callback) +{ + dispatch->friend_lossy_packet_callback = callback; +} +void tox_events_callback_friend_message( + Tox_Dispatch *dispatch, tox_events_friend_message_cb *callback) +{ + dispatch->friend_message_callback = callback; +} +void tox_events_callback_friend_name( + Tox_Dispatch *dispatch, tox_events_friend_name_cb *callback) +{ + dispatch->friend_name_callback = callback; +} +void tox_events_callback_friend_read_receipt( + Tox_Dispatch *dispatch, tox_events_friend_read_receipt_cb *callback) +{ + dispatch->friend_read_receipt_callback = callback; +} +void tox_events_callback_friend_request( + Tox_Dispatch *dispatch, tox_events_friend_request_cb *callback) +{ + dispatch->friend_request_callback = callback; +} +void tox_events_callback_friend_status( + Tox_Dispatch *dispatch, tox_events_friend_status_cb *callback) +{ + dispatch->friend_status_callback = callback; +} +void tox_events_callback_friend_status_message( + Tox_Dispatch *dispatch, tox_events_friend_status_message_cb *callback) +{ + dispatch->friend_status_message_callback = callback; +} +void tox_events_callback_friend_typing( + Tox_Dispatch *dispatch, tox_events_friend_typing_cb *callback) +{ + dispatch->friend_typing_callback = callback; +} +void tox_events_callback_self_connection_status( + Tox_Dispatch *dispatch, tox_events_self_connection_status_cb *callback) +{ + dispatch->self_connection_status_callback = callback; +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_conference_connected( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_conference_connected_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->conference_connected_callback != nullptr) { + dispatch->conference_connected_callback( + tox, tox_events_get_conference_connected(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_conference_invite( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_conference_invite_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->conference_invite_callback != nullptr) { + dispatch->conference_invite_callback( + tox, tox_events_get_conference_invite(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_conference_message( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_conference_message_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->conference_message_callback != nullptr) { + dispatch->conference_message_callback( + tox, tox_events_get_conference_message(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_conference_peer_list_changed( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_conference_peer_list_changed_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->conference_peer_list_changed_callback != nullptr) { + dispatch->conference_peer_list_changed_callback( + tox, tox_events_get_conference_peer_list_changed(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_conference_peer_name( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_conference_peer_name_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->conference_peer_name_callback != nullptr) { + dispatch->conference_peer_name_callback( + tox, tox_events_get_conference_peer_name(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_conference_title( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_conference_title_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->conference_title_callback != nullptr) { + dispatch->conference_title_callback( + tox, tox_events_get_conference_title(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_file_chunk_request( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_file_chunk_request_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->file_chunk_request_callback != nullptr) { + dispatch->file_chunk_request_callback( + tox, tox_events_get_file_chunk_request(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_file_recv( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_file_recv_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->file_recv_callback != nullptr) { + dispatch->file_recv_callback( + tox, tox_events_get_file_recv(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_file_recv_chunk( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_file_recv_chunk_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->file_recv_chunk_callback != nullptr) { + dispatch->file_recv_chunk_callback( + tox, tox_events_get_file_recv_chunk(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_file_recv_control( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_file_recv_control_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->file_recv_control_callback != nullptr) { + dispatch->file_recv_control_callback( + tox, tox_events_get_file_recv_control(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_friend_connection_status( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_friend_connection_status_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->friend_connection_status_callback != nullptr) { + dispatch->friend_connection_status_callback( + tox, tox_events_get_friend_connection_status(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_friend_lossless_packet( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_friend_lossless_packet_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->friend_lossless_packet_callback != nullptr) { + dispatch->friend_lossless_packet_callback( + tox, tox_events_get_friend_lossless_packet(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_friend_lossy_packet( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_friend_lossy_packet_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->friend_lossy_packet_callback != nullptr) { + dispatch->friend_lossy_packet_callback( + tox, tox_events_get_friend_lossy_packet(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_friend_message( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_friend_message_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->friend_message_callback != nullptr) { + dispatch->friend_message_callback( + tox, tox_events_get_friend_message(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_friend_name( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_friend_name_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->friend_name_callback != nullptr) { + dispatch->friend_name_callback( + tox, tox_events_get_friend_name(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_friend_read_receipt( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_friend_read_receipt_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->friend_read_receipt_callback != nullptr) { + dispatch->friend_read_receipt_callback( + tox, tox_events_get_friend_read_receipt(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_friend_request( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_friend_request_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->friend_request_callback != nullptr) { + dispatch->friend_request_callback( + tox, tox_events_get_friend_request(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_friend_status( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_friend_status_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->friend_status_callback != nullptr) { + dispatch->friend_status_callback( + tox, tox_events_get_friend_status(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_friend_status_message( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_friend_status_message_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->friend_status_message_callback != nullptr) { + dispatch->friend_status_message_callback( + tox, tox_events_get_friend_status_message(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_friend_typing( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_friend_typing_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->friend_typing_callback != nullptr) { + dispatch->friend_typing_callback( + tox, tox_events_get_friend_typing(events, i), user_data); + } + } +} + +non_null(1, 3) nullable(2, 4) +static void tox_dispatch_invoke_self_connection_status( + const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + const uint32_t size = tox_events_get_self_connection_status_size(events); + + for (uint32_t i = 0; i < size; ++i) { + if (dispatch->self_connection_status_callback != nullptr) { + dispatch->self_connection_status_callback( + tox, tox_events_get_self_connection_status(events, i), user_data); + } + } +} + +void tox_dispatch_invoke(const Tox_Dispatch *dispatch, const Tox_Events *events, Tox *tox, void *user_data) +{ + tox_dispatch_invoke_conference_connected(dispatch, events, tox, user_data); + tox_dispatch_invoke_conference_invite(dispatch, events, tox, user_data); + tox_dispatch_invoke_conference_message(dispatch, events, tox, user_data); + tox_dispatch_invoke_conference_peer_list_changed(dispatch, events, tox, user_data); + tox_dispatch_invoke_conference_peer_name(dispatch, events, tox, user_data); + tox_dispatch_invoke_conference_title(dispatch, events, tox, user_data); + tox_dispatch_invoke_file_chunk_request(dispatch, events, tox, user_data); + tox_dispatch_invoke_file_recv(dispatch, events, tox, user_data); + tox_dispatch_invoke_file_recv_chunk(dispatch, events, tox, user_data); + tox_dispatch_invoke_file_recv_control(dispatch, events, tox, user_data); + tox_dispatch_invoke_friend_connection_status(dispatch, events, tox, user_data); + tox_dispatch_invoke_friend_lossless_packet(dispatch, events, tox, user_data); + tox_dispatch_invoke_friend_lossy_packet(dispatch, events, tox, user_data); + tox_dispatch_invoke_friend_message(dispatch, events, tox, user_data); + tox_dispatch_invoke_friend_name(dispatch, events, tox, user_data); + tox_dispatch_invoke_friend_read_receipt(dispatch, events, tox, user_data); + tox_dispatch_invoke_friend_request(dispatch, events, tox, user_data); + tox_dispatch_invoke_friend_status(dispatch, events, tox, user_data); + tox_dispatch_invoke_friend_status_message(dispatch, events, tox, user_data); + tox_dispatch_invoke_friend_typing(dispatch, events, tox, user_data); + tox_dispatch_invoke_self_connection_status(dispatch, events, tox, user_data); +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/tox_events.h b/local_pod_repo/toxcore/toxcore/toxcore/tox_events.h new file mode 100644 index 0000000..f33a71b --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/tox_events.h @@ -0,0 +1,354 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#ifndef C_TOXCORE_TOXCORE_TOX_EVENTS_H +#define C_TOXCORE_TOXCORE_TOX_EVENTS_H + +#include "tox.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Tox_Event_Conference_Connected Tox_Event_Conference_Connected; +uint32_t tox_event_conference_connected_get_conference_number( + const Tox_Event_Conference_Connected *conference_connected); + +typedef struct Tox_Event_Conference_Invite Tox_Event_Conference_Invite; +const uint8_t *tox_event_conference_invite_get_cookie( + const Tox_Event_Conference_Invite *conference_invite); +uint32_t tox_event_conference_invite_get_cookie_length( + const Tox_Event_Conference_Invite *conference_invite); +Tox_Conference_Type tox_event_conference_invite_get_type( + const Tox_Event_Conference_Invite *conference_invite); +uint32_t tox_event_conference_invite_get_friend_number( + const Tox_Event_Conference_Invite *conference_invite); + +typedef struct Tox_Event_Conference_Message Tox_Event_Conference_Message; +const uint8_t *tox_event_conference_message_get_message( + const Tox_Event_Conference_Message *conference_message); +uint32_t tox_event_conference_message_get_message_length( + const Tox_Event_Conference_Message *conference_message); +Tox_Message_Type tox_event_conference_message_get_type( + const Tox_Event_Conference_Message *conference_message); +uint32_t tox_event_conference_message_get_conference_number( + const Tox_Event_Conference_Message *conference_message); +uint32_t tox_event_conference_message_get_peer_number( + const Tox_Event_Conference_Message *conference_message); + +typedef struct Tox_Event_Conference_Peer_List_Changed Tox_Event_Conference_Peer_List_Changed; +uint32_t tox_event_conference_peer_list_changed_get_conference_number( + const Tox_Event_Conference_Peer_List_Changed *conference_peer_list_changed); + +typedef struct Tox_Event_Conference_Peer_Name Tox_Event_Conference_Peer_Name; +const uint8_t *tox_event_conference_peer_name_get_name( + const Tox_Event_Conference_Peer_Name *conference_peer_name); +uint32_t tox_event_conference_peer_name_get_name_length( + const Tox_Event_Conference_Peer_Name *conference_peer_name); +uint32_t tox_event_conference_peer_name_get_conference_number( + const Tox_Event_Conference_Peer_Name *conference_peer_name); +uint32_t tox_event_conference_peer_name_get_peer_number( + const Tox_Event_Conference_Peer_Name *conference_peer_name); + +typedef struct Tox_Event_Conference_Title Tox_Event_Conference_Title; +const uint8_t *tox_event_conference_title_get_title( + const Tox_Event_Conference_Title *conference_title); +uint32_t tox_event_conference_title_get_title_length( + const Tox_Event_Conference_Title *conference_title); +uint32_t tox_event_conference_title_get_conference_number( + const Tox_Event_Conference_Title *conference_title); +uint32_t tox_event_conference_title_get_peer_number( + const Tox_Event_Conference_Title *conference_title); + +typedef struct Tox_Event_File_Chunk_Request Tox_Event_File_Chunk_Request; +uint16_t tox_event_file_chunk_request_get_length( + const Tox_Event_File_Chunk_Request *file_chunk_request); +uint32_t tox_event_file_chunk_request_get_file_number( + const Tox_Event_File_Chunk_Request *file_chunk_request); +uint32_t tox_event_file_chunk_request_get_friend_number( + const Tox_Event_File_Chunk_Request *file_chunk_request); +uint64_t tox_event_file_chunk_request_get_position( + const Tox_Event_File_Chunk_Request *file_chunk_request); + +typedef struct Tox_Event_File_Recv Tox_Event_File_Recv; +const uint8_t *tox_event_file_recv_get_filename( + const Tox_Event_File_Recv *file_recv); +uint32_t tox_event_file_recv_get_filename_length( + const Tox_Event_File_Recv *file_recv); +uint32_t tox_event_file_recv_get_file_number( + const Tox_Event_File_Recv *file_recv); +uint64_t tox_event_file_recv_get_file_size( + const Tox_Event_File_Recv *file_recv); +uint32_t tox_event_file_recv_get_friend_number( + const Tox_Event_File_Recv *file_recv); +uint32_t tox_event_file_recv_get_kind( + const Tox_Event_File_Recv *file_recv); + +typedef struct Tox_Event_File_Recv_Chunk Tox_Event_File_Recv_Chunk; +const uint8_t *tox_event_file_recv_chunk_get_data( + const Tox_Event_File_Recv_Chunk *file_recv_chunk); +uint32_t tox_event_file_recv_chunk_get_length( + const Tox_Event_File_Recv_Chunk *file_recv_chunk); +uint32_t tox_event_file_recv_chunk_get_file_number( + const Tox_Event_File_Recv_Chunk *file_recv_chunk); +uint32_t tox_event_file_recv_chunk_get_friend_number( + const Tox_Event_File_Recv_Chunk *file_recv_chunk); +uint64_t tox_event_file_recv_chunk_get_position( + const Tox_Event_File_Recv_Chunk *file_recv_chunk); + +typedef struct Tox_Event_File_Recv_Control Tox_Event_File_Recv_Control; +Tox_File_Control tox_event_file_recv_control_get_control( + const Tox_Event_File_Recv_Control *file_recv_control); +uint32_t tox_event_file_recv_control_get_file_number( + const Tox_Event_File_Recv_Control *file_recv_control); +uint32_t tox_event_file_recv_control_get_friend_number( + const Tox_Event_File_Recv_Control *file_recv_control); + +typedef struct Tox_Event_Friend_Connection_Status Tox_Event_Friend_Connection_Status; +Tox_Connection tox_event_friend_connection_status_get_connection_status( + const Tox_Event_Friend_Connection_Status *friend_connection_status); +uint32_t tox_event_friend_connection_status_get_friend_number( + const Tox_Event_Friend_Connection_Status *friend_connection_status); + +typedef struct Tox_Event_Friend_Lossless_Packet Tox_Event_Friend_Lossless_Packet; +const uint8_t *tox_event_friend_lossless_packet_get_data( + const Tox_Event_Friend_Lossless_Packet *friend_lossless_packet); +uint32_t tox_event_friend_lossless_packet_get_data_length( + const Tox_Event_Friend_Lossless_Packet *friend_lossless_packet); +uint32_t tox_event_friend_lossless_packet_get_friend_number( + const Tox_Event_Friend_Lossless_Packet *friend_lossless_packet); + +typedef struct Tox_Event_Friend_Lossy_Packet Tox_Event_Friend_Lossy_Packet; +const uint8_t *tox_event_friend_lossy_packet_get_data( + const Tox_Event_Friend_Lossy_Packet *friend_lossy_packet); +uint32_t tox_event_friend_lossy_packet_get_data_length( + const Tox_Event_Friend_Lossy_Packet *friend_lossy_packet); +uint32_t tox_event_friend_lossy_packet_get_friend_number( + const Tox_Event_Friend_Lossy_Packet *friend_lossy_packet); + +typedef struct Tox_Event_Friend_Message Tox_Event_Friend_Message; +uint32_t tox_event_friend_message_get_friend_number( + const Tox_Event_Friend_Message *friend_message); +Tox_Message_Type tox_event_friend_message_get_type( + const Tox_Event_Friend_Message *friend_message); +uint32_t tox_event_friend_message_get_message_length( + const Tox_Event_Friend_Message *friend_message); +const uint8_t *tox_event_friend_message_get_message( + const Tox_Event_Friend_Message *friend_message); + +typedef struct Tox_Event_Friend_Name Tox_Event_Friend_Name; +const uint8_t *tox_event_friend_name_get_name( + const Tox_Event_Friend_Name *friend_name); +uint32_t tox_event_friend_name_get_name_length( + const Tox_Event_Friend_Name *friend_name); +uint32_t tox_event_friend_name_get_friend_number( + const Tox_Event_Friend_Name *friend_name); + +typedef struct Tox_Event_Friend_Read_Receipt Tox_Event_Friend_Read_Receipt; +uint32_t tox_event_friend_read_receipt_get_friend_number( + const Tox_Event_Friend_Read_Receipt *friend_read_receipt); +uint32_t tox_event_friend_read_receipt_get_message_id( + const Tox_Event_Friend_Read_Receipt *friend_read_receipt); + +typedef struct Tox_Event_Friend_Request Tox_Event_Friend_Request; +const uint8_t *tox_event_friend_request_get_message( + const Tox_Event_Friend_Request *friend_request); +const uint8_t *tox_event_friend_request_get_public_key( + const Tox_Event_Friend_Request *friend_request); +uint32_t tox_event_friend_request_get_message_length( + const Tox_Event_Friend_Request *friend_request); + +typedef struct Tox_Event_Friend_Status Tox_Event_Friend_Status; +Tox_User_Status tox_event_friend_status_get_status( + const Tox_Event_Friend_Status *friend_status); +uint32_t tox_event_friend_status_get_friend_number( + const Tox_Event_Friend_Status *friend_status); + +typedef struct Tox_Event_Friend_Status_Message Tox_Event_Friend_Status_Message; +const uint8_t *tox_event_friend_status_message_get_message( + const Tox_Event_Friend_Status_Message *friend_status_message); +uint32_t tox_event_friend_status_message_get_message_length( + const Tox_Event_Friend_Status_Message *friend_status_message); +uint32_t tox_event_friend_status_message_get_friend_number( + const Tox_Event_Friend_Status_Message *friend_status_message); + +typedef struct Tox_Event_Friend_Typing Tox_Event_Friend_Typing; +bool tox_event_friend_typing_get_typing( + const Tox_Event_Friend_Typing *friend_typing); +uint32_t tox_event_friend_typing_get_friend_number( + const Tox_Event_Friend_Typing *friend_typing); + +typedef struct Tox_Event_Self_Connection_Status Tox_Event_Self_Connection_Status; +Tox_Connection tox_event_self_connection_status_get_connection_status( + const Tox_Event_Self_Connection_Status *self_connection_status); + + +typedef enum Tox_Event { + TOX_EVENT_SELF_CONNECTION_STATUS = 0, + + TOX_EVENT_FRIEND_REQUEST = 1, + TOX_EVENT_FRIEND_CONNECTION_STATUS = 2, + TOX_EVENT_FRIEND_LOSSY_PACKET = 3, + TOX_EVENT_FRIEND_LOSSLESS_PACKET = 4, + + TOX_EVENT_FRIEND_NAME = 5, + TOX_EVENT_FRIEND_STATUS = 6, + TOX_EVENT_FRIEND_STATUS_MESSAGE = 7, + + TOX_EVENT_FRIEND_MESSAGE = 8, + TOX_EVENT_FRIEND_READ_RECEIPT = 9, + TOX_EVENT_FRIEND_TYPING = 10, + + TOX_EVENT_FILE_CHUNK_REQUEST = 11, + TOX_EVENT_FILE_RECV = 12, + TOX_EVENT_FILE_RECV_CHUNK = 13, + TOX_EVENT_FILE_RECV_CONTROL = 14, + + TOX_EVENT_CONFERENCE_INVITE = 15, + TOX_EVENT_CONFERENCE_CONNECTED = 16, + TOX_EVENT_CONFERENCE_PEER_LIST_CHANGED = 17, + TOX_EVENT_CONFERENCE_PEER_NAME = 18, + TOX_EVENT_CONFERENCE_TITLE = 19, + + TOX_EVENT_CONFERENCE_MESSAGE = 20, +} Tox_Event; + +/** + * Container object for all Tox core events. + * + * This is an immutable object once created. + */ +typedef struct Tox_Events Tox_Events; + +uint32_t tox_events_get_conference_connected_size(const Tox_Events *events); +uint32_t tox_events_get_conference_invite_size(const Tox_Events *events); +uint32_t tox_events_get_conference_message_size(const Tox_Events *events); +uint32_t tox_events_get_conference_peer_list_changed_size(const Tox_Events *events); +uint32_t tox_events_get_conference_peer_name_size(const Tox_Events *events); +uint32_t tox_events_get_conference_title_size(const Tox_Events *events); +uint32_t tox_events_get_file_chunk_request_size(const Tox_Events *events); +uint32_t tox_events_get_file_recv_chunk_size(const Tox_Events *events); +uint32_t tox_events_get_file_recv_control_size(const Tox_Events *events); +uint32_t tox_events_get_file_recv_size(const Tox_Events *events); +uint32_t tox_events_get_friend_connection_status_size(const Tox_Events *events); +uint32_t tox_events_get_friend_lossless_packet_size(const Tox_Events *events); +uint32_t tox_events_get_friend_lossy_packet_size(const Tox_Events *events); +uint32_t tox_events_get_friend_message_size(const Tox_Events *events); +uint32_t tox_events_get_friend_name_size(const Tox_Events *events); +uint32_t tox_events_get_friend_read_receipt_size(const Tox_Events *events); +uint32_t tox_events_get_friend_request_size(const Tox_Events *events); +uint32_t tox_events_get_friend_status_message_size(const Tox_Events *events); +uint32_t tox_events_get_friend_status_size(const Tox_Events *events); +uint32_t tox_events_get_friend_typing_size(const Tox_Events *events); +uint32_t tox_events_get_self_connection_status_size(const Tox_Events *events); + +const Tox_Event_Conference_Connected *tox_events_get_conference_connected( + const Tox_Events *events, uint32_t index); +const Tox_Event_Conference_Invite *tox_events_get_conference_invite( + const Tox_Events *events, uint32_t index); +const Tox_Event_Conference_Message *tox_events_get_conference_message( + const Tox_Events *events, uint32_t index); +const Tox_Event_Conference_Peer_List_Changed *tox_events_get_conference_peer_list_changed( + const Tox_Events *events, uint32_t index); +const Tox_Event_Conference_Peer_Name *tox_events_get_conference_peer_name( + const Tox_Events *events, uint32_t index); +const Tox_Event_Conference_Title *tox_events_get_conference_title( + const Tox_Events *events, uint32_t index); +const Tox_Event_File_Chunk_Request *tox_events_get_file_chunk_request( + const Tox_Events *events, uint32_t index); +const Tox_Event_File_Recv_Chunk *tox_events_get_file_recv_chunk( + const Tox_Events *events, uint32_t index); +const Tox_Event_File_Recv_Control *tox_events_get_file_recv_control( + const Tox_Events *events, uint32_t index); +const Tox_Event_File_Recv *tox_events_get_file_recv( + const Tox_Events *events, uint32_t index); +const Tox_Event_Friend_Connection_Status *tox_events_get_friend_connection_status( + const Tox_Events *events, uint32_t index); +const Tox_Event_Friend_Lossless_Packet *tox_events_get_friend_lossless_packet( + const Tox_Events *events, uint32_t index); +const Tox_Event_Friend_Lossy_Packet *tox_events_get_friend_lossy_packet( + const Tox_Events *events, uint32_t index); +const Tox_Event_Friend_Message *tox_events_get_friend_message( + const Tox_Events *events, uint32_t index); +const Tox_Event_Friend_Name *tox_events_get_friend_name( + const Tox_Events *events, uint32_t index); +const Tox_Event_Friend_Read_Receipt *tox_events_get_friend_read_receipt( + const Tox_Events *events, uint32_t index); +const Tox_Event_Friend_Request *tox_events_get_friend_request( + const Tox_Events *events, uint32_t index); +const Tox_Event_Friend_Status_Message *tox_events_get_friend_status_message( + const Tox_Events *events, uint32_t index); +const Tox_Event_Friend_Status *tox_events_get_friend_status( + const Tox_Events *events, uint32_t index); +const Tox_Event_Friend_Typing *tox_events_get_friend_typing( + const Tox_Events *events, uint32_t index); +const Tox_Event_Self_Connection_Status *tox_events_get_self_connection_status( + const Tox_Events *events, uint32_t index); + +/** + * Initialise the events recording system. + * + * All callbacks will be set to handlers inside the events recording system. + * After this function returns, no user-defined event handlers will be + * invoked. If the client sets their own handlers after calling this function, + * the events associated with that handler will not be recorded. + */ +void tox_events_init(Tox *tox); + +typedef enum Tox_Err_Events_Iterate { + /** + * The function returned successfully. + */ + TOX_ERR_EVENTS_ITERATE_OK, + + /** + * The function failed to allocate enough memory to store the events. + * + * Some events may still be stored if the return value is NULL. The events + * object will always be valid (or NULL) but if this error code is set, + * the function may have missed some events. + */ + TOX_ERR_EVENTS_ITERATE_MALLOC, +} Tox_Err_Events_Iterate; + +/** + * Run a single `tox_iterate` iteration and record all the events. + * + * If allocation of the top level events object fails, this returns NULL. + * Otherwise it returns an object with the recorded events in it. If an + * allocation fails while recording events, some events may be dropped. + * + * If @p fail_hard is `true`, any failure will result in NULL, so all recorded + * events will be dropped. + * + * The result must be freed using `tox_events_free`. + * + * @param tox The Tox instance to iterate on. + * @param fail_hard Drop all events when any allocation fails. + * @param error An error code. Will be set to OK on success. + * + * @return the recorded events structure. + */ +Tox_Events *tox_events_iterate(Tox *tox, bool fail_hard, Tox_Err_Events_Iterate *error); + +/** + * Frees all memory associated with the events structure. + * + * All pointers into this object and its sub-objects, including byte buffers, + * will be invalid once this function returns. + */ +void tox_events_free(Tox_Events *events); + +uint32_t tox_events_bytes_size(const Tox_Events *events); +void tox_events_get_bytes(const Tox_Events *events, uint8_t *bytes); + +Tox_Events *tox_events_load(const uint8_t *bytes, uint32_t bytes_size); + +bool tox_events_equal(const Tox_Events *a, const Tox_Events *b); + +#ifdef __cplusplus +} +#endif + +#endif // C_TOXCORE_TOXCORE_TOX_EVENTS_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/tox_events.m b/local_pod_repo/toxcore/toxcore/toxcore/tox_events.m new file mode 100644 index 0000000..91a7767 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/tox_events.m @@ -0,0 +1,290 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "tox_events.h" + +#include +#include +#include + +#include "bin_unpack.h" +#include "ccompat.h" +#include "events/events_alloc.h" +#include "tox.h" + + +/***************************************************** + * + * :: Set up event handlers. + * + *****************************************************/ + + +void tox_events_init(Tox *tox) +{ + tox_callback_conference_connected(tox, tox_events_handle_conference_connected); + tox_callback_conference_invite(tox, tox_events_handle_conference_invite); + tox_callback_conference_message(tox, tox_events_handle_conference_message); + tox_callback_conference_peer_list_changed(tox, tox_events_handle_conference_peer_list_changed); + tox_callback_conference_peer_name(tox, tox_events_handle_conference_peer_name); + tox_callback_conference_title(tox, tox_events_handle_conference_title); + tox_callback_file_chunk_request(tox, tox_events_handle_file_chunk_request); + tox_callback_file_recv_chunk(tox, tox_events_handle_file_recv_chunk); + tox_callback_file_recv_control(tox, tox_events_handle_file_recv_control); + tox_callback_file_recv(tox, tox_events_handle_file_recv); + tox_callback_friend_connection_status(tox, tox_events_handle_friend_connection_status); + tox_callback_friend_lossless_packet(tox, tox_events_handle_friend_lossless_packet); + tox_callback_friend_lossy_packet(tox, tox_events_handle_friend_lossy_packet); + tox_callback_friend_message(tox, tox_events_handle_friend_message); + tox_callback_friend_name(tox, tox_events_handle_friend_name); + tox_callback_friend_read_receipt(tox, tox_events_handle_friend_read_receipt); + tox_callback_friend_request(tox, tox_events_handle_friend_request); + tox_callback_friend_status_message(tox, tox_events_handle_friend_status_message); + tox_callback_friend_status(tox, tox_events_handle_friend_status); + tox_callback_friend_typing(tox, tox_events_handle_friend_typing); + tox_callback_self_connection_status(tox, tox_events_handle_self_connection_status); +} + +Tox_Events *tox_events_iterate(Tox *tox, bool fail_hard, Tox_Err_Events_Iterate *error) +{ + Tox_Events_State state = {TOX_ERR_EVENTS_ITERATE_OK}; + tox_iterate(tox, &state); + + if (error != nullptr) { + *error = state.error; + } + + if (fail_hard && state.error != TOX_ERR_EVENTS_ITERATE_OK) { + tox_events_free(state.events); + return nullptr; + } + + return state.events; +} + +bool tox_events_pack(const Tox_Events *events, Bin_Pack *bp) +{ + const uint32_t count = tox_events_get_conference_connected_size(events) + + tox_events_get_conference_invite_size(events) + + tox_events_get_conference_message_size(events) + + tox_events_get_conference_peer_list_changed_size(events) + + tox_events_get_conference_peer_name_size(events) + + tox_events_get_conference_title_size(events) + + tox_events_get_file_chunk_request_size(events) + + tox_events_get_file_recv_chunk_size(events) + + tox_events_get_file_recv_control_size(events) + + tox_events_get_file_recv_size(events) + + tox_events_get_friend_connection_status_size(events) + + tox_events_get_friend_lossless_packet_size(events) + + tox_events_get_friend_lossy_packet_size(events) + + tox_events_get_friend_message_size(events) + + tox_events_get_friend_name_size(events) + + tox_events_get_friend_read_receipt_size(events) + + tox_events_get_friend_request_size(events) + + tox_events_get_friend_status_message_size(events) + + tox_events_get_friend_status_size(events) + + tox_events_get_friend_typing_size(events) + + tox_events_get_self_connection_status_size(events); + + return bin_pack_array(bp, count) + && tox_events_pack_conference_connected(events, bp) + && tox_events_pack_conference_invite(events, bp) + && tox_events_pack_conference_message(events, bp) + && tox_events_pack_conference_peer_list_changed(events, bp) + && tox_events_pack_conference_peer_name(events, bp) + && tox_events_pack_conference_title(events, bp) + && tox_events_pack_file_chunk_request(events, bp) + && tox_events_pack_file_recv_chunk(events, bp) + && tox_events_pack_file_recv_control(events, bp) + && tox_events_pack_file_recv(events, bp) + && tox_events_pack_friend_connection_status(events, bp) + && tox_events_pack_friend_lossless_packet(events, bp) + && tox_events_pack_friend_lossy_packet(events, bp) + && tox_events_pack_friend_message(events, bp) + && tox_events_pack_friend_name(events, bp) + && tox_events_pack_friend_read_receipt(events, bp) + && tox_events_pack_friend_request(events, bp) + && tox_events_pack_friend_status_message(events, bp) + && tox_events_pack_friend_status(events, bp) + && tox_events_pack_friend_typing(events, bp) + && tox_events_pack_self_connection_status(events, bp); +} + +non_null() +static bool tox_event_unpack(Tox_Events *events, Bin_Unpack *bu) +{ + uint32_t size; + if (!bin_unpack_array(bu, &size)) { + return false; + } + + if (size != 2) { + return false; + } + + uint8_t type; + if (!bin_unpack_u08(bu, &type)) { + return false; + } + + switch (type) { + case TOX_EVENT_CONFERENCE_CONNECTED: + return tox_events_unpack_conference_connected(events, bu); + + case TOX_EVENT_CONFERENCE_INVITE: + return tox_events_unpack_conference_invite(events, bu); + + case TOX_EVENT_CONFERENCE_MESSAGE: + return tox_events_unpack_conference_message(events, bu); + + case TOX_EVENT_CONFERENCE_PEER_LIST_CHANGED: + return tox_events_unpack_conference_peer_list_changed(events, bu); + + case TOX_EVENT_CONFERENCE_PEER_NAME: + return tox_events_unpack_conference_peer_name(events, bu); + + case TOX_EVENT_CONFERENCE_TITLE: + return tox_events_unpack_conference_title(events, bu); + + case TOX_EVENT_FILE_CHUNK_REQUEST: + return tox_events_unpack_file_chunk_request(events, bu); + + case TOX_EVENT_FILE_RECV_CHUNK: + return tox_events_unpack_file_recv_chunk(events, bu); + + case TOX_EVENT_FILE_RECV_CONTROL: + return tox_events_unpack_file_recv_control(events, bu); + + case TOX_EVENT_FILE_RECV: + return tox_events_unpack_file_recv(events, bu); + + case TOX_EVENT_FRIEND_CONNECTION_STATUS: + return tox_events_unpack_friend_connection_status(events, bu); + + case TOX_EVENT_FRIEND_LOSSLESS_PACKET: + return tox_events_unpack_friend_lossless_packet(events, bu); + + case TOX_EVENT_FRIEND_LOSSY_PACKET: + return tox_events_unpack_friend_lossy_packet(events, bu); + + case TOX_EVENT_FRIEND_MESSAGE: + return tox_events_unpack_friend_message(events, bu); + + case TOX_EVENT_FRIEND_NAME: + return tox_events_unpack_friend_name(events, bu); + + case TOX_EVENT_FRIEND_READ_RECEIPT: + return tox_events_unpack_friend_read_receipt(events, bu); + + case TOX_EVENT_FRIEND_REQUEST: + return tox_events_unpack_friend_request(events, bu); + + case TOX_EVENT_FRIEND_STATUS_MESSAGE: + return tox_events_unpack_friend_status_message(events, bu); + + case TOX_EVENT_FRIEND_STATUS: + return tox_events_unpack_friend_status(events, bu); + + case TOX_EVENT_FRIEND_TYPING: + return tox_events_unpack_friend_typing(events, bu); + + case TOX_EVENT_SELF_CONNECTION_STATUS: + return tox_events_unpack_self_connection_status(events, bu); + + default: + return false; + } + + return true; +} + +bool tox_events_unpack(Tox_Events *events, Bin_Unpack *bu) +{ + uint32_t size; + if (!bin_unpack_array(bu, &size)) { + return false; + } + + for (uint32_t i = 0; i < size; ++i) { + if (!tox_event_unpack(events, bu)) { + return false; + } + } + + return true; +} + +non_null(1) nullable(2) +static bool tox_events_bin_pack_handler(Bin_Pack *bp, const void *obj) +{ + return tox_events_pack((const Tox_Events *)obj, bp); +} + +uint32_t tox_events_bytes_size(const Tox_Events *events) +{ + return bin_pack_obj_size(tox_events_bin_pack_handler, events); +} + +void tox_events_get_bytes(const Tox_Events *events, uint8_t *bytes) +{ + bin_pack_obj(tox_events_bin_pack_handler, events, bytes, UINT32_MAX); +} + +Tox_Events *tox_events_load(const uint8_t *bytes, uint32_t bytes_size) +{ + Bin_Unpack *bu = bin_unpack_new(bytes, bytes_size); + + if (bu == nullptr) { + return nullptr; + } + + Tox_Events *events = (Tox_Events *)calloc(1, sizeof(Tox_Events)); + + if (events == nullptr) { + bin_unpack_free(bu); + return nullptr; + } + + *events = (Tox_Events) { + nullptr + }; + + if (!tox_events_unpack(events, bu)) { + tox_events_free(events); + bin_unpack_free(bu); + return nullptr; + } + + bin_unpack_free(bu); + return events; +} + +bool tox_events_equal(const Tox_Events *a, const Tox_Events *b) +{ + const uint32_t a_size = tox_events_bytes_size(a); + const uint32_t b_size = tox_events_bytes_size(b); + + if (a_size != b_size) { + return false; + } + + uint8_t *a_bytes = (uint8_t *)malloc(a_size); + uint8_t *b_bytes = (uint8_t *)malloc(b_size); + + if (a_bytes == nullptr || b_bytes == nullptr) { + free(b_bytes); + free(a_bytes); + return false; + } + + tox_events_get_bytes(a, a_bytes); + tox_events_get_bytes(b, b_bytes); + + const bool ret = memcmp(a_bytes, b_bytes, a_size) == 0; + + free(b_bytes); + free(a_bytes); + + return ret; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/tox_private.h b/local_pod_repo/toxcore/toxcore/toxcore/tox_private.h new file mode 100644 index 0000000..71d7a97 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/tox_private.h @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2020 The TokTok team. + * Copyright © 2013 Tox project. + */ + +#ifndef C_TOXCORE_TOXCORE_TOX_PRIVATE_H +#define C_TOXCORE_TOXCORE_TOX_PRIVATE_H + +#include +#include +#include + +#include "tox.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint64_t tox_mono_time_cb(void *user_data); + +struct Tox_System { + tox_mono_time_cb *mono_time_callback; + void *mono_time_user_data; + const struct Random *rng; + const struct Network *ns; +}; + +Tox_System tox_default_system(void); + +void tox_lock(const Tox *tox); +void tox_unlock(const Tox *tox); + +/** + * Set the callback for the `friend_lossy_packet` event for a specific packet ID. + * Pass NULL to unset. + * + * allowed packet ID range: + * from `PACKET_ID_RANGE_LOSSY_START` to `PACKET_ID_RANGE_LOSSY_END` (both inclusive) + */ +void tox_callback_friend_lossy_packet_per_pktid(Tox *tox, tox_friend_lossy_packet_cb *callback, uint8_t pktid); + +/** + * Set the callback for the `friend_lossless_packet` event for a specific packet ID. + * Pass NULL to unset. + * + * allowed packet ID range: + * from `PACKET_ID_RANGE_LOSSLESS_CUSTOM_START` to `PACKET_ID_RANGE_LOSSLESS_CUSTOM_END` (both inclusive) + * and + * `PACKET_ID_MSI` + */ +void tox_callback_friend_lossless_packet_per_pktid(Tox *tox, tox_friend_lossless_packet_cb *callback, uint8_t pktid); + +void tox_set_av_object(Tox *tox, void *object); +void *tox_get_av_object(const Tox *tox); + + +/******************************************************************************* + * + * :: DHT network queries. + * + ******************************************************************************/ + +/** + * The minimum size of an IP string buffer in bytes. + */ +#define TOX_DHT_NODE_IP_STRING_SIZE 96 + +uint32_t tox_dht_node_ip_string_size(void); + +/** + * The size of a DHT node public key in bytes. + */ +#define TOX_DHT_NODE_PUBLIC_KEY_SIZE 32 + +uint32_t tox_dht_node_public_key_size(void); + +/** + * @param public_key The node's public key. + * @param ip The node's IP address, represented as a null terminated string. + * @param port The node's port. + */ +typedef void tox_dht_get_nodes_response_cb(Tox *tox, const uint8_t *public_key, const char *ip, uint16_t port, + void *user_data); + + +/** + * Set the callback for the `dht_get_nodes_response` event. Pass NULL to unset. + * + * This event is triggered when a getnodes response is received from a DHT peer. + */ +void tox_callback_dht_get_nodes_response(Tox *tox, tox_dht_get_nodes_response_cb *callback); + + +typedef enum Tox_Err_Dht_Get_Nodes { + /** + * The function returned successfully. + */ + TOX_ERR_DHT_GET_NODES_OK, + + /** + * UDP is disabled in tox options; the DHT can only be queried when UDP is enabled. + */ + TOX_ERR_DHT_GET_NODES_UDP_DISABLED, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_DHT_GET_NODES_NULL, + + /** + * The supplied port is invalid. + */ + TOX_ERR_DHT_GET_NODES_BAD_PORT, + + /** + * The supplied IP address is invalid. + */ + TOX_ERR_DHT_GET_NODES_BAD_IP, + + /** + * The getnodes request failed. This usually means the packet failed to send. + */ + TOX_ERR_DHT_GET_NODES_FAIL, +} Tox_Err_Dht_Get_Nodes; + +/** + * This function sends a getnodes request to a DHT node for its peers that + * are "close" to the passed target public key according to the distance metric used + * by the DHT implementation. + * + * @param public_key The public key of the node that we wish to query. This key must be + * at least `TOX_DHT_NODE_PUBLIC_KEY_SIZE` bytes in length. + * @param ip A NULL terminated string representing the IP address of the node we wish to query. + * @param port The port of the node we wish to query. + * @param target_public_key The public key for which we want to find close nodes. + * + * @return true on success. + */ +bool tox_dht_get_nodes(const Tox *tox, const uint8_t *public_key, const char *ip, uint16_t port, + const uint8_t *target_public_key, Tox_Err_Dht_Get_Nodes *error); + +#ifdef __cplusplus +} +#endif + +#endif // C_TOXCORE_TOXCORE_TOX_PRIVATE_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/tox_private.m b/local_pod_repo/toxcore/toxcore/toxcore/tox_private.m new file mode 100644 index 0000000..847e96d --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/tox_private.m @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2022 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * The Tox private API (for tests). + */ +#include "tox_private.h" + +#include + +#include "ccompat.h" +#include "network.h" +#include "tox_struct.h" + +#define SET_ERROR_PARAMETER(param, x) \ + do { \ + if (param != nullptr) { \ + *param = x; \ + } \ + } while (0) + +Tox_System tox_default_system(void) +{ + const Tox_System sys = { + nullptr, // mono_time_callback + nullptr, // mono_time_user_data + system_random(), + system_network(), + }; + return sys; +} + +void tox_lock(const Tox *tox) +{ + if (tox->mutex != nullptr) { + pthread_mutex_lock(tox->mutex); + } +} + +void tox_unlock(const Tox *tox) +{ + if (tox->mutex != nullptr) { + pthread_mutex_unlock(tox->mutex); + } +} + +void tox_callback_friend_lossy_packet_per_pktid(Tox *tox, tox_friend_lossy_packet_cb *callback, uint8_t pktid) +{ + assert(tox != nullptr); + + if (pktid >= PACKET_ID_RANGE_LOSSY_START && pktid <= PACKET_ID_RANGE_LOSSY_END) { + tox->friend_lossy_packet_callback_per_pktid[pktid] = callback; + } +} + +void tox_callback_friend_lossless_packet_per_pktid(Tox *tox, tox_friend_lossless_packet_cb *callback, uint8_t pktid) +{ + assert(tox != nullptr); + + if ((pktid >= PACKET_ID_RANGE_LOSSLESS_CUSTOM_START && pktid <= PACKET_ID_RANGE_LOSSLESS_CUSTOM_END) + || pktid == PACKET_ID_MSI) { + tox->friend_lossless_packet_callback_per_pktid[pktid] = callback; + } +} + +void tox_set_av_object(Tox *tox, void *object) +{ + assert(tox != nullptr); + tox_lock(tox); + tox->toxav_object = object; + tox_unlock(tox); +} + +void *tox_get_av_object(const Tox *tox) +{ + assert(tox != nullptr); + tox_lock(tox); + void *object = tox->toxav_object; + tox_unlock(tox); + return object; +} + +void tox_callback_dht_get_nodes_response(Tox *tox, tox_dht_get_nodes_response_cb *callback) +{ + assert(tox != nullptr); + tox->dht_get_nodes_response_callback = callback; +} + +bool tox_dht_get_nodes(const Tox *tox, const uint8_t *public_key, const char *ip, uint16_t port, + const uint8_t *target_public_key, Tox_Err_Dht_Get_Nodes *error) +{ + assert(tox != nullptr); + + tox_lock(tox); + + if (tox->m->options.udp_disabled) { + SET_ERROR_PARAMETER(error, TOX_ERR_DHT_GET_NODES_UDP_DISABLED); + tox_unlock(tox); + return false; + } + + if (public_key == nullptr || ip == nullptr || target_public_key == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_DHT_GET_NODES_NULL); + tox_unlock(tox); + return false; + } + + if (port == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_DHT_GET_NODES_BAD_PORT); + tox_unlock(tox); + return false; + } + + IP_Port *root; + + const int32_t count = net_getipport(ip, &root, TOX_SOCK_DGRAM); + + if (count < 1) { + SET_ERROR_PARAMETER(error, TOX_ERR_DHT_GET_NODES_BAD_IP); + net_freeipport(root); + tox_unlock(tox); + return false; + } + + bool success = false; + + for (int32_t i = 0; i < count; ++i) { + root[i].port = net_htons(port); + + if (dht_getnodes(tox->m->dht, &root[i], public_key, target_public_key)) { + success = true; + } + } + + tox_unlock(tox); + + net_freeipport(root); + + if (!success) { + SET_ERROR_PARAMETER(error, TOX_ERR_DHT_GET_NODES_FAIL); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_DHT_GET_NODES_OK); + + return true; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/tox_struct.h b/local_pod_repo/toxcore/toxcore/toxcore/tox_struct.h new file mode 100644 index 0000000..22d1c54 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/tox_struct.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2022 The TokTok team. + * Copyright © 2013 Tox project. + */ + +#ifndef C_TOXCORE_TOXCORE_TOX_STRUCT_H +#define C_TOXCORE_TOXCORE_TOX_STRUCT_H + +#include "Messenger.h" +#include "tox.h" +#include "tox_private.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct Tox { + Messenger *m; + Mono_Time *mono_time; + Random rng; + Network ns; + pthread_mutex_t *mutex; + + tox_log_cb *log_callback; + tox_self_connection_status_cb *self_connection_status_callback; + tox_friend_name_cb *friend_name_callback; + tox_friend_status_message_cb *friend_status_message_callback; + tox_friend_status_cb *friend_status_callback; + tox_friend_connection_status_cb *friend_connection_status_callback; + tox_friend_typing_cb *friend_typing_callback; + tox_friend_read_receipt_cb *friend_read_receipt_callback; + tox_friend_request_cb *friend_request_callback; + tox_friend_message_cb *friend_message_callback; + tox_file_recv_control_cb *file_recv_control_callback; + tox_file_chunk_request_cb *file_chunk_request_callback; + tox_file_recv_cb *file_recv_callback; + tox_file_recv_chunk_cb *file_recv_chunk_callback; + tox_conference_invite_cb *conference_invite_callback; + tox_conference_connected_cb *conference_connected_callback; + tox_conference_message_cb *conference_message_callback; + tox_conference_title_cb *conference_title_callback; + tox_conference_peer_name_cb *conference_peer_name_callback; + tox_conference_peer_list_changed_cb *conference_peer_list_changed_callback; + tox_dht_get_nodes_response_cb *dht_get_nodes_response_callback; + tox_friend_lossy_packet_cb *friend_lossy_packet_callback_per_pktid[UINT8_MAX + 1]; + tox_friend_lossless_packet_cb *friend_lossless_packet_callback_per_pktid[UINT8_MAX + 1]; + + void *toxav_object; // workaround to store a ToxAV object (setter and getter functions are available) +}; + +#ifdef __cplusplus +} +#endif + +#endif // C_TOXCORE_TOXCORE_TOX_STRUCT_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/tox_unpack.h b/local_pod_repo/toxcore/toxcore/toxcore/tox_unpack.h new file mode 100644 index 0000000..5f0b18a --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/tox_unpack.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#ifndef C_TOXCORE_TOXCORE_TOX_UNPACK_H +#define C_TOXCORE_TOXCORE_TOX_UNPACK_H + +#include "attributes.h" +#include "bin_unpack.h" +#include "tox.h" + +non_null() bool tox_unpack_conference_type(Bin_Unpack *bu, Tox_Conference_Type *val); +non_null() bool tox_unpack_connection(Bin_Unpack *bu, Tox_Connection *val); +non_null() bool tox_unpack_file_control(Bin_Unpack *bu, Tox_File_Control *val); +non_null() bool tox_unpack_message_type(Bin_Unpack *bu, Tox_Message_Type *val); +non_null() bool tox_unpack_user_status(Bin_Unpack *bu, Tox_User_Status *val); + +#endif // C_TOXCORE_TOXCORE_TOX_UNPACK_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/tox_unpack.m b/local_pod_repo/toxcore/toxcore/toxcore/tox_unpack.m new file mode 100644 index 0000000..6508399 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/tox_unpack.m @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2022 The TokTok team. + */ + +#include "tox_unpack.h" + +#include + +#include "bin_unpack.h" +#include "ccompat.h" + +bool tox_unpack_conference_type(Bin_Unpack *bu, Tox_Conference_Type *val) +{ + uint32_t u32; + + if (!bin_unpack_u32(bu, &u32)) { + return false; + } + + *val = (Tox_Conference_Type)u32; + return true; +} + +bool tox_unpack_connection(Bin_Unpack *bu, Tox_Connection *val) +{ + uint32_t u32; + + if (!bin_unpack_u32(bu, &u32)) { + return false; + } + + *val = (Tox_Connection)u32; + return true; +} + +bool tox_unpack_file_control(Bin_Unpack *bu, Tox_File_Control *val) +{ + uint32_t u32; + + if (!bin_unpack_u32(bu, &u32)) { + return false; + } + + *val = (Tox_File_Control)u32; + return true; +} + +bool tox_unpack_message_type(Bin_Unpack *bu, Tox_Message_Type *val) +{ + uint32_t u32; + + if (!bin_unpack_u32(bu, &u32)) { + return false; + } + + *val = (Tox_Message_Type)u32; + return true; +} + +bool tox_unpack_user_status(Bin_Unpack *bu, Tox_User_Status *val) +{ + uint32_t u32; + + if (!bin_unpack_u32(bu, &u32)) { + return false; + } + + *val = (Tox_User_Status)u32; + return true; +} diff --git a/local_pod_repo/toxcore/toxcore/toxcore/util.h b/local_pod_repo/toxcore/toxcore/toxcore/util.h new file mode 100644 index 0000000..588feb5 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/util.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + * Copyright © 2013 plutooo + */ + +/** + * Utilities. + */ +#ifndef C_TOXCORE_TOXCORE_UTIL_H +#define C_TOXCORE_TOXCORE_UTIL_H + +#include +#include +#include +#include + +#include "attributes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool is_power_of_2(uint64_t x); + +/** @brief Frees all pointers in a uint8_t pointer array, as well as the array itself. */ +nullable(1) +void free_uint8_t_pointer_array(uint8_t **ary, size_t n_items); + +/** Returns -1 if failed or 0 if success */ +non_null() int create_recursive_mutex(pthread_mutex_t *mutex); + +/** + * @brief Checks whether two buffers are the same length and contents. + * + * Calls `memcmp` after checking the sizes are equal. + * + * @retval true if sizes and contents are equal. + * @retval false otherwise. + */ +non_null() bool memeq(const uint8_t *a, size_t a_size, const uint8_t *b, size_t b_size); + +// Safe min/max functions with specific types. This forces the conversion to the +// desired type before the comparison expression, giving the choice of +// conversion to the caller. Use these instead of inline comparisons or MIN/MAX +// macros (effectively inline comparisons). +int16_t max_s16(int16_t a, int16_t b); +int32_t max_s32(int32_t a, int32_t b); +int64_t max_s64(int64_t a, int64_t b); + +int16_t min_s16(int16_t a, int16_t b); +int32_t min_s32(int32_t a, int32_t b); +int64_t min_s64(int64_t a, int64_t b); + +uint16_t max_u16(uint16_t a, uint16_t b); +uint32_t max_u32(uint32_t a, uint32_t b); +uint64_t max_u64(uint64_t a, uint64_t b); + +uint16_t min_u16(uint16_t a, uint16_t b); +uint32_t min_u32(uint32_t a, uint32_t b); +uint64_t min_u64(uint64_t a, uint64_t b); + +/** @brief Returns a 32-bit hash of key of size len */ +non_null() +uint32_t jenkins_one_at_a_time_hash(const uint8_t *key, size_t len); + +/** @brief Computes a checksum of a byte array. + * + * @param data The byte array used to compute the checksum. + * @param length The length in bytes of the passed data. + * + * @retval The resulting checksum. + */ +non_null() +uint16_t data_checksum(const uint8_t *data, uint32_t length); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // C_TOXCORE_TOXCORE_UTIL_H diff --git a/local_pod_repo/toxcore/toxcore/toxcore/util.m b/local_pod_repo/toxcore/toxcore/toxcore/util.m new file mode 100644 index 0000000..c70c3a7 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxcore/util.m @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + * Copyright © 2013 plutooo + */ + +/** + * Utilities. + */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include "util.h" + +#include +#include +#include + +#include "ccompat.h" + +bool is_power_of_2(uint64_t x) +{ + return x != 0 && (x & (~x + 1)) == x; +} + +void free_uint8_t_pointer_array(uint8_t **ary, size_t n_items) +{ + if (ary == nullptr) { + return; + } + + for (size_t i = 0; i < n_items; ++i) { + if (ary[i] != nullptr) { + free(ary[i]); + } + } + + free(ary); +} + +uint16_t data_checksum(const uint8_t *data, uint32_t length) +{ + uint8_t checksum[2] = {0}; + uint16_t check; + + for (uint32_t i = 0; i < length; ++i) { + checksum[i % 2] ^= data[i]; + } + + memcpy(&check, checksum, sizeof(check)); + return check; +} + +int create_recursive_mutex(pthread_mutex_t *mutex) +{ + pthread_mutexattr_t attr; + + if (pthread_mutexattr_init(&attr) != 0) { + return -1; + } + + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) { + pthread_mutexattr_destroy(&attr); + return -1; + } + + /* Create queue mutex */ + if (pthread_mutex_init(mutex, &attr) != 0) { + pthread_mutexattr_destroy(&attr); + return -1; + } + + pthread_mutexattr_destroy(&attr); + + return 0; +} + +bool memeq(const uint8_t *a, size_t a_size, const uint8_t *b, size_t b_size) +{ + return a_size == b_size && memcmp(a, b, a_size) == 0; +} + +int16_t max_s16(int16_t a, int16_t b) +{ + return a > b ? a : b; +} +int32_t max_s32(int32_t a, int32_t b) +{ + return a > b ? a : b; +} +int64_t max_s64(int64_t a, int64_t b) +{ + return a > b ? a : b; +} + +int16_t min_s16(int16_t a, int16_t b) +{ + return a < b ? a : b; +} +int32_t min_s32(int32_t a, int32_t b) +{ + return a < b ? a : b; +} +int64_t min_s64(int64_t a, int64_t b) +{ + return a < b ? a : b; +} + +uint16_t max_u16(uint16_t a, uint16_t b) +{ + return a > b ? a : b; +} +uint32_t max_u32(uint32_t a, uint32_t b) +{ + return a > b ? a : b; +} +uint64_t max_u64(uint64_t a, uint64_t b) +{ + return a > b ? a : b; +} + +uint16_t min_u16(uint16_t a, uint16_t b) +{ + return a < b ? a : b; +} +uint32_t min_u32(uint32_t a, uint32_t b) +{ + return a < b ? a : b; +} +uint64_t min_u64(uint64_t a, uint64_t b) +{ + return a < b ? a : b; +} + +uint32_t jenkins_one_at_a_time_hash(const uint8_t *key, size_t len) +{ + uint32_t hash = 0; + + for (uint32_t i = 0; i < len; ++i) { + hash += key[i]; + hash += hash << 10; + hash ^= hash >> 6; + } + + hash += hash << 3; + hash ^= hash >> 11; + hash += hash << 15; + return hash; +} diff --git a/local_pod_repo/toxcore/toxcore/toxencryptsave/Makefile.inc b/local_pod_repo/toxcore/toxcore/toxencryptsave/Makefile.inc new file mode 100644 index 0000000..4b517a1 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxencryptsave/Makefile.inc @@ -0,0 +1,38 @@ +lib_LTLIBRARIES += libtoxencryptsave.la + +libtoxencryptsave_la_include_HEADERS = \ + ../toxencryptsave/toxencryptsave.h + +libtoxencryptsave_la_includedir = $(includedir)/tox + +if !WITH_NACL +libtoxencryptsave_la_SOURCES = ../toxencryptsave/toxencryptsave.h \ + ../toxencryptsave/toxencryptsave.c \ + ../toxencryptsave/defines.h + +libtoxencryptsave_la_CFLAGS = -I$(top_srcdir) \ + -I$(top_srcdir)/toxcore \ + $(LIBSODIUM_CFLAGS) \ + $(NACL_CFLAGS) \ + $(PTHREAD_CFLAGS) + +libtoxencryptsave_la_LDFLAGS = $(LT_LDFLAGS) \ + $(EXTRA_LT_LDFLAGS) \ + $(LIBSODIUM_LDFLAGS) \ + $(NACL_LDFLAGS) \ + $(MATH_LDFLAGS) \ + $(RT_LIBS) \ + $(WINSOCK2_LIBS) + +libtoxencryptsave_la_LIBADD = $(LIBSODIUM_LIBS) \ + $(NACL_OBJECTS) \ + $(NACL_LIBS) \ + $(PTHREAD_LIBS) \ + libtoxcore.la + +if SET_SO_VERSION + +EXTRA_libtoxencryptsave_la_DEPENDENCIES = ../so.version + +endif +endif diff --git a/local_pod_repo/toxcore/toxcore/toxencryptsave/defines.h b/local_pod_repo/toxcore/toxcore/toxencryptsave/defines.h new file mode 100644 index 0000000..71c0f10 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxencryptsave/defines.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2021 The TokTok team. + * Copyright © 2013 Tox project. + */ + +#ifndef C_TOXCORE_TOXENCRYPTSAVE_DEFINES_H +#define C_TOXCORE_TOXENCRYPTSAVE_DEFINES_H + +#define TOX_ENC_SAVE_MAGIC_NUMBER ((const uint8_t *)"toxEsave") +#define TOX_ENC_SAVE_MAGIC_LENGTH 8 + +#endif diff --git a/local_pod_repo/toxcore/toxcore/toxencryptsave/toxencryptsave.h b/local_pod_repo/toxcore/toxcore/toxencryptsave/toxencryptsave.h new file mode 100644 index 0000000..90d8255 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxencryptsave/toxencryptsave.h @@ -0,0 +1,373 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013-2016 Tox Developers. + */ + +/** + * Batch encryption functions. + */ + +#ifndef C_TOXCORE_TOXENCRYPTSAVE_TOXENCRYPTSAVE_H +#define C_TOXCORE_TOXENCRYPTSAVE_TOXENCRYPTSAVE_H + +#include +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************************* + * + * This module is organized into two parts. + * + * 1. A simple API operating on plain text/cipher text data and a password to + * encrypt or decrypt it. + * 2. A more advanced API that splits key derivation and encryption into two + * separate function calls. + * + * The first part is implemented in terms of the second part and simply calls + * the separate functions in sequence. Since key derivation is very expensive + * compared to the actual encryption, clients that do a lot of crypto should + * prefer the advanced API and reuse pass-key objects. + * + * To use the second part, first derive an encryption key from a password with + * tox_pass_key_derive, then use the derived key to encrypt the data. + * + * The encrypted data is prepended with a magic number, to aid validity + * checking (no guarantees are made of course). Any data to be decrypted must + * start with the magic number. + * + * Clients should consider alerting their users that, unlike plain data, if + * even one bit becomes corrupted, the data will be entirely unrecoverable. + * Ditto if they forget their password, there is no way to recover the data. + * + ******************************************************************************/ + + + +/** + * The size of the salt part of a pass-key. + */ +#define TOX_PASS_SALT_LENGTH 32 + +uint32_t tox_pass_salt_length(void); + +/** + * The size of the key part of a pass-key. + */ +#define TOX_PASS_KEY_LENGTH 32 + +uint32_t tox_pass_key_length(void); + +/** + * The amount of additional data required to store any encrypted byte array. + * Encrypting an array of N bytes requires N + TOX_PASS_ENCRYPTION_EXTRA_LENGTH + * bytes in the encrypted byte array. + */ +#define TOX_PASS_ENCRYPTION_EXTRA_LENGTH 80 + +uint32_t tox_pass_encryption_extra_length(void); + +typedef enum Tox_Err_Key_Derivation { + + /** + * The function returned successfully. + */ + TOX_ERR_KEY_DERIVATION_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_KEY_DERIVATION_NULL, + + /** + * The crypto lib was unable to derive a key from the given passphrase, + * which is usually a lack of memory issue. + */ + TOX_ERR_KEY_DERIVATION_FAILED, + +} Tox_Err_Key_Derivation; + + +typedef enum Tox_Err_Encryption { + + /** + * The function returned successfully. + */ + TOX_ERR_ENCRYPTION_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_ENCRYPTION_NULL, + + /** + * The crypto lib was unable to derive a key from the given passphrase, + * which is usually a lack of memory issue. The functions accepting keys + * do not produce this error. + */ + TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED, + + /** + * The encryption itself failed. + */ + TOX_ERR_ENCRYPTION_FAILED, + +} Tox_Err_Encryption; + + +typedef enum Tox_Err_Decryption { + + /** + * The function returned successfully. + */ + TOX_ERR_DECRYPTION_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_DECRYPTION_NULL, + + /** + * The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes + */ + TOX_ERR_DECRYPTION_INVALID_LENGTH, + + /** + * The input data is missing the magic number (i.e. wasn't created by this + * module, or is corrupted). + */ + TOX_ERR_DECRYPTION_BAD_FORMAT, + + /** + * The crypto lib was unable to derive a key from the given passphrase, + * which is usually a lack of memory issue. The functions accepting keys + * do not produce this error. + */ + TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED, + + /** + * The encrypted byte array could not be decrypted. Either the data was + * corrupted or the password/key was incorrect. + */ + TOX_ERR_DECRYPTION_FAILED, + +} Tox_Err_Decryption; + + + +/******************************************************************************* + * + * BEGIN PART 1 + * + * The simple API is presented first. If your code spends too much time using + * these functions, consider using the advanced functions instead and caching + * the generated pass-key. + * + ******************************************************************************/ + + + +/** + * Encrypts the given data with the given passphrase. + * + * The output array must be at least `plaintext_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH` + * bytes long. This delegates to tox_pass_key_derive and + * tox_pass_key_encrypt. + * + * @param plaintext A byte array of length `plaintext_len`. + * @param plaintext_len The length of the plain text array. Bigger than 0. + * @param passphrase The user-provided password. Can be empty. + * @param passphrase_len The length of the password. + * @param ciphertext The cipher text array to write the encrypted data to. + * + * @return true on success. + */ +bool tox_pass_encrypt(const uint8_t *plaintext, size_t plaintext_len, const uint8_t *passphrase, size_t passphrase_len, + uint8_t *ciphertext, Tox_Err_Encryption *error); + +/** + * Decrypts the given data with the given passphrase. + * + * The output array must be at least `ciphertext_len - TOX_PASS_ENCRYPTION_EXTRA_LENGTH` + * bytes long. This delegates to tox_pass_key_decrypt. + * + * @param ciphertext A byte array of length `ciphertext_len`. + * @param ciphertext_len The length of the cipher text array. At least TOX_PASS_ENCRYPTION_EXTRA_LENGTH. + * @param passphrase The user-provided password. Can be empty. + * @param passphrase_len The length of the password. + * @param plaintext The plain text array to write the decrypted data to. + * + * @return true on success. + */ +bool tox_pass_decrypt(const uint8_t *ciphertext, size_t ciphertext_len, const uint8_t *passphrase, + size_t passphrase_len, uint8_t *plaintext, Tox_Err_Decryption *error); + + +/******************************************************************************* + * + * BEGIN PART 2 + * + * And now part 2, which does the actual encryption, and can be used to write + * less CPU intensive client code than part one. + * + ******************************************************************************/ + + + +/** + * This type represents a pass-key. + * + * A pass-key and a password are two different concepts: a password is given + * by the user in plain text. A pass-key is the generated symmetric key used + * for encryption and decryption. It is derived from a salt and the + * user-provided password. + * + * The Tox_Pass_Key structure is hidden in the implementation. It can be created + * using tox_pass_key_derive or tox_pass_key_derive_with_salt and must be deallocated using tox_pass_key_free. + */ +#ifndef TOX_PASS_KEY_DEFINED +#define TOX_PASS_KEY_DEFINED +typedef struct Tox_Pass_Key Tox_Pass_Key; +#endif /* TOX_PASS_KEY_DEFINED */ + +/** + * Deallocate a Tox_Pass_Key. This function behaves like `free()`, so NULL is an + * acceptable argument value. + */ +void tox_pass_key_free(Tox_Pass_Key *key); + +/** + * Generates a secret symmetric key from the given passphrase. + * + * Be sure to not compromise the key! Only keep it in memory, do not write + * it to disk. + * + * Note that this function is not deterministic; to derive the same key from + * a password, you also must know the random salt that was used. A + * deterministic version of this function is `tox_pass_key_derive_with_salt`. + * + * @param passphrase The user-provided password. Can be empty. + * @param passphrase_len The length of the password. + * + * @return new symmetric key on success, NULL on failure. + */ +Tox_Pass_Key *tox_pass_key_derive(const uint8_t *passphrase, size_t passphrase_len, + Tox_Err_Key_Derivation *error); + +/** + * Same as above, except use the given salt for deterministic key derivation. + * + * @param passphrase The user-provided password. Can be empty. + * @param passphrase_len The length of the password. + * @param salt An array of at least TOX_PASS_SALT_LENGTH bytes. + * + * @return new symmetric key on success, NULL on failure. + */ +Tox_Pass_Key *tox_pass_key_derive_with_salt(const uint8_t *passphrase, size_t passphrase_len, + const uint8_t *salt, Tox_Err_Key_Derivation *error); + +/** + * Encrypt a plain text with a key produced by tox_pass_key_derive or tox_pass_key_derive_with_salt. + * + * The output array must be at least `plaintext_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH` + * bytes long. + * + * @param plaintext A byte array of length `plaintext_len`. + * @param plaintext_len The length of the plain text array. Bigger than 0. + * @param ciphertext The cipher text array to write the encrypted data to. + * + * @return true on success. + */ +bool tox_pass_key_encrypt(const Tox_Pass_Key *key, const uint8_t *plaintext, size_t plaintext_len, + uint8_t *ciphertext, Tox_Err_Encryption *error); + +/** + * This is the inverse of tox_pass_key_encrypt, also using only keys produced by + * tox_pass_key_derive or tox_pass_key_derive_with_salt. + * + * @param ciphertext A byte array of length `ciphertext_len`. + * @param ciphertext_len The length of the cipher text array. At least TOX_PASS_ENCRYPTION_EXTRA_LENGTH. + * @param plaintext The plain text array to write the decrypted data to. + * + * @return true on success. + */ +bool tox_pass_key_decrypt(const Tox_Pass_Key *key, const uint8_t *ciphertext, size_t ciphertext_len, + uint8_t *plaintext, Tox_Err_Decryption *error); + +typedef enum Tox_Err_Get_Salt { + + /** + * The function returned successfully. + */ + TOX_ERR_GET_SALT_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_GET_SALT_NULL, + + /** + * The input data is missing the magic number (i.e. wasn't created by this + * module, or is corrupted). + */ + TOX_ERR_GET_SALT_BAD_FORMAT, + +} Tox_Err_Get_Salt; + + +/** + * Retrieves the salt used to encrypt the given data. + * + * The retrieved salt can then be passed to tox_pass_key_derive_with_salt to + * produce the same key as was previously used. Any data encrypted with this + * module can be used as input. + * + * The cipher text must be at least TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes in length. + * The salt must be TOX_PASS_SALT_LENGTH bytes in length. + * If the passed byte arrays are smaller than required, the behaviour is + * undefined. + * + * If the cipher text pointer or the salt is NULL, this function returns false. + * + * Success does not say anything about the validity of the data, only that + * data of the appropriate size was copied. + * + * @return true on success. + */ +bool tox_get_salt(const uint8_t *ciphertext, uint8_t *salt, Tox_Err_Get_Salt *error); + +/** + * Determines whether or not the given data is encrypted by this module. + * + * It does this check by verifying that the magic number is the one put in + * place by the encryption functions. + * + * The data must be at least TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes in length. + * If the passed byte array is smaller than required, the behaviour is + * undefined. + * + * If the data pointer is NULL, the behaviour is undefined + * + * @return true if the data is encrypted by this module. + */ +bool tox_is_data_encrypted(const uint8_t *data); + + +#ifdef __cplusplus +} +#endif + +//!TOKSTYLE- + +typedef Tox_Err_Key_Derivation TOX_ERR_KEY_DERIVATION; +typedef Tox_Err_Encryption TOX_ERR_ENCRYPTION; +typedef Tox_Err_Decryption TOX_ERR_DECRYPTION; +typedef Tox_Err_Get_Salt TOX_ERR_GET_SALT; + +//!TOKSTYLE+ + +#endif // C_TOXCORE_TOXENCRYPTSAVE_TOXENCRYPTSAVE_H diff --git a/local_pod_repo/toxcore/toxcore/toxencryptsave/toxencryptsave.m b/local_pod_repo/toxcore/toxcore/toxencryptsave/toxencryptsave.m new file mode 100644 index 0000000..7df35e5 --- /dev/null +++ b/local_pod_repo/toxcore/toxcore/toxencryptsave/toxencryptsave.m @@ -0,0 +1,388 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2013 Tox project. + */ + +/** + * Batch encryption functions. + */ +#include "toxencryptsave.h" + +#include "sodium.h" + +#include +#include + +#include "../toxcore/ccompat.h" +#include "../toxcore/crypto_core.h" +#include "defines.h" + +static_assert(TOX_PASS_SALT_LENGTH == crypto_pwhash_scryptsalsa208sha256_SALTBYTES, + "TOX_PASS_SALT_LENGTH is assumed to be equal to crypto_pwhash_scryptsalsa208sha256_SALTBYTES"); +static_assert(TOX_PASS_KEY_LENGTH == CRYPTO_SHARED_KEY_SIZE, + "TOX_PASS_KEY_LENGTH is assumed to be equal to CRYPTO_SHARED_KEY_SIZE"); +static_assert(TOX_PASS_ENCRYPTION_EXTRA_LENGTH == (crypto_box_MACBYTES + crypto_box_NONCEBYTES + + crypto_pwhash_scryptsalsa208sha256_SALTBYTES + TOX_ENC_SAVE_MAGIC_LENGTH), + "TOX_PASS_ENCRYPTION_EXTRA_LENGTH is assumed to be equal to (crypto_box_MACBYTES + crypto_box_NONCEBYTES + crypto_pwhash_scryptsalsa208sha256_SALTBYTES + TOX_ENC_SAVE_MAGIC_LENGTH)"); + +#define SET_ERROR_PARAMETER(param, x) \ + do { \ + if (param != nullptr) { \ + *param = x; \ + } \ + } while (0) + +uint32_t tox_pass_salt_length(void) +{ + return TOX_PASS_SALT_LENGTH; +} +uint32_t tox_pass_key_length(void) +{ + return TOX_PASS_KEY_LENGTH; +} +uint32_t tox_pass_encryption_extra_length(void) +{ + return TOX_PASS_ENCRYPTION_EXTRA_LENGTH; +} + +struct Tox_Pass_Key { + uint8_t salt[TOX_PASS_SALT_LENGTH]; + uint8_t key[TOX_PASS_KEY_LENGTH]; +}; + +void tox_pass_key_free(Tox_Pass_Key *key) +{ + free(key); +} + +/* Clients should consider alerting their users that, unlike plain data, if even one bit + * becomes corrupted, the data will be entirely unrecoverable. + * Ditto if they forget their password, there is no way to recover the data. + */ + +/** + * Retrieves the salt used to encrypt the given data. + * + * The retrieved salt can then be passed to tox_pass_key_derive_with_salt to + * produce the same key as was previously used. Any data encrypted with this + * module can be used as input. + * + * The cipher text must be at least TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes in length. + * The salt must be TOX_PASS_SALT_LENGTH bytes in length. + * If the passed byte arrays are smaller than required, the behaviour is + * undefined. + * + * If the cipher text pointer or the salt is NULL, this function returns false. + * + * Success does not say anything about the validity of the data, only that + * data of the appropriate size was copied. + * + * @return true on success. + */ +bool tox_get_salt(const uint8_t *ciphertext, uint8_t *salt, Tox_Err_Get_Salt *error) +{ + if (ciphertext == nullptr || salt == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_GET_SALT_NULL); + return false; + } + + if (memcmp(ciphertext, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) != 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_GET_SALT_BAD_FORMAT); + return false; + } + + ciphertext += TOX_ENC_SAVE_MAGIC_LENGTH; + memcpy(salt, ciphertext, crypto_pwhash_scryptsalsa208sha256_SALTBYTES); + SET_ERROR_PARAMETER(error, TOX_ERR_GET_SALT_OK); + return true; +} + +/** + * Generates a secret symmetric key from the given passphrase. + * + * Be sure to not compromise the key! Only keep it in memory, do not write + * it to disk. + * + * Note that this function is not deterministic; to derive the same key from + * a password, you also must know the random salt that was used. A + * deterministic version of this function is `tox_pass_key_derive_with_salt`. + * + * @param passphrase The user-provided password. Can be empty. + * @param passphrase_len The length of the password. + * + * @return new symmetric key on success, NULL on failure. + */ +Tox_Pass_Key *tox_pass_key_derive(const uint8_t *passphrase, size_t passphrase_len, + Tox_Err_Key_Derivation *error) +{ + const Random *rng = system_random(); + + if (rng == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_FAILED); + return nullptr; + } + + uint8_t salt[crypto_pwhash_scryptsalsa208sha256_SALTBYTES]; + random_bytes(rng, salt, sizeof(salt)); + return tox_pass_key_derive_with_salt(passphrase, passphrase_len, salt, error); +} + +/** + * Same as above, except use the given salt for deterministic key derivation. + * + * @param passphrase The user-provided password. Can be empty. + * @param passphrase_len The length of the password. + * @param salt An array of at least TOX_PASS_SALT_LENGTH bytes. + * + * @return new symmetric key on success, NULL on failure. + */ +Tox_Pass_Key *tox_pass_key_derive_with_salt(const uint8_t *passphrase, size_t passphrase_len, + const uint8_t *salt, Tox_Err_Key_Derivation *error) +{ + if (salt == nullptr || (passphrase == nullptr && passphrase_len != 0)) { + SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_NULL); + return nullptr; + } + + uint8_t passkey[crypto_hash_sha256_BYTES]; + crypto_hash_sha256(passkey, passphrase, passphrase_len); + + uint8_t key[CRYPTO_SHARED_KEY_SIZE]; + + // Derive a key from the password + // http://doc.libsodium.org/key_derivation/README.html + // note that, according to the documentation, a generic pwhash interface will be created + // once the pwhash competition (https://password-hashing.net/) is over */ + if (crypto_pwhash_scryptsalsa208sha256( + key, sizeof(key), (char *)passkey, sizeof(passkey), salt, + crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE * 2, /* slightly stronger */ + crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE) != 0) { + /* out of memory most likely */ + SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_FAILED); + return nullptr; + } + + crypto_memzero(passkey, crypto_hash_sha256_BYTES); /* wipe plaintext pw */ + + Tox_Pass_Key *out_key = (Tox_Pass_Key *)calloc(1, sizeof(Tox_Pass_Key)); + + if (out_key == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_FAILED); + return nullptr; + } + + memcpy(out_key->salt, salt, crypto_pwhash_scryptsalsa208sha256_SALTBYTES); + memcpy(out_key->key, key, CRYPTO_SHARED_KEY_SIZE); + SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_OK); + return out_key; +} + +/** + * Encrypt a plain text with a key produced by tox_pass_key_derive or tox_pass_key_derive_with_salt. + * + * The output array must be at least `plaintext_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH` + * bytes long. + * + * @param plaintext A byte array of length `plaintext_len`. + * @param plaintext_len The length of the plain text array. Bigger than 0. + * @param ciphertext The cipher text array to write the encrypted data to. + * + * @return true on success. + */ +bool tox_pass_key_encrypt(const Tox_Pass_Key *key, const uint8_t *plaintext, size_t plaintext_len, + uint8_t *ciphertext, Tox_Err_Encryption *error) +{ + const Random *rng = system_random(); + + if (rng == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_FAILED); + return false; + } + + if (plaintext_len == 0 || plaintext == nullptr || key == nullptr || ciphertext == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_NULL); + return false; + } + + // the output data consists of, in order: + // salt, nonce, mac, enc_data + // where the mac is automatically prepended by the encrypt() + // the salt+nonce is called the prefix + // I'm not sure what else I'm supposed to do with the salt and nonce, since we + // need them to decrypt the data + + /* first add the magic number */ + memcpy(ciphertext, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH); + ciphertext += TOX_ENC_SAVE_MAGIC_LENGTH; + + /* then add the rest prefix */ + memcpy(ciphertext, key->salt, crypto_pwhash_scryptsalsa208sha256_SALTBYTES); + ciphertext += crypto_pwhash_scryptsalsa208sha256_SALTBYTES; + + uint8_t nonce[crypto_box_NONCEBYTES]; + random_nonce(rng, nonce); + memcpy(ciphertext, nonce, crypto_box_NONCEBYTES); + ciphertext += crypto_box_NONCEBYTES; + + /* now encrypt */ + if (encrypt_data_symmetric(key->key, nonce, plaintext, plaintext_len, ciphertext) + != plaintext_len + crypto_box_MACBYTES) { + SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_FAILED); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_OK); + return true; +} + +/** + * Encrypts the given data with the given passphrase. + * + * The output array must be at least `plaintext_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH` + * bytes long. This delegates to tox_pass_key_derive and + * tox_pass_key_encrypt. + * + * @param plaintext A byte array of length `plaintext_len`. + * @param plaintext_len The length of the plain text array. Bigger than 0. + * @param passphrase The user-provided password. Can be empty. + * @param passphrase_len The length of the password. + * @param ciphertext The cipher text array to write the encrypted data to. + * + * @return true on success. + */ +bool tox_pass_encrypt(const uint8_t *plaintext, size_t plaintext_len, const uint8_t *passphrase, size_t passphrase_len, + uint8_t *ciphertext, Tox_Err_Encryption *error) +{ + Tox_Err_Key_Derivation err; + Tox_Pass_Key *key = tox_pass_key_derive(passphrase, passphrase_len, &err); + + if (key == nullptr) { + if (err == TOX_ERR_KEY_DERIVATION_NULL) { + SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_NULL); + } else if (err == TOX_ERR_KEY_DERIVATION_FAILED) { + SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED); + } + + return false; + } + + const bool result = tox_pass_key_encrypt(key, plaintext, plaintext_len, ciphertext, error); + tox_pass_key_free(key); + return result; +} + +/** + * This is the inverse of tox_pass_key_encrypt, also using only keys produced by + * tox_pass_key_derive or tox_pass_key_derive_with_salt. + * + * @param ciphertext A byte array of length `ciphertext_len`. + * @param ciphertext_len The length of the cipher text array. At least TOX_PASS_ENCRYPTION_EXTRA_LENGTH. + * @param plaintext The plain text array to write the decrypted data to. + * + * @return true on success. + */ +bool tox_pass_key_decrypt(const Tox_Pass_Key *key, const uint8_t *ciphertext, size_t ciphertext_len, + uint8_t *plaintext, Tox_Err_Decryption *error) +{ + if (ciphertext_len <= TOX_PASS_ENCRYPTION_EXTRA_LENGTH) { + SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_INVALID_LENGTH); + return false; + } + + if (ciphertext == nullptr || key == nullptr || plaintext == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_NULL); + return false; + } + + if (memcmp(ciphertext, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) != 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_BAD_FORMAT); + return false; + } + + ciphertext += TOX_ENC_SAVE_MAGIC_LENGTH; + ciphertext += crypto_pwhash_scryptsalsa208sha256_SALTBYTES; // salt only affects key derivation + + const size_t decrypt_length = ciphertext_len - TOX_PASS_ENCRYPTION_EXTRA_LENGTH; + + uint8_t nonce[crypto_box_NONCEBYTES]; + memcpy(nonce, ciphertext, crypto_box_NONCEBYTES); + ciphertext += crypto_box_NONCEBYTES; + + /* decrypt the ciphertext */ + if (decrypt_data_symmetric(key->key, nonce, ciphertext, decrypt_length + crypto_box_MACBYTES, plaintext) + != decrypt_length) { + SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_FAILED); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_OK); + return true; +} + +/** + * Decrypts the given data with the given passphrase. + * + * The output array must be at least `ciphertext_len - TOX_PASS_ENCRYPTION_EXTRA_LENGTH` + * bytes long. This delegates to tox_pass_key_decrypt. + * + * @param ciphertext A byte array of length `ciphertext_len`. + * @param ciphertext_len The length of the cipher text array. At least TOX_PASS_ENCRYPTION_EXTRA_LENGTH. + * @param passphrase The user-provided password. Can be empty. + * @param passphrase_len The length of the password. + * @param plaintext The plain text array to write the decrypted data to. + * + * @return true on success. + */ +bool tox_pass_decrypt(const uint8_t *ciphertext, size_t ciphertext_len, const uint8_t *passphrase, + size_t passphrase_len, uint8_t *plaintext, Tox_Err_Decryption *error) +{ + if (ciphertext_len <= TOX_PASS_ENCRYPTION_EXTRA_LENGTH) { + SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_INVALID_LENGTH); + return false; + } + + if (ciphertext == nullptr || passphrase == nullptr || plaintext == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_NULL); + return false; + } + + if (memcmp(ciphertext, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) != 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_BAD_FORMAT); + return false; + } + + uint8_t salt[crypto_pwhash_scryptsalsa208sha256_SALTBYTES]; + memcpy(salt, ciphertext + TOX_ENC_SAVE_MAGIC_LENGTH, crypto_pwhash_scryptsalsa208sha256_SALTBYTES); + + /* derive the key */ + Tox_Pass_Key *key = tox_pass_key_derive_with_salt(passphrase, passphrase_len, salt, nullptr); + + if (key == nullptr) { + /* out of memory most likely */ + SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED); + return false; + } + + const bool result = tox_pass_key_decrypt(key, ciphertext, ciphertext_len, plaintext, error); + tox_pass_key_free(key); + return result; +} + +/** + * Determines whether or not the given data is encrypted by this module. + * + * It does this check by verifying that the magic number is the one put in + * place by the encryption functions. + * + * The data must be at least TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes in length. + * If the passed byte array is smaller than required, the behaviour is + * undefined. + * + * If the data pointer is NULL, the behaviour is undefined + * + * @return true if the data is encrypted by this module. + */ +bool tox_is_data_encrypted(const uint8_t *data) +{ + return memcmp(data, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) == 0; +} diff --git a/local_pod_repo/toxcore/vpx-ios.diff b/local_pod_repo/toxcore/vpx-ios.diff new file mode 100644 index 0000000..3259c33 --- /dev/null +++ b/local_pod_repo/toxcore/vpx-ios.diff @@ -0,0 +1,98 @@ +diff --git a/build/make/ads2gas_apple.pl b/build/make/ads2gas_apple.pl +index a82f3eb..74e45a2 100755 +--- a/build/make/ads2gas_apple.pl ++++ b/build/make/ads2gas_apple.pl +@@ -18,7 +18,7 @@ + # Usage: cat inputfile | perl ads2gas_apple.pl > outputfile + # + +-my $chromium = 0; ++my $chromium = 1; + + foreach my $arg (@ARGV) { + $chromium = 1 if ($arg eq "-chromium"); +diff --git a/build/make/configure.sh b/build/make/configure.sh +index 25c9f80..507df2f 100644 +--- a/build/make/configure.sh ++++ b/build/make/configure.sh +@@ -743,7 +743,7 @@ process_common_toolchain() { + # platforms, so use the newest one available. + case ${toolchain} in + *-darwin*) +- osx_sdk_dir="$(show_darwin_sdk_path macosx)" ++ osx_sdk_dir="$(show_darwin_sdk_path iphoneos)" + if [ -d "${osx_sdk_dir}" ]; then + add_cflags "-isysroot ${osx_sdk_dir}" + add_ldflags "-isysroot ${osx_sdk_dir}" +diff --git a/build/make/iosbuild.sh b/build/make/iosbuild.sh +index 89fa681..764e450 100755 +--- a/build/make/iosbuild.sh ++++ b/build/make/iosbuild.sh +@@ -9,10 +9,10 @@ + ## be found in the AUTHORS file in the root of the source tree. + ## + ## +-## This script generates 'VPX.framework'. An iOS app can encode and decode VPx +-## video by including 'VPX.framework'. ++## This script generates 'vpx.framework'. An iOS app can encode and decode VPx ++## video by including 'vpx.framework'. + ## +-## Run iosbuild.sh to create 'VPX.framework' in the current directory. ++## Run iosbuild.sh to create 'vpx.framework' in the current directory. + ## + set -e + devnull='> /dev/null 2>&1' +@@ -23,7 +23,7 @@ CONFIGURE_ARGS="--disable-docs + --disable-libyuv + --disable-unit-tests" + DIST_DIR="_dist" +-FRAMEWORK_DIR="VPX.framework" ++FRAMEWORK_DIR="vpx.framework" + HEADER_DIR="${FRAMEWORK_DIR}/Headers/vpx" + MAKE_JOBS=1 + SCRIPT_DIR=$(dirname "$0") +@@ -113,7 +113,7 @@ create_vpx_framework_config_shim() { + preproc_symbol=$(target_to_preproc_symbol "${target}") + printf " ${preproc_symbol}\n" >> "${config_file}" + printf "#define VPX_FRAMEWORK_TARGET \"${target}\"\n" >> "${config_file}" +- printf "#include \"VPX/vpx/${target}/vpx_config.h\"\n" >> "${config_file}" ++ printf "#include \"vpx/vpx/${target}/vpx_config.h\"\n" >> "${config_file}" + printf "#elif defined" >> "${config_file}" + mkdir "${HEADER_DIR}/${target}" + cp -p "${BUILD_ROOT}/${target}/vpx_config.h" "${HEADER_DIR}/${target}" +@@ -127,7 +127,7 @@ create_vpx_framework_config_shim() { + } + + # Configures and builds each target specified by $1, and then builds +-# VPX.framework. ++# vpx.framework. + build_framework() { + local lib_list="" + local targets="$1" +@@ -156,22 +156,22 @@ build_framework() { + cp -p "${target_dist_dir}"/include/vpx/* "${HEADER_DIR}" + + # Build the fat library. +- ${LIPO} -create ${lib_list} -output ${FRAMEWORK_DIR}/VPX ++ ${LIPO} -create ${lib_list} -output ${FRAMEWORK_DIR}/vpx + + # Create the vpx_config.h shim that allows usage of vpx_config.h from +- # within VPX.framework. ++ # within vpx.framework. + create_vpx_framework_config_shim "${targets}" + + # Copy in vpx_version.h. + cp -p "${BUILD_ROOT}/${target}/vpx_version.h" "${HEADER_DIR}" + +- vlog "Created fat library ${FRAMEWORK_DIR}/VPX containing:" ++ vlog "Created fat library ${FRAMEWORK_DIR}/vpx containing:" + for lib in ${lib_list}; do + vlog " $(echo ${lib} | awk -F / '{print $2, $NF}')" + done + + # TODO(tomfinegan): Verify that expected targets are included within +- # VPX.framework/VPX via lipo -info. ++ # vpx.framework/vpx via lipo -info. + } + + # Trap function. Cleans up the subtree used to build all targets contained in diff --git a/local_pod_repo/toxcore/vpx-osx.diff b/local_pod_repo/toxcore/vpx-osx.diff new file mode 100644 index 0000000..7604d0e --- /dev/null +++ b/local_pod_repo/toxcore/vpx-osx.diff @@ -0,0 +1,98 @@ +diff --git a/build/make/ads2gas_apple.pl b/build/make/ads2gas_apple.pl +index a82f3eb..74e45a2 100755 +--- a/build/make/ads2gas_apple.pl ++++ b/build/make/ads2gas_apple.pl +@@ -18,7 +18,7 @@ + # Usage: cat inputfile | perl ads2gas_apple.pl > outputfile + # + +-my $chromium = 0; ++my $chromium = 1; + + foreach my $arg (@ARGV) { + $chromium = 1 if ($arg eq "-chromium"); +diff --git a/build/make/iosbuild.sh b/build/make/iosbuild.sh +index 89fa681..8ceff06 100755 +--- a/build/make/iosbuild.sh ++++ b/build/make/iosbuild.sh +@@ -9,10 +9,10 @@ + ## be found in the AUTHORS file in the root of the source tree. + ## + ## +-## This script generates 'VPX.framework'. An iOS app can encode and decode VPx +-## video by including 'VPX.framework'. ++## This script generates 'vpx.framework'. An iOS app can encode and decode VPx ++## video by including 'vpx.framework'. + ## +-## Run iosbuild.sh to create 'VPX.framework' in the current directory. ++## Run iosbuild.sh to create 'vpx.framework' in the current directory. + ## + set -e + devnull='> /dev/null 2>&1' +@@ -23,18 +23,14 @@ CONFIGURE_ARGS="--disable-docs + --disable-libyuv + --disable-unit-tests" + DIST_DIR="_dist" +-FRAMEWORK_DIR="VPX.framework" ++FRAMEWORK_DIR="vpx.framework" + HEADER_DIR="${FRAMEWORK_DIR}/Headers/vpx" + MAKE_JOBS=1 + SCRIPT_DIR=$(dirname "$0") + LIBVPX_SOURCE_DIR=$(cd ${SCRIPT_DIR}/../..; pwd) +-LIPO=$(xcrun -sdk iphoneos${SDK} -find lipo) ++LIPO=$(xcrun -sdk macosx${SDK} -find lipo) + ORIG_PWD="$(pwd)" +-TARGETS="arm64-darwin-gcc +- armv7-darwin-gcc +- armv7s-darwin-gcc +- x86-iphonesimulator-gcc +- x86_64-iphonesimulator-gcc" ++TARGETS="x86_64-darwin13-gcc" + + # Configures for the target specified by $1, and invokes make with the dist + # target using $DIST_DIR as the distribution output directory. +@@ -113,7 +109,7 @@ create_vpx_framework_config_shim() { + preproc_symbol=$(target_to_preproc_symbol "${target}") + printf " ${preproc_symbol}\n" >> "${config_file}" + printf "#define VPX_FRAMEWORK_TARGET \"${target}\"\n" >> "${config_file}" +- printf "#include \"VPX/vpx/${target}/vpx_config.h\"\n" >> "${config_file}" ++ printf "#include \"vpx/vpx/${target}/vpx_config.h\"\n" >> "${config_file}" + printf "#elif defined" >> "${config_file}" + mkdir "${HEADER_DIR}/${target}" + cp -p "${BUILD_ROOT}/${target}/vpx_config.h" "${HEADER_DIR}/${target}" +@@ -127,7 +123,7 @@ create_vpx_framework_config_shim() { + } + + # Configures and builds each target specified by $1, and then builds +-# VPX.framework. ++# vpx.framework. + build_framework() { + local lib_list="" + local targets="$1" +@@ -156,22 +152,22 @@ build_framework() { + cp -p "${target_dist_dir}"/include/vpx/* "${HEADER_DIR}" + + # Build the fat library. +- ${LIPO} -create ${lib_list} -output ${FRAMEWORK_DIR}/VPX ++ ${LIPO} -create ${lib_list} -output ${FRAMEWORK_DIR}/vpx + + # Create the vpx_config.h shim that allows usage of vpx_config.h from +- # within VPX.framework. ++ # within vpx.framework. + create_vpx_framework_config_shim "${targets}" + + # Copy in vpx_version.h. + cp -p "${BUILD_ROOT}/${target}/vpx_version.h" "${HEADER_DIR}" + +- vlog "Created fat library ${FRAMEWORK_DIR}/VPX containing:" ++ vlog "Created fat library ${FRAMEWORK_DIR}/vpx containing:" + for lib in ${lib_list}; do + vlog " $(echo ${lib} | awk -F / '{print $2, $NF}')" + done + + # TODO(tomfinegan): Verify that expected targets are included within +- # VPX.framework/VPX via lipo -info. ++ # vpx.framework/vpx via lipo -info. + } + + # Trap function. Cleans up the subtree used to build all targets contained in diff --git a/pushextension/Info.plist b/pushextension/Info.plist new file mode 100644 index 0000000..7e6e493 --- /dev/null +++ b/pushextension/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + pushextension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.4.28 + CFBundleVersion + 142800 + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/pushextension/NotificationService.swift b/pushextension/NotificationService.swift new file mode 100644 index 0000000..5efff31 --- /dev/null +++ b/pushextension/NotificationService.swift @@ -0,0 +1,58 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UserNotifications + +class NotificationService: UNNotificationServiceExtension { + + static var lastRemoteNotifictionTS: Int64 = 0 + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + + var change: Bool = false + + // var diffTime = Date().millisecondsSince1970 - NotificationService.lastRemoteNotifictionTS + // print("noti:Tlast=\(NotificationService.lastRemoteNotifictionTS)") + // print("noti:Tnow=\(Date().millisecondsSince1970)") + // print("noti:Tdiff=\(diffTime)") + + // HINT: now always change message text to "connecting ..." + // check if last notification was received less than 24 seconds ago + // if (diffTime < (25 * 1000)) { + // print("noti:change=true") + change = true + // } + + NotificationService.lastRemoteNotifictionTS = Date().millisecondsSince1970 + + if let bestAttemptContent = bestAttemptContent { + if (change) { + // print("noti:actually changing") + bestAttemptContent.title = "connecting ..." + } + contentHandler(bestAttemptContent) + } + } + + override func serviceExtensionTimeWillExpire() { + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + contentHandler(bestAttemptContent) + } + } + +} + +extension Date { + var millisecondsSince1970: Int64 { + Int64((self.timeIntervalSince1970 * 1000.0).rounded()) + } + + init(milliseconds: Int64) { + self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000) + } +} diff --git a/tools/package-ida.sh b/tools/package-ida.sh new file mode 100755 index 0000000..2bf566c --- /dev/null +++ b/tools/package-ida.sh @@ -0,0 +1,56 @@ +#!/bin/bash -e + +# +# package-ipa.sh +# +# Bundles an iOS app correctly, using the same directory structure that Xcode does when using the export functionality. +# + +xcarchive="$1" +output_ipa="$2" +build_dir=$(mktemp -d '/tmp/package-ipa.XXXXXX') +echo "build_dir: $build_dir" + +if [ ! -d "${xcarchive}" ]; then + echo "Usage: package-ipa.sh /path/to/app.xcarchive /path/to/ouput.ipa" + exit 1 +fi + +echo "Packaging ${xcarchive} into ${output_ipa}" + +if [ -f "${output_ipa}" ]; then + rm "${output_ipa}" +fi + +# if [ -d "${build_dir}" ]; then +# rm -rf "${build_dir}" +# fi + +echo "Preparing folder tree for IPA" +mkdir -p "${build_dir}/Payload" + +# Copy .app into Payload dir +pushd "${xcarchive}/Products/Applications" > /dev/null +ls -l +cp -Rp ./*.app "${build_dir}/Payload" +popd > /dev/null + +# Check for and copy swift libraries +#if [ -d "${xcarchive}/SwiftSupport" ]; then +# echo "Adding Swift support dylibs" +# cp -Rp "${xcarchive}/SwiftSupport" "${build_dir}/" +#fi + +# Check for and copy WatchKit file +#if [ -d "${xcarchive}/WatchKitSupport" ]; then +# echo "Adding WatchKit support file" +# cp -Rp "${xcarchive}/WatchKitSupport" "${build_dir}/" +#fi + +echo "Zipping" +pushd "${build_dir}" > /dev/null +zip --symlinks --verbose --recurse-paths "${output_ipa}" . +popd > /dev/null + +rm -rf "${build_dir}" +echo "Created ${output_ipa}" diff --git a/tools/prepare_new_release_version.sh b/tools/prepare_new_release_version.sh new file mode 100755 index 0000000..1d3ff97 --- /dev/null +++ b/tools/prepare_new_release_version.sh @@ -0,0 +1,55 @@ +#! /bin/bash + +_HOME2_=$(dirname $0) +export _HOME2_ +_HOME_=$(cd $_HOME2_;pwd) +export _HOME_ + +basedir="$_HOME_""/../" +f1="Antidote.xcodeproj/project.pbxproj" +f2="pushextension/Info.plist" + +cd "$basedir" + +if [[ $(git status --porcelain --untracked-files=no) ]]; then + echo "ERROR: git repo has changes." + echo "please commit or cleanup the git repo." + exit 1 +else + echo "git repo clean." +fi + +cur_p_version=$(cat "$f1" | grep 'CURRENT_PROJECT_VERSION' | head -1 | \ + sed -e 's#^.*CURRENT_PROJECT_VERSION = ##' | \ + sed -e 's#;.*$##') +cur_m_version=$(cat "$f1" | grep 'MARKETING_VERSION' | head -1 | \ + sed -e 's#^.*MARKETING_VERSION = ##' | \ + sed -e 's#;.*$##') + + +# cur_p_version=149900 +# cur_m_version=1.4.99 +# cur_p_version=150000 +# cur_m_version=1.5.00 + +next_p_version=$[ $cur_p_version + 100 ] +# thanks to: https://stackoverflow.com/a/8653732 +next_m_version=$(echo "$cur_m_version"|awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{if(length($NF+1)>length($NF))$(NF-1)++; $NF=sprintf("%0*d", length($NF), ($NF+1)%(10^length($NF))); print}') + +echo $cur_p_version +echo $next_p_version + +echo $cur_m_version +echo $next_m_version + +sed -i -e 's#CURRENT_PROJECT_VERSION = .*;#CURRENT_PROJECT_VERSION = '"$next_p_version"';#g' "$f1" +sed -i -e 's#MARKETING_VERSION = .*;#MARKETING_VERSION = '"$next_m_version"';#g' "$f1" + +sed -i -e 's#'"$cur_m_version"'#'"$next_m_version"'#g' "$f2" +sed -i -e 's#'"$cur_p_version"'#'"$next_p_version"'#g' "$f2" + +commit_message="v""$next_m_version" +tag_name="$next_m_version" + +git commit -m "$commit_message" "$f1" "$f2" +git tag -a "v$next_m_version" -m "v$next_m_version"