import com.fasterxml.jackson.databind.ObjectMapper;
+import org.asamk.signal.manager.groups.GroupId;
+import org.asamk.signal.manager.groups.GroupIdV1;
+import org.asamk.signal.manager.groups.GroupIdV2;
+import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
+import org.asamk.signal.manager.groups.GroupNotFoundException;
+import org.asamk.signal.manager.groups.GroupUtils;
+import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.helper.GroupHelper;
import org.asamk.signal.manager.helper.ProfileHelper;
import org.asamk.signal.manager.helper.UnidentifiedAccessHelper;
-import org.asamk.signal.storage.SignalAccount;
-import org.asamk.signal.storage.contacts.ContactInfo;
-import org.asamk.signal.storage.groups.GroupInfo;
-import org.asamk.signal.storage.groups.GroupInfoV1;
-import org.asamk.signal.storage.groups.GroupInfoV2;
-import org.asamk.signal.storage.profiles.SignalProfile;
-import org.asamk.signal.storage.profiles.SignalProfileEntry;
-import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
-import org.asamk.signal.storage.stickers.Sticker;
-import org.asamk.signal.util.IOUtils;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.storage.SignalAccount;
+import org.asamk.signal.manager.storage.contacts.ContactInfo;
+import org.asamk.signal.manager.storage.groups.GroupInfo;
+import org.asamk.signal.manager.storage.groups.GroupInfoV1;
+import org.asamk.signal.manager.storage.groups.GroupInfoV2;
+import org.asamk.signal.manager.storage.profiles.SignalProfile;
+import org.asamk.signal.manager.storage.profiles.SignalProfileEntry;
+import org.asamk.signal.manager.storage.protocol.IdentityInfo;
+import org.asamk.signal.manager.storage.stickers.Sticker;
+import org.asamk.signal.manager.util.AttachmentUtils;
+import org.asamk.signal.manager.util.IOUtils;
+import org.asamk.signal.manager.util.KeyUtils;
+import org.asamk.signal.manager.util.MessageCacheUtils;
+import org.asamk.signal.manager.util.Utils;
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
import org.signal.libsignal.metadata.ProtocolDuplicateMessageException;
import org.signal.libsignal.metadata.ProtocolNoSessionException;
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.signal.libsignal.metadata.SelfSendException;
+import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
import org.signal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.zkgroup.profiles.ProfileKey;
import org.signal.zkgroup.profiles.ProfileKeyCredential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
+import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.api.util.SleepTimer;
import org.whispersystems.signalservice.api.util.StreamDetails;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
public class Manager implements Closeable {
+ final static Logger logger = LoggerFactory.getLogger(Manager.class);
+
private final SleepTimer timer = new UptimeSleepTimer();
+ private final CertificateValidator certificateValidator = new CertificateValidator(ServiceConfig.getUnidentifiedSenderTrustRoot());
private final SignalServiceConfiguration serviceConfiguration;
private final String userAgent;
return account.getDeviceId();
}
- private String getMessageCachePath() {
- return pathConfig.getDataPath() + "/" + account.getUsername() + ".d/msg-cache";
+ private File getMessageCachePath() {
+ return SignalAccount.getMessageCachePath(pathConfig.getDataPath(), account.getUsername());
}
- private String getMessageCachePath(String sender) {
+ private File getMessageCachePath(String sender) {
if (sender == null || sender.isEmpty()) {
return getMessageCachePath();
}
- return getMessageCachePath() + "/" + sender.replace("/", "_");
+ return new File(getMessageCachePath(), sender.replace("/", "_"));
}
private File getMessageCacheFile(String sender, long now, long timestamp) throws IOException {
- String cachePath = getMessageCachePath(sender);
+ File cachePath = getMessageCachePath(sender);
IOUtils.createPrivateDirectories(cachePath);
- return new File(cachePath + "/" + now + "_" + timestamp);
+ return new File(cachePath, now + "_" + timestamp);
}
public static Manager init(
- String username, String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent
+ String username, File settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent
) throws IOException {
PathConfig pathConfig = PathConfig.createDefault(settingsPath);
}
public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
- Utils.DeviceLinkInfo info = Utils.parseDeviceLinkUri(linkUri);
+ DeviceLinkInfo info = DeviceLinkInfo.parseDeviceLinkUri(linkUri);
addDevice(info.deviceIdentifier, info.deviceKey);
}
try {
profile = retrieveRecipientProfile(address, profileKey);
} catch (IOException e) {
- System.err.println("Failed to retrieve profile, ignoring: " + e.getMessage());
+ logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
profileEntry.setRequestPending(false);
return null;
}
profileAndCredential = profileHelper.retrieveProfileSync(address,
SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL);
} catch (IOException e) {
- System.err.println("Failed to retrieve profile key credential, ignoring: " + e.getMessage());
+ logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage());
return null;
}
? null
: retrieveProfileAvatar(address, encryptedProfile.getAvatar(), profileKey);
} catch (Throwable e) {
- System.err.println("Failed to retrieve profile avatar, ignoring: " + e.getMessage());
+ logger.warn("Failed to retrieve profile avatar, ignoring: {}", e.getMessage());
}
ProfileCipher profileCipher = new ProfileCipher(profileKey);
return Optional.absent();
}
- return Optional.of(Utils.createAttachment(file));
+ return Optional.of(AttachmentUtils.createAttachment(file));
}
private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(String number) throws IOException {
return Optional.absent();
}
- return Optional.of(Utils.createAttachment(file));
+ return Optional.of(AttachmentUtils.createAttachment(file));
}
private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.withBody(messageText);
if (attachments != null) {
- messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
+ messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments));
}
return sendGroupMessage(messageBuilder, groupId);
newE164Members.remove(contact.getNumber());
}
throw new IOException("Failed to add members "
- + Util.join(", ", newE164Members)
+ + String.join(", ", newE164Members)
+ " to group: Not registered on Signal");
}
File aFile = getGroupAvatarFile(g.getGroupId());
if (aFile.exists()) {
try {
- group.withAvatar(Utils.createAttachment(aFile));
+ group.withAvatar(AttachmentUtils.createAttachment(aFile));
} catch (IOException e) {
throw new AttachmentInvalidException(aFile.toString(), e);
}
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.withBody(messageText);
if (attachments != null) {
- List<SignalServiceAttachment> attachmentStreams = Utils.getSignalServiceAttachments(attachments);
+ List<SignalServiceAttachment> attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments);
// Upload attachments here, so we only upload once even for multiple recipients
SignalServiceMessageSender messageSender = createMessageSender();
* @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
* @return if successful, returns the URL to install the sticker pack in the signal app
*/
- public String uploadStickerPack(String path) throws IOException, StickerPackInvalidException {
+ public String uploadStickerPack(File path) throws IOException, StickerPackInvalidException {
SignalServiceStickerManifestUpload manifest = getSignalServiceStickerManifestUpload(path);
SignalServiceMessageSender messageSender = createMessageSender();
}
private SignalServiceStickerManifestUpload getSignalServiceStickerManifestUpload(
- final String path
+ final File file
) throws IOException, StickerPackInvalidException {
ZipFile zip = null;
String rootPath = null;
- final File file = new File(path);
if (file.getName().endsWith(".zip")) {
zip = new ZipFile(file);
} else if (file.getName().equals("manifest.json")) {
try {
certificate = accountManager.getSenderCertificate();
} catch (IOException e) {
- System.err.println("Failed to get sender certificate: " + e);
+ logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
return null;
}
// TODO cache for a day
missingUuids.stream().map(a -> a.getNumber().get()).collect(Collectors.toSet()),
CDS_MRENCLAVE);
} catch (IOException | Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException e) {
- System.err.println("Failed to resolve uuids from server: " + e.getMessage());
+ logger.warn("Failed to resolve uuids from server, ignoring: {}", e.getMessage());
registeredUsers = new HashMap<>();
}
private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, org.whispersystems.libsignal.UntrustedIdentityException {
SignalServiceCipher cipher = new SignalServiceCipher(account.getSelfAddress(),
account.getSignalProtocolStore(),
- Utils.getCertificateValidator());
+ certificateValidator);
try {
return cipher.decrypt(envelope);
} catch (ProtocolUntrustedIdentityException e) {
try {
retrieveGroupAvatarAttachment(avatar.asPointer(), groupV1.getGroupId());
} catch (IOException | InvalidMessageException | MissingConfigurationException e) {
- System.err.println("Failed to retrieve group avatar (" + avatar.asPointer()
- .getRemoteId() + "): " + e.getMessage());
+ logger.warn("Failed to retrieve avatar for group {}, ignoring: {}",
+ groupId.toBase64(),
+ e.getMessage());
}
}
}
}
case DELIVER:
if (groupV1 == null && !isSync) {
- actions.add(new SendGroupInfoRequestAction(source, groupV1.getGroupId()));
+ actions.add(new SendGroupInfoRequestAction(source, groupId));
}
break;
case QUIT: {
try {
retrieveAttachment(attachment.asPointer());
} catch (IOException | InvalidMessageException | MissingConfigurationException e) {
- System.err.println("Failed to retrieve attachment ("
- + attachment.asPointer().getRemoteId()
- + "): "
- + e.getMessage());
+ logger.warn("Failed to retrieve attachment ({}), ignoring: {}",
+ attachment.asPointer().getRemoteId(),
+ e.getMessage());
}
}
}
try {
retrieveAttachment(attachment);
} catch (IOException | InvalidMessageException | MissingConfigurationException e) {
- System.err.println("Failed to retrieve attachment ("
- + attachment.getRemoteId()
- + "): "
- + e.getMessage());
+ logger.warn("Failed to retrieve preview image ({}), ignoring: {}",
+ attachment.getRemoteId(),
+ e.getMessage());
}
}
}
try {
retrieveAttachment(attachment.asPointer());
} catch (IOException | InvalidMessageException | MissingConfigurationException e) {
- System.err.println("Failed to retrieve attachment ("
- + attachment.asPointer().getRemoteId()
- + "): "
- + e.getMessage());
+ logger.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}",
+ attachment.asPointer().getRemoteId(),
+ e.getMessage());
}
}
}
// Received a v2 group message for a v1 group, we need to locally migrate the group
account.getGroupStore().deleteGroup(groupInfo.getGroupId());
groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey);
- System.err.println("Locally migrated group "
- + groupInfo.getGroupId().toBase64()
- + " to group v2, id: "
- + groupInfoV2.getGroupId().toBase64()
- + " !!!");
+ logger.info("Locally migrated group {} to group v2, id: {}",
+ groupInfo.getGroupId().toBase64(),
+ groupInfoV2.getGroupId().toBase64());
} else if (groupInfo instanceof GroupInfoV2) {
groupInfoV2 = (GroupInfoV2) groupInfo;
} else {
}
if (group != null) {
storeProfileKeysFromMembers(group);
- try {
- retrieveGroupAvatar(groupId, groupSecretParams, group.getAvatar());
- } catch (IOException e) {
- System.err.println("Failed to download group avatar, ignoring ...");
+ final String avatar = group.getAvatar();
+ if (avatar != null && !avatar.isEmpty()) {
+ try {
+ retrieveGroupAvatar(groupId, groupSecretParams, avatar);
+ } catch (IOException e) {
+ logger.warn("Failed to download group avatar, ignoring: {}", e.getMessage());
+ }
}
}
groupInfoV2.setGroup(group);
private void retryFailedReceivedMessages(
ReceiveMessageHandler handler, boolean ignoreAttachments
) {
- final File cachePath = new File(getMessageCachePath());
+ final File cachePath = getMessageCachePath();
if (!cachePath.exists()) {
return;
}
) {
SignalServiceEnvelope envelope;
try {
- envelope = Utils.loadEnvelope(fileEntry);
+ envelope = MessageCacheUtils.loadEnvelope(fileEntry);
if (envelope == null) {
return;
}
try {
Files.delete(fileEntry.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage());
+ logger.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry, e.getMessage());
}
return;
}
try {
Files.delete(fileEntry.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage());
+ logger.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry, e.getMessage());
}
}
try {
String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : "";
File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp());
- Utils.storeEnvelope(envelope1, cacheFile);
+ MessageCacheUtils.storeEnvelope(envelope1, cacheFile);
} catch (IOException e) {
- System.err.println("Failed to store encrypted message in disk cache, ignoring: "
- + e.getMessage());
+ logger.warn("Failed to store encrypted message in disk cache, ignoring: {}", e.getMessage());
}
});
if (result.isPresent()) {
if (returnOnTimeout) return;
continue;
} catch (InvalidVersionException e) {
- System.err.println("Ignoring error: " + e.getMessage());
+ logger.warn("Error while receiving messages, ignoring: {}", e.getMessage());
continue;
}
cacheFile = getMessageCacheFile(source, now, envelope.getTimestamp());
Files.delete(cacheFile.toPath());
// Try to delete directory if empty
- new File(getMessageCachePath()).delete();
+ getMessageCachePath().delete();
} catch (IOException e) {
- System.err.println("Failed to delete cached message file “" + cacheFile + "”: " + e.getMessage());
+ logger.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile, e.getMessage());
}
}
}
}
}
} catch (Exception e) {
+ logger.warn("Failed to handle received sync groups “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
e.printStackTrace();
} finally {
if (tmpFile != null) {
try {
Files.delete(tmpFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete received groups temp file “"
- + tmpFile
- + "”: "
- + e.getMessage());
+ logger.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
}
}
}
try {
setGroupBlocked(groupId, true);
} catch (GroupNotFoundException e) {
- System.err.println("BlockedListMessage contained groupID that was not found in GroupStore: "
- + groupId.toBase64());
+ logger.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
+ groupId.toBase64());
}
}
}
try {
Files.delete(tmpFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete received contacts temp file “"
- + tmpFile
- + "”: "
- + e.getMessage());
+ logger.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
}
}
}
return retrieveAttachment(pointer, getContactAvatarFile(number), false);
} else {
SignalServiceAttachmentStream stream = attachment.asStream();
- return Utils.retrieveAttachment(stream, getContactAvatarFile(number));
+ return AttachmentUtils.retrieveAttachment(stream, getContactAvatarFile(number));
}
}
return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
} else {
SignalServiceAttachmentStream stream = attachment.asStream();
- return Utils.retrieveAttachment(stream, getGroupAvatarFile(groupId));
+ return AttachmentUtils.retrieveAttachment(stream, getGroupAvatarFile(groupId));
}
}
try {
Files.delete(tmpFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete received avatar temp file “" + tmpFile + "”: " + e.getMessage());
+ logger.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
}
}
return outputFile;
try {
Files.delete(tmpFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete received avatar temp file “" + tmpFile + "”: " + e.getMessage());
+ logger.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
}
}
return outputFile;
try {
Files.delete(tmpFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete received attachment temp file “"
- + tmpFile
- + "”: "
- + e.getMessage());
+ logger.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
}
}
return outputFile;
try {
Files.delete(groupsFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete groups temp file “" + groupsFile + "”: " + e.getMessage());
+ logger.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile, e.getMessage());
}
}
}
DeviceContactsOutputStream out = new DeviceContactsOutputStream(fos);
for (ContactInfo record : account.getContactStore().getContacts()) {
VerifiedMessage verifiedMessage = null;
- JsonIdentityKeyStore.Identity currentIdentity = account.getSignalProtocolStore()
- .getIdentity(record.getAddress());
+ IdentityInfo currentIdentity = account.getSignalProtocolStore().getIdentity(record.getAddress());
if (currentIdentity != null) {
verifiedMessage = new VerifiedMessage(record.getAddress(),
currentIdentity.getIdentityKey(),
try {
Files.delete(contactsFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete contacts temp file “" + contactsFile + "”: " + e.getMessage());
+ logger.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile, e.getMessage());
}
}
}
}
public ContactInfo getContact(String number) {
- return account.getContactStore().getContact(Util.getSignalServiceAddressFromIdentifier(number));
+ return account.getContactStore().getContact(Utils.getSignalServiceAddressFromIdentifier(number));
}
public GroupInfo getGroup(GroupId groupId) {
return account.getGroupStore().getGroup(groupId);
}
- public List<JsonIdentityKeyStore.Identity> getIdentities() {
+ public List<IdentityInfo> getIdentities() {
return account.getSignalProtocolStore().getIdentities();
}
- public List<JsonIdentityKeyStore.Identity> getIdentities(String number) throws InvalidNumberException {
+ public List<IdentityInfo> getIdentities(String number) throws InvalidNumberException {
return account.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number));
}
*/
public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException {
SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(name);
- List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(address);
+ List<IdentityInfo> ids = account.getSignalProtocolStore().getIdentities(address);
if (ids == null) {
return false;
}
- for (JsonIdentityKeyStore.Identity id : ids) {
+ for (IdentityInfo id : ids) {
if (!Arrays.equals(id.getIdentityKey().serialize(), fingerprint)) {
continue;
}
*/
public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException {
SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(name);
- List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(address);
+ List<IdentityInfo> ids = account.getSignalProtocolStore().getIdentities(address);
if (ids == null) {
return false;
}
- for (JsonIdentityKeyStore.Identity id : ids) {
+ for (IdentityInfo id : ids) {
if (!safetyNumber.equals(computeSafetyNumber(address, id.getIdentityKey()))) {
continue;
}
*/
public boolean trustIdentityAllKeys(String name) {
SignalServiceAddress address = resolveSignalServiceAddress(name);
- List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(address);
+ List<IdentityInfo> ids = account.getSignalProtocolStore().getIdentities(address);
if (ids == null) {
return false;
}
- for (JsonIdentityKeyStore.Identity id : ids) {
+ for (IdentityInfo id : ids) {
if (id.getTrustLevel() == TrustLevel.UNTRUSTED) {
account.getSignalProtocolStore()
.setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
public String computeSafetyNumber(
SignalServiceAddress theirAddress, IdentityKey theirIdentityKey
) {
- return Utils.computeSafetyNumber(account.getSelfAddress(),
+ return Utils.computeSafetyNumber(ServiceConfig.capabilities.isUuid(),
+ account.getSelfAddress(),
getIdentityKeyPair().getPublicKey(),
theirAddress,
theirIdentityKey);
public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
String canonicalizedNumber = UuidUtil.isUuid(identifier)
? identifier
- : Util.canonicalizeNumber(identifier, account.getUsername());
+ : PhoneNumberFormatter.formatNumber(identifier, account.getUsername());
return resolveSignalServiceAddress(canonicalizedNumber);
}
public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
- SignalServiceAddress address = Util.getSignalServiceAddressFromIdentifier(identifier);
+ SignalServiceAddress address = Utils.getSignalServiceAddressFromIdentifier(identifier);
return resolveSignalServiceAddress(address);
}