├── .dockerignore ├── .drone.yml ├── .gitignore ├── .jdk-overlay └── jre │ └── lib │ └── security │ ├── US_export_policy.jar │ └── local_policy.jar ├── Dockerfile ├── Makefile ├── Procfile ├── README.md ├── app.json ├── assembly.xml ├── config ├── sample.yml └── seed.yml ├── docs └── kubernetes │ ├── configmaps.yaml │ ├── deployments.yaml │ ├── secrets.yaml │ ├── services.yaml │ └── storage.yaml ├── pom.xml ├── protobuf ├── Makefile ├── PubSubMessage.proto ├── StoredMessage.proto └── TextSecure.proto ├── src ├── main │ ├── java │ │ └── org │ │ │ └── whispersystems │ │ │ ├── dispatch │ │ │ ├── DispatchChannel.java │ │ │ ├── DispatchManager.java │ │ │ ├── io │ │ │ │ ├── RedisInputStream.java │ │ │ │ └── RedisPubSubConnectionFactory.java │ │ │ ├── redis │ │ │ │ ├── PubSubConnection.java │ │ │ │ ├── PubSubReply.java │ │ │ │ └── protocol │ │ │ │ │ ├── ArrayReplyHeader.java │ │ │ │ │ ├── IntReply.java │ │ │ │ │ └── StringReplyHeader.java │ │ │ └── util │ │ │ │ └── Util.java │ │ │ └── textsecuregcm │ │ │ ├── WhisperServerConfiguration.java │ │ │ ├── WhisperServerService.java │ │ │ ├── auth │ │ │ ├── AccountAuthenticator.java │ │ │ ├── AuthToken.java │ │ │ ├── AuthenticationCredentials.java │ │ │ ├── AuthorizationHeader.java │ │ │ ├── AuthorizationToken.java │ │ │ ├── AuthorizationTokenGenerator.java │ │ │ ├── FederatedPeerAuthenticator.java │ │ │ ├── InvalidAuthorizationHeaderException.java │ │ │ ├── PartnerAuthenticator.java │ │ │ └── TokenAuthFilter.java │ │ │ ├── configuration │ │ │ ├── ApnConfiguration.java │ │ │ ├── FederationConfiguration.java │ │ │ ├── FirebaseConfiguration.java │ │ │ ├── GraphiteConfiguration.java │ │ │ ├── MaxDeviceConfiguration.java │ │ │ ├── MessageStoreConfiguration.java │ │ │ ├── PartnerConfiguration.java │ │ │ ├── PromMetricsConfiguration.java │ │ │ ├── PushConfiguration.java │ │ │ ├── RateLimitsConfiguration.java │ │ │ ├── RedPhoneConfiguration.java │ │ │ ├── RedisConfiguration.java │ │ │ ├── S3Configuration.java │ │ │ └── TestDeviceConfiguration.java │ │ │ ├── controllers │ │ │ ├── AccountController.java │ │ │ ├── AttachmentController.java │ │ │ ├── DeviceController.java │ │ │ ├── DeviceLimitExceededException.java │ │ │ ├── DirectoryController.java │ │ │ ├── FederationController.java │ │ │ ├── FederationControllerV2.java │ │ │ ├── HealthController.java │ │ │ ├── InvalidDestinationException.java │ │ │ ├── KeepAliveController.java │ │ │ ├── KeysController.java │ │ │ ├── MessageController.java │ │ │ ├── MismatchedDevicesException.java │ │ │ ├── NoSuchUserException.java │ │ │ ├── ProvisioningController.java │ │ │ ├── RateLimitExceededException.java │ │ │ ├── ReceiptController.java │ │ │ ├── StaleDevicesException.java │ │ │ ├── TimestampController.java │ │ │ └── ValidationException.java │ │ │ ├── entities │ │ │ ├── AccountAttributes.java │ │ │ ├── AccountCount.java │ │ │ ├── AcknowledgeWebsocketMessage.java │ │ │ ├── ApnRegistrationId.java │ │ │ ├── AttachmentDescriptor.java │ │ │ ├── AttachmentUri.java │ │ │ ├── ClientContact.java │ │ │ ├── ClientContactTokens.java │ │ │ ├── ClientContacts.java │ │ │ ├── CryptoEncodingException.java │ │ │ ├── DeviceInfo.java │ │ │ ├── DeviceInfoList.java │ │ │ ├── DeviceResponse.java │ │ │ ├── EncryptedOutgoingMessage.java │ │ │ ├── GcmRegistrationId.java │ │ │ ├── IncomingMessage.java │ │ │ ├── IncomingMessageList.java │ │ │ ├── IncomingWebsocketMessage.java │ │ │ ├── MessageProtos.java │ │ │ ├── MessageResponse.java │ │ │ ├── MismatchedDevices.java │ │ │ ├── OutgoingMessageEntity.java │ │ │ ├── OutgoingMessageEntityList.java │ │ │ ├── PreKey.java │ │ │ ├── PreKeyCount.java │ │ │ ├── PreKeyResponse.java │ │ │ ├── PreKeyResponseItem.java │ │ │ ├── PreKeyState.java │ │ │ ├── ProvisioningMessage.java │ │ │ ├── RelayMessage.java │ │ │ ├── SendMessageResponse.java │ │ │ ├── SignedPreKey.java │ │ │ ├── StaleDevices.java │ │ │ ├── UnregisteredEvent.java │ │ │ └── UnregisteredEventList.java │ │ │ ├── federation │ │ │ ├── FederatedClient.java │ │ │ ├── FederatedClientManager.java │ │ │ ├── FederatedPeer.java │ │ │ ├── NoSuchPeerException.java │ │ │ └── NonLimitedAccount.java │ │ │ ├── limits │ │ │ ├── LeakyBucket.java │ │ │ ├── RateLimiter.java │ │ │ └── RateLimiters.java │ │ │ ├── liquibase │ │ │ ├── AbstractLiquibaseCommand.java │ │ │ ├── CloseableLiquibase.java │ │ │ ├── DbMigrateCommand.java │ │ │ ├── DbStatusCommand.java │ │ │ ├── NameableDbCommand.java │ │ │ └── NameableMigrationsBundle.java │ │ │ ├── mappers │ │ │ ├── DeviceLimitExceededExceptionMapper.java │ │ │ ├── IOExceptionMapper.java │ │ │ ├── InvalidWebsocketAddressExceptionMapper.java │ │ │ └── RateLimitExceededExceptionMapper.java │ │ │ ├── metrics │ │ │ ├── CpuUsageGauge.java │ │ │ ├── FileDescriptorGauge.java │ │ │ ├── FreeMemoryGauge.java │ │ │ ├── JsonMetricsReporter.java │ │ │ ├── JsonMetricsReporterFactory.java │ │ │ ├── NetworkGauge.java │ │ │ ├── NetworkReceivedGauge.java │ │ │ └── NetworkSentGauge.java │ │ │ ├── partner │ │ │ └── Partner.java │ │ │ ├── providers │ │ │ ├── RedisClientFactory.java │ │ │ ├── RedisHealthCheck.java │ │ │ └── TimeProvider.java │ │ │ ├── push │ │ │ ├── APNSender.java │ │ │ ├── ApnFallbackManager.java │ │ │ ├── ApnMessage.java │ │ │ ├── FCMSender.java │ │ │ ├── FcmMessage.java │ │ │ ├── NotPushRegisteredException.java │ │ │ ├── PushSender.java │ │ │ ├── ReceiptSender.java │ │ │ ├── RetryingApnsClient.java │ │ │ ├── TransientPushFailureException.java │ │ │ └── WebsocketSender.java │ │ │ ├── storage │ │ │ ├── Account.java │ │ │ ├── Accounts.java │ │ │ ├── AccountsManager.java │ │ │ ├── Device.java │ │ │ ├── DirectoryManager.java │ │ │ ├── KeyRecord.java │ │ │ ├── Keys.java │ │ │ ├── Messages.java │ │ │ ├── MessagesManager.java │ │ │ ├── PendingAccounts.java │ │ │ ├── PendingAccountsManager.java │ │ │ ├── PendingDevices.java │ │ │ ├── PendingDevicesManager.java │ │ │ ├── PubSubAddress.java │ │ │ ├── PubSubManager.java │ │ │ └── PubSubProtos.java │ │ │ ├── util │ │ │ ├── Base64.java │ │ │ ├── BlockingThreadPoolExecutor.java │ │ │ ├── ByteArrayAdapter.java │ │ │ ├── Constants.java │ │ │ ├── Conversions.java │ │ │ ├── Hex.java │ │ │ ├── IterablePair.java │ │ │ ├── Pair.java │ │ │ ├── SystemMapper.java │ │ │ ├── UrlSigner.java │ │ │ ├── Util.java │ │ │ └── VerificationCode.java │ │ │ ├── websocket │ │ │ ├── AuthenticatedConnectListener.java │ │ │ ├── DeadLetterHandler.java │ │ │ ├── InvalidWebsocketAddressException.java │ │ │ ├── ProvisioningAddress.java │ │ │ ├── ProvisioningConnectListener.java │ │ │ ├── ProvisioningConnection.java │ │ │ ├── WebSocketAccountAuthenticator.java │ │ │ ├── WebSocketConnection.java │ │ │ ├── WebSocketConnectionInfo.java │ │ │ └── WebsocketAddress.java │ │ │ └── workers │ │ │ ├── DeleteUserCommand.java │ │ │ ├── DirectoryCommand.java │ │ │ ├── DirectoryUpdater.java │ │ │ ├── PeriodicStatsCommand.java │ │ │ ├── TrimMessagesCommand.java │ │ │ └── VacuumCommand.java │ └── resources │ │ ├── META-INF │ │ └── services │ │ │ └── io.dropwizard.metrics.ReporterFactory │ │ ├── accountsdb.xml │ │ ├── banner.txt │ │ └── messagedb.xml └── test │ ├── java │ └── org │ │ └── whispersystems │ │ ├── dispatch │ │ ├── DispatchManagerTest.java │ │ └── redis │ │ │ ├── PubSubConnectionTest.java │ │ │ └── protocol │ │ │ ├── ArrayReplyHeaderTest.java │ │ │ ├── IntReplyHeaderTest.java │ │ │ └── StringReplyHeaderTest.java │ │ └── textsecuregcm │ │ └── tests │ │ ├── controllers │ │ ├── AccountControllerTest.java │ │ ├── DeviceControllerTest.java │ │ ├── DirectoryControllerTest.java │ │ ├── KeyControllerTest.java │ │ ├── MessageControllerTest.java │ │ └── ReceiptControllerTest.java │ │ ├── entities │ │ ├── ClientContactTest.java │ │ └── PreKeyTest.java │ │ ├── limits │ │ └── LeakyBucketTest.java │ │ ├── push │ │ ├── APNSenderTest.java │ │ ├── ApnFallbackManagerTest.java │ │ └── ApnFallbackTaskQueueTest.java │ │ ├── storage │ │ └── AccountTest.java │ │ ├── util │ │ ├── AuthHelper.java │ │ ├── BlockingThreadPoolExecutorTest.java │ │ ├── JsonHelpers.java │ │ ├── SynchronousExecutorService.java │ │ └── UrlSignerTest.java │ │ └── websocket │ │ └── WebSocketConnectionTest.java │ └── resources │ └── fixtures │ ├── contact.json │ ├── contact.relay.json │ ├── contact.relay.voice.json │ ├── current_message_extra_device.json │ ├── current_message_multi_device.json │ ├── current_message_registration_id.json │ ├── current_message_single_device.json │ ├── legacy_message_single_device.json │ ├── mismatched_registration_id.json │ ├── missing_device_response.json │ ├── missing_device_response2.json │ ├── prekey.json │ └── prekey_v2.json └── system.properties /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | target 3 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | name: default 3 | clone: 4 | depth: 1 5 | trigger: 6 | branch: 7 | - master 8 | event: 9 | - push 10 | steps: 11 | - name: build-master 12 | image: plugins/ecr 13 | privileged: true 14 | settings: 15 | repo: 577012953595.dkr.ecr.us-west-2.amazonaws.com/signal-server 16 | registry: 577012953595.dkr.ecr.us-west-2.amazonaws.com 17 | region: us-west-2 18 | tags: commit-${DRONE_COMMIT},dev-build-${DRONE_BUILD_NUMBER} 19 | build_args: 20 | - source_version=${DRONE_COMMIT} 21 | - name: deploy 22 | image: quay.io/honestbee/drone-kubernetes 23 | settings: 24 | kubernetes_server: https://172.20.0.1 25 | kubernetes_token: 26 | from_secret: drone-deploy-token 27 | kubernetes_cert: 28 | from_secret: drone-deploy-cert 29 | deployment: signal-app 30 | repo: 577012953595.dkr.ecr.us-west-2.amazonaws.com/signal-server 31 | container: [signal-server] 32 | tag: commit-${DRONE_COMMIT} 33 | - name: notify 34 | image: plugins/webhook 35 | settings: 36 | urls: http://172.20.207.235/drone 37 | when: 38 | status: 39 | - success 40 | - failure 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | local.properties 3 | .idea 4 | *.iml 5 | run.sh 6 | *~ 7 | local.yml 8 | config/production.yml 9 | config/federated.yml 10 | config/staging.yml 11 | .opsmanage 12 | put.sh 13 | -------------------------------------------------------------------------------- /.jdk-overlay/jre/lib/security/US_export_policy.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForstaLabs/Signal-Server/2d52561a279d768e67fad6d99553a59b25e782a3/.jdk-overlay/jre/lib/security/US_export_policy.jar -------------------------------------------------------------------------------- /.jdk-overlay/jre/lib/security/local_policy.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForstaLabs/Signal-Server/2d52561a279d768e67fad6d99553a59b25e782a3/.jdk-overlay/jre/lib/security/local_policy.jar -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3-jdk-8 2 | RUN apt-get update && \ 3 | apt-get install -y make && \ 4 | apt-get clean && \ 5 | rm -rf /var/lib/apt/lists/* 6 | WORKDIR /usr/src 7 | COPY . . 8 | RUN make 9 | ENV ACCOUNT_DATABASE_URL=postgres://postgres:@db/account \ 10 | MESSAGE_DATABASE_URL=postgres://postgres:@db/message \ 11 | REDIS_URL=redis://redis:6379 12 | EXPOSE 8180 13 | CMD ["make", "run"] 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: build 2 | 3 | ACCOUNT_DATABASE_URL ?= postgres://postgres:NOPE@localhost:5432/account 4 | MESSAGE_DATABASE_URL ?= postgres://postgres:NOPE@localhost:5432/message 5 | 6 | VERSION := 1.49 7 | TARGET := target/TextSecureServer-$(VERSION).jar 8 | CONFIG := config/seed.yml 9 | JAVA := java $(JAVA_OPTS) 10 | 11 | ifdef REDIS_URL 12 | REDIS_DIR_URL=$(REDIS_URL)/0 13 | REDIS_CACHE_URL=$(REDIS_URL)/0 14 | endif 15 | 16 | get_db_username = $(shell echo $(1) | awk -F'://' {'print $$2'} | awk -F':' {'print $$1'}) 17 | get_db_password = $(shell echo $(1) | awk -F'://' {'print $$2'} | awk -F':' {'print $$2'} | awk -F'@' {'print $$1'}) 18 | get_db_host = $(shell echo $(1) | awk -F'@' {'print $$2'}) 19 | ifset = $(if $(1),$(2)="$(1)") 20 | 21 | ARGS := \ 22 | $(call ifset,$(S3_ACCESSKEY),-Ddw.s3.accessKey) \ 23 | $(call ifset,$(S3_ACCESSSECRET),-Ddw.s3.accessSecret) \ 24 | $(call ifset,$(S3_ATTACHMENTSBUCKET),-Ddw.s3.attachmentsBucket) \ 25 | $(call ifset,$(S3_ENDPOINT),-Ddw.s3.endpoint) \ 26 | $(call ifset,$(S3_REGION),-Ddw.s3.region) \ 27 | $(call ifset,$(REDIS_DIR_URL),-Ddw.directory.url) \ 28 | $(call ifset,$(REDIS_CACHE_URL),-Ddw.cache.url) \ 29 | $(call ifset,$(PORT),-Ddw.server.applicationConnectors[0].port) \ 30 | $(call ifset,$(ADMIN_PORT),-Ddw.server.adminConnectors[0].port) \ 31 | $(call ifset,$(CCSM_PARTNER_TOKEN),-Ddw.trusted.partners[0].token) \ 32 | $(call ifset,$(FIREBASE_CONFIG),-Ddw.firebase.config) \ 33 | $(call ifset,$(APN_CERT),-Ddw.apn.pushCertificate) \ 34 | $(call ifset,$(APN_KEY),-Ddw.apn.pushKey) \ 35 | $(call ifset,$(APN_BUNDLEID),-Ddw.apn.bundleId) \ 36 | $(if $(CCSM_PARTNER_TOKEN),-Ddw.trusted.partners[0].name=CCSM) \ 37 | $(if $(PROM_METRICS),-Ddw.promMetrics.enabled=true) \ 38 | $(if $(SENTRY_DSN),-Ddw.logging.appenders[0].type=sentry) \ 39 | $(if $(SENTRY_DSN),-Ddw.logging.appenders[0].threshold=ERROR) \ 40 | $(if $(SENTRY_DSN),-Ddw.logging.appenders[0].dsn=$(SENTRY_DSN)) \ 41 | $(if $(SENTRY_DSN),-Ddw.logging.appenders[0].environment=$(STACK_ENV)) \ 42 | -Ddw.database.url=jdbc:postgresql://$(call get_db_host,$(ACCOUNT_DATABASE_URL)) \ 43 | -Ddw.database.user=$(call get_db_username,$(ACCOUNT_DATABASE_URL)) \ 44 | -Ddw.database.password=$(call get_db_password,$(ACCOUNT_DATABASE_URL)) \ 45 | -Ddw.messageStore.url=jdbc:postgresql://$(call get_db_host,$(MESSAGE_DATABASE_URL)) \ 46 | -Ddw.messageStore.user=$(call get_db_username,$(MESSAGE_DATABASE_URL)) \ 47 | -Ddw.messageStore.password=$(call get_db_password,$(MESSAGE_DATABASE_URL)) 48 | 49 | 50 | RUN := $(JAVA) $(ARGS) -jar $(TARGET) 51 | 52 | build: 53 | mvn install -DskipTests 54 | 55 | clean: 56 | mvn clean 57 | 58 | test: 59 | mvn test 60 | 61 | dbmigrate: 62 | $(RUN) accountdb migrate $(CONFIG) 63 | $(RUN) messagedb migrate $(CONFIG) 64 | 65 | run: dbmigrate 66 | $(RUN) server $(CONFIG) 67 | 68 | docker-build: 69 | docker build -t textsecure-server . 70 | 71 | docker-run: 72 | docker run -it -p 8080:8080 textsecure-server 73 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: make run -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Signal-Server 2 | ================= 3 | Signal E2E message server. 4 | 5 | 6 | Building 7 | -------- 8 | 9 | make 10 | 11 | 12 | Running 13 | -------- 14 | 15 | make run 16 | 17 | 18 | 19 | Configuration 20 | -------- 21 | 22 | Configuration is achieved via setting env vars. 23 | 24 | ### Required 25 | 26 | APN_CERT 27 | APN_KEY 28 | APN_BUNDLEID 29 | 30 | GCM_APIKEY 31 | GCM_SENDERID 32 | 33 | ACCOUNT_DATABASE_URL 34 | MESSAGE_DATABASE_URL 35 | 36 | REDIS_URL 37 | 38 | S3_ACCESSKEY 39 | S3_ACCESSSECRET 40 | S3_ATTACHMENTSBUCKET 41 | 42 | ### Optional 43 | 44 | PORT 45 | ADMIN_PORT 46 | CCSM_PARTNER_TOKEN 47 | 48 | 49 | License 50 | -------- 51 | Licensed under the AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html 52 | 53 | * Copyright 2013 Open Whisper Systems 54 | * Copyright 2017-2019 Forsta Inc. 55 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TextSecure-Server", 3 | "description": "Heroku Deployment file", 4 | "scripts": { 5 | }, 6 | "env": { 7 | "APN_BUNDLEID": { 8 | "required": true 9 | }, 10 | "APN_KEY": { 11 | "required": true 12 | }, 13 | "APN_CERT": { 14 | "required": true 15 | }, 16 | "GCM_SENDERID": { 17 | "required": true 18 | }, 19 | "GCM_APIKEY": { 20 | "required": true 21 | }, 22 | "S3_ACCESSKEY": { 23 | "required": true 24 | }, 25 | "S3_ACCESSSECRET": { 26 | "required": true 27 | }, 28 | "S3_ATTACHMENTSBUCKET": { 29 | "required": true 30 | }, 31 | "S3_ENDPOINT": { 32 | "required": false 33 | } 34 | }, 35 | "addons": [{ 36 | "plan": "heroku-postgresql", 37 | "as": "ACCOUNT_DATABASE" 38 | }, { 39 | "plan": "heroku-postgresql", 40 | "as": "MESSAGE_DATABASE" 41 | }, { 42 | "plan": "heroku-redis" 43 | }] 44 | } 45 | -------------------------------------------------------------------------------- /assembly.xml: -------------------------------------------------------------------------------- 1 | 4 | bin 5 | false 6 | 7 | tar.gz 8 | 9 | 10 | 11 | ${project.basedir}/config 12 | /config 13 | 14 | * 15 | 16 | 17 | 18 | ${project.build.directory} 19 | / 20 | 21 | ${project.name}-${project.version}.jar 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /config/sample.yml: -------------------------------------------------------------------------------- 1 | push: 2 | queueSize: # Size of push pending queue 3 | 4 | cache: # Redis server configuration for cache cluster 5 | url: 6 | 7 | directory: # Redis server configuration for directory cluster 8 | url: 9 | 10 | messageStore: # Postgresql database configuration for message store 11 | driverClass: org.postgresql.Driver 12 | user: 13 | password: 14 | url: 15 | 16 | s3: # AWS S3 configuration 17 | accessKey: 18 | accessSecret: 19 | attachmentsBucket: 20 | 21 | database: # Postgresql database configuration 22 | driverClass: org.postgresql.Driver 23 | user: 24 | password: 25 | url: 26 | 27 | apn: # Apple Push Notifications configuration 28 | bundleId: 29 | pushCertificate: 30 | pushKey: 31 | 32 | gcm: # GCM Configuration 33 | senderId: 34 | apiKey: 35 | -------------------------------------------------------------------------------- /config/seed.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: INFO 3 | appenders: 4 | - type: console 5 | threshold: ERROR 6 | - type: console 7 | threshold: ALL 8 | 9 | trusted: 10 | partners: 11 | - name: PLACEHOLDER 12 | 13 | push: 14 | queueSize: 200 15 | 16 | s3: 17 | accessKey: MUST_SET_S3_ACCESSKEY 18 | accessSecret: MUST_SET_S3_ACCESSSECRET 19 | attachmentsBucket: MUST_SET_ATTACHMENTSBUCKET 20 | 21 | server: 22 | minThreads: 4 23 | applicationConnectors: 24 | - type: http 25 | port: 8180 26 | adminConnectors: 27 | - type: http 28 | port: 8181 29 | 30 | directory: 31 | url: "redis://localhost:6379/0" 32 | 33 | cache: 34 | url: "redis://localhost:6379/1" 35 | 36 | messageStore: 37 | driverClass: org.postgresql.Driver 38 | properties: 39 | charSet: UTF-8 40 | 41 | database: 42 | driverClass: org.postgresql.Driver 43 | properties: 44 | charSet: UTF-8 45 | initialSize: 4 46 | minSize: 4 47 | maxSize: 8 48 | 49 | limits: 50 | prekeys: 51 | bucketSize: 100 52 | leakRatePerMinute: 50 53 | 54 | messages: 55 | bucketSize: 1000 56 | leakRatePerMinute: 500 57 | 58 | attachments: 59 | bucketSize: 1000 60 | leakRatePerMinute: 100 61 | 62 | allocateDevice: 63 | bucketSize: 10 64 | leakRatePerMinute: 5 65 | 66 | verifyDevice: 67 | bucketSize: 10 68 | leakRatePerMinute: 5 69 | -------------------------------------------------------------------------------- /docs/kubernetes/configmaps.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: signal-app-config 5 | labels: 6 | app: signal-app 7 | data: 8 | STACK_ENV: prototype 9 | -------------------------------------------------------------------------------- /docs/kubernetes/secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | type: Opaque 4 | metadata: 5 | name: signal-secrets 6 | data: 7 | APN_BUNDLEID: MUST_PROVIDE 8 | APN_CERT: MUST_PROVIDE 9 | APN_KEY: MUST_PROVIDE 10 | CCSM_PARTNER_TOKEN: MUST_PROVIDE 11 | GCM_APIKEY: MUST_PROVIDE 12 | S3_ACCESSKEY: MUST_PROVIDE 13 | S3_ACCESSSECRET: MUST_PROVIDE 14 | S3_ATTACHMENTSBUCKET: MUST_PROVIDE 15 | SENTRY_DSN: OPTIONAL 16 | -------------------------------------------------------------------------------- /docs/kubernetes/services.yaml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: signal-db 5 | spec: 6 | selector: 7 | app: signal-db 8 | ports: 9 | - name: postgres 10 | protocol: TCP 11 | port: 5432 12 | targetPort: postgres 13 | --- 14 | kind: Service 15 | apiVersion: v1 16 | metadata: 17 | name: signal-redis 18 | spec: 19 | selector: 20 | app: signal-redis 21 | ports: 22 | - name: redis 23 | protocol: TCP 24 | port: 6379 25 | targetPort: redis 26 | --- 27 | kind: Service 28 | apiVersion: v1 29 | metadata: 30 | name: signal-app 31 | spec: 32 | ports: 33 | - name: http 34 | protocol: TCP 35 | port: 80 36 | targetPort: http 37 | selector: 38 | app: signal-app 39 | -------------------------------------------------------------------------------- /docs/kubernetes/storage.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: signal-db-all 5 | labels: 6 | app: signal-db 7 | spec: 8 | storageClassName: gp2 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 10Gi 14 | -------------------------------------------------------------------------------- /protobuf/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: 3 | protoc --java_out=../src/main/java/ TextSecure.proto PubSubMessage.proto 4 | -------------------------------------------------------------------------------- /protobuf/PubSubMessage.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | syntax = "proto2"; 19 | 20 | package textsecure; 21 | 22 | option java_package = "org.whispersystems.textsecuregcm.storage"; 23 | option java_outer_classname = "PubSubProtos"; 24 | 25 | message PubSubMessage { 26 | enum Type { 27 | UNKNOWN = 0; 28 | QUERY_DB = 1; 29 | DELIVER = 2; 30 | KEEPALIVE = 3; 31 | CLOSE = 4; 32 | CONNECTED = 5; 33 | } 34 | 35 | optional Type type = 1; 36 | optional bytes content = 2; 37 | } 38 | -------------------------------------------------------------------------------- /protobuf/StoredMessage.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | syntax = "proto2"; 19 | 20 | package textsecure; 21 | 22 | option java_package = "org.whispersystems.textsecuregcm.storage"; 23 | option java_outer_classname = "StoredMessageProtos"; 24 | 25 | message StoredMessage { 26 | enum Type { 27 | UNKNOWN = 0; 28 | MESSAGE = 1; 29 | } 30 | 31 | optional Type type = 1; 32 | optional bytes content = 2; 33 | } 34 | -------------------------------------------------------------------------------- /protobuf/TextSecure.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 - 2015 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | syntax = "proto2"; 19 | 20 | package textsecure; 21 | 22 | option java_package = "org.whispersystems.textsecuregcm.entities"; 23 | option java_outer_classname = "MessageProtos"; 24 | 25 | message Envelope { 26 | enum Type { 27 | UNKNOWN = 0; 28 | CIPHERTEXT = 1; 29 | KEY_EXCHANGE = 2; 30 | PREKEY_BUNDLE = 3; 31 | RECEIPT = 5; 32 | } 33 | 34 | optional Type type = 1; 35 | optional string source = 2; 36 | optional uint32 sourceDevice = 7; 37 | optional string relay = 3; 38 | optional uint64 timestamp = 5; 39 | optional bytes legacyMessage = 6; // Contains an encrypted DataMessage XXX -- Remove after 10/01/15 40 | optional bytes content = 8; // Contains an encrypted Content 41 | optional uint64 age = 9; // How long the message was stored on the server. 42 | optional uint64 received = 10; // Server added timestamp of when message came in. 43 | } 44 | 45 | message ProvisioningUuid { 46 | optional string uuid = 1; 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/dispatch/DispatchChannel.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.dispatch; 2 | 3 | public interface DispatchChannel { 4 | public void onDispatchMessage(String channel, byte[] message); 5 | public void onDispatchSubscribed(String channel); 6 | public void onDispatchUnsubscribed(String channel); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/dispatch/io/RedisInputStream.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.dispatch.io; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | 7 | public class RedisInputStream { 8 | 9 | private static final byte CR = 0x0D; 10 | private static final byte LF = 0x0A; 11 | 12 | private final InputStream inputStream; 13 | 14 | public RedisInputStream(InputStream inputStream) { 15 | this.inputStream = inputStream; 16 | } 17 | 18 | public String readLine() throws IOException { 19 | ByteArrayOutputStream boas = new ByteArrayOutputStream(); 20 | 21 | boolean foundCr = false; 22 | 23 | while (true) { 24 | int character = inputStream.read(); 25 | 26 | if (character == -1) { 27 | throw new IOException("Stream closed!"); 28 | } 29 | 30 | boas.write(character); 31 | 32 | if (foundCr && character == LF) break; 33 | else if (character == CR) foundCr = true; 34 | else if (foundCr) foundCr = false; 35 | } 36 | 37 | byte[] data = boas.toByteArray(); 38 | return new String(data, 0, data.length-2); 39 | } 40 | 41 | public byte[] readFully(int size) throws IOException { 42 | byte[] result = new byte[size]; 43 | int offset = 0; 44 | int remaining = result.length; 45 | 46 | while (remaining > 0) { 47 | int read = inputStream.read(result, offset, remaining); 48 | 49 | if (read < 0) { 50 | throw new IOException("Stream closed!"); 51 | } 52 | 53 | offset += read; 54 | remaining -= read; 55 | } 56 | 57 | return result; 58 | } 59 | 60 | public void close() throws IOException { 61 | inputStream.close(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/dispatch/io/RedisPubSubConnectionFactory.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.dispatch.io; 2 | 3 | import org.whispersystems.dispatch.redis.PubSubConnection; 4 | 5 | public interface RedisPubSubConnectionFactory { 6 | 7 | public PubSubConnection connect(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/dispatch/redis/PubSubReply.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.dispatch.redis; 2 | 3 | import com.google.common.base.Optional; 4 | 5 | public class PubSubReply { 6 | 7 | public enum Type { 8 | MESSAGE, 9 | SUBSCRIBE, 10 | UNSUBSCRIBE 11 | } 12 | 13 | private final Type type; 14 | private final String channel; 15 | private final Optional content; 16 | 17 | public PubSubReply(Type type, String channel, Optional content) { 18 | this.type = type; 19 | this.channel = channel; 20 | this.content = content; 21 | } 22 | 23 | public Type getType() { 24 | return type; 25 | } 26 | 27 | public String getChannel() { 28 | return channel; 29 | } 30 | 31 | public Optional getContent() { 32 | return content; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/dispatch/redis/protocol/ArrayReplyHeader.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.dispatch.redis.protocol; 2 | 3 | import java.io.IOException; 4 | 5 | public class ArrayReplyHeader { 6 | 7 | private final int elementCount; 8 | 9 | public ArrayReplyHeader(String header) throws IOException { 10 | if (header == null || header.length() < 2 || header.charAt(0) != '*') { 11 | throw new IOException("Invalid array reply header: " + header); 12 | } 13 | 14 | try { 15 | this.elementCount = Integer.parseInt(header.substring(1)); 16 | } catch (NumberFormatException e) { 17 | throw new IOException(e); 18 | } 19 | } 20 | 21 | public int getElementCount() { 22 | return elementCount; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/dispatch/redis/protocol/IntReply.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.dispatch.redis.protocol; 2 | 3 | import java.io.IOException; 4 | 5 | public class IntReply { 6 | 7 | private final int value; 8 | 9 | public IntReply(String reply) throws IOException { 10 | if (reply == null || reply.length() < 2 || reply.charAt(0) != ':') { 11 | throw new IOException("Invalid int reply: " + reply); 12 | } 13 | 14 | try { 15 | this.value = Integer.parseInt(reply.substring(1)); 16 | } catch (NumberFormatException e) { 17 | throw new IOException(e); 18 | } 19 | } 20 | 21 | public int getValue() { 22 | return value; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/dispatch/redis/protocol/StringReplyHeader.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.dispatch.redis.protocol; 2 | 3 | import java.io.IOException; 4 | 5 | public class StringReplyHeader { 6 | 7 | private final int stringLength; 8 | 9 | public StringReplyHeader(String header) throws IOException { 10 | if (header == null || header.length() < 2 || header.charAt(0) != '$') { 11 | throw new IOException("Invalid string reply header: " + header); 12 | } 13 | 14 | try { 15 | this.stringLength = Integer.parseInt(header.substring(1)); 16 | } catch (NumberFormatException e) { 17 | throw new IOException(e); 18 | } 19 | } 20 | 21 | public int getStringLength() { 22 | return stringLength; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/dispatch/util/Util.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.dispatch.util; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | 6 | public class Util { 7 | 8 | public static byte[] combine(byte[]... elements) { 9 | try { 10 | int sum = 0; 11 | 12 | for (byte[] element : elements) { 13 | sum += element.length; 14 | } 15 | 16 | ByteArrayOutputStream baos = new ByteArrayOutputStream(sum); 17 | 18 | for (byte[] element : elements) { 19 | baos.write(element); 20 | } 21 | 22 | return baos.toByteArray(); 23 | } catch (IOException e) { 24 | throw new AssertionError(e); 25 | } 26 | } 27 | 28 | 29 | public static void sleep(long millis) { 30 | try { 31 | Thread.sleep(millis); 32 | } catch (InterruptedException e) { 33 | throw new AssertionError(e); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/auth/AuthToken.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.auth; 2 | 3 | import java.util.Objects; 4 | 5 | import static java.util.Objects.requireNonNull; 6 | 7 | public class AuthToken { 8 | private final String token; 9 | 10 | public AuthToken(String token) { 11 | this.token = requireNonNull(token); 12 | } 13 | 14 | public String getToken() { 15 | return token; 16 | } 17 | 18 | public String getName() { 19 | return "AuthToken"; 20 | } 21 | 22 | public int hashCode() { 23 | return Objects.hash(token); 24 | } 25 | 26 | public boolean equals(Object obj) { 27 | if (this == obj) { 28 | return true; 29 | } 30 | if (obj == null || getClass() != obj.getClass()) { 31 | return false; 32 | } 33 | final AuthToken other = (AuthToken) obj; 34 | return Objects.equals(token, other.getToken()); 35 | } 36 | 37 | public String toString() { 38 | return token.toString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/auth/AuthenticationCredentials.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.auth; 18 | 19 | import org.apache.commons.codec.binary.Hex; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import java.io.UnsupportedEncodingException; 24 | import java.security.MessageDigest; 25 | import java.security.NoSuchAlgorithmException; 26 | import java.security.SecureRandom; 27 | 28 | public class AuthenticationCredentials { 29 | 30 | private final Logger logger = LoggerFactory.getLogger(AuthenticationCredentials.class); 31 | 32 | private final String hashedAuthenticationToken; 33 | private final String salt; 34 | 35 | public AuthenticationCredentials(String hashedAuthenticationToken, String salt) { 36 | this.hashedAuthenticationToken = hashedAuthenticationToken; 37 | this.salt = salt; 38 | } 39 | 40 | public AuthenticationCredentials(String authenticationToken) { 41 | this.salt = Math.abs(new SecureRandom().nextInt()) + ""; 42 | this.hashedAuthenticationToken = getHashedValue(salt, authenticationToken); 43 | } 44 | 45 | public String getHashedAuthenticationToken() { 46 | return hashedAuthenticationToken; 47 | } 48 | 49 | public String getSalt() { 50 | return salt; 51 | } 52 | 53 | public boolean verify(String authenticationToken) { 54 | String theirValue = getHashedValue(salt, authenticationToken); 55 | 56 | logger.debug("Comparing: " + theirValue + " , " + this.hashedAuthenticationToken); 57 | 58 | return theirValue.equals(this.hashedAuthenticationToken); 59 | } 60 | 61 | private static String getHashedValue(String salt, String token) { 62 | Logger logger = LoggerFactory.getLogger(AuthenticationCredentials.class); 63 | logger.debug("Getting hashed token: " + salt + " , " + token); 64 | 65 | try { 66 | return new String(Hex.encodeHex(MessageDigest.getInstance("SHA1").digest((salt + token).getBytes("UTF-8")))); 67 | } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { 68 | throw new AssertionError(e); 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/auth/AuthorizationToken.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.auth; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import org.apache.commons.codec.DecoderException; 6 | import org.apache.commons.codec.binary.Hex; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.whispersystems.textsecuregcm.util.Util; 10 | 11 | import javax.crypto.Mac; 12 | import javax.crypto.spec.SecretKeySpec; 13 | import java.security.InvalidKeyException; 14 | import java.security.MessageDigest; 15 | import java.security.NoSuchAlgorithmException; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | public class AuthorizationToken { 19 | 20 | @JsonProperty 21 | private String token; 22 | 23 | public AuthorizationToken(String token) { 24 | this.token = token; 25 | } 26 | 27 | public AuthorizationToken() {} 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/auth/AuthorizationTokenGenerator.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.auth; 2 | 3 | import org.apache.commons.codec.DecoderException; 4 | import org.apache.commons.codec.binary.Hex; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.whispersystems.textsecuregcm.util.Util; 8 | 9 | import javax.crypto.Mac; 10 | import javax.crypto.spec.SecretKeySpec; 11 | import java.security.InvalidKeyException; 12 | import java.security.MessageDigest; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | public class AuthorizationTokenGenerator { 17 | 18 | private final Logger logger = LoggerFactory.getLogger(AuthorizationTokenGenerator.class); 19 | 20 | private final byte[] key; 21 | 22 | public AuthorizationTokenGenerator(byte[] key) { 23 | this.key = key; 24 | } 25 | 26 | public AuthorizationToken generateFor(String number) { 27 | try { 28 | Mac mac = Mac.getInstance("HmacSHA256"); 29 | long currentTimeSeconds = System.currentTimeMillis() / 1000; 30 | String prefix = number + ":" + currentTimeSeconds; 31 | 32 | mac.init(new SecretKeySpec(key, "HmacSHA256")); 33 | String output = Hex.encodeHexString(Util.truncate(mac.doFinal(prefix.getBytes()), 10)); 34 | String token = prefix + ":" + output; 35 | 36 | return new AuthorizationToken(token); 37 | } catch (NoSuchAlgorithmException | InvalidKeyException e) { 38 | throw new AssertionError(e); 39 | } 40 | } 41 | 42 | 43 | public boolean isValid(String token, String number, long currentTimeMillis) { 44 | String[] parts = token.split(":"); 45 | 46 | if (parts.length != 3) { 47 | return false; 48 | } 49 | 50 | if (!number.equals(parts[0])) { 51 | return false; 52 | } 53 | 54 | if (!isValidTime(parts[1], currentTimeMillis)) { 55 | return false; 56 | } 57 | 58 | return isValidSignature(parts[0] + ":" + parts[1], parts[2]); 59 | } 60 | 61 | private boolean isValidTime(String timeString, long currentTimeMillis) { 62 | try { 63 | long tokenTime = Long.parseLong(timeString); 64 | long ourTime = TimeUnit.MILLISECONDS.toSeconds(currentTimeMillis); 65 | 66 | return TimeUnit.SECONDS.toHours(Math.abs(ourTime - tokenTime)) < 24; 67 | } catch (NumberFormatException e) { 68 | logger.warn("Number Format", e); 69 | return false; 70 | } 71 | } 72 | 73 | private boolean isValidSignature(String prefix, String suffix) { 74 | try { 75 | Mac hmac = Mac.getInstance("HmacSHA256"); 76 | hmac.init(new SecretKeySpec(key, "HmacSHA256")); 77 | 78 | byte[] ourSuffix = Util.truncate(hmac.doFinal(prefix.getBytes()), 10); 79 | byte[] theirSuffix = Hex.decodeHex(suffix.toCharArray()); 80 | 81 | return MessageDigest.isEqual(ourSuffix, theirSuffix); 82 | } catch (NoSuchAlgorithmException | InvalidKeyException e) { 83 | throw new AssertionError(e); 84 | } catch (DecoderException e) { 85 | logger.warn("Authorizationtoken", e); 86 | return false; 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/auth/InvalidAuthorizationHeaderException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.auth; 18 | 19 | 20 | public class InvalidAuthorizationHeaderException extends Exception { 21 | public InvalidAuthorizationHeaderException(String s) { 22 | super(s); 23 | } 24 | 25 | public InvalidAuthorizationHeaderException(Exception e) { 26 | super(e); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/auth/PartnerAuthenticator.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.auth; 2 | 3 | import com.google.common.base.Optional; 4 | import org.whispersystems.dropwizard.simpleauth.Authenticator; 5 | import org.whispersystems.textsecuregcm.configuration.PartnerConfiguration; 6 | import org.whispersystems.textsecuregcm.partner.Partner; 7 | 8 | import java.util.List; 9 | 10 | import io.dropwizard.auth.AuthenticationException; 11 | 12 | 13 | public class PartnerAuthenticator implements Authenticator { 14 | 15 | private final List partners; 16 | 17 | public PartnerAuthenticator(PartnerConfiguration config) { 18 | this.partners = config.getPartners(); 19 | } 20 | 21 | public Optional authenticate(AuthToken token) throws AuthenticationException { 22 | if (partners == null) { 23 | return Optional.absent(); 24 | } 25 | for (Partner partner : partners) { 26 | if (partner != null && token.getToken().equals(partner.getToken())) { 27 | return Optional.of(partner); 28 | } 29 | } 30 | return Optional.absent(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/auth/TokenAuthFilter.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.auth; 2 | 3 | import org.whispersystems.dropwizard.simpleauth.AuthFilter; 4 | import org.whispersystems.dropwizard.simpleauth.AuthSecurityContext; 5 | import org.whispersystems.dropwizard.simpleauth.Authenticator; 6 | import org.whispersystems.textsecuregcm.auth.AuthToken; 7 | import com.google.common.base.Optional; 8 | import com.google.common.io.BaseEncoding; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import javax.annotation.Priority; 12 | import javax.ws.rs.InternalServerErrorException; 13 | import javax.ws.rs.Priorities; 14 | import javax.ws.rs.WebApplicationException; 15 | import javax.ws.rs.container.ContainerRequestContext; 16 | import javax.ws.rs.core.HttpHeaders; 17 | import java.io.IOException; 18 | import java.nio.charset.StandardCharsets; 19 | import io.dropwizard.auth.AuthenticationException; 20 | 21 | 22 | @Priority(Priorities.AUTHENTICATION) 23 | public class TokenAuthFilter

extends AuthFilter { 24 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(TokenAuthFilter.class); 26 | protected String prefix = "Token"; 27 | protected String realm = ""; 28 | 29 | private TokenAuthFilter() {} 30 | 31 | @Override 32 | public void filter(final ContainerRequestContext requestContext) throws IOException { 33 | final String header = requestContext.getHeaders().getFirst(HttpHeaders.AUTHORIZATION); 34 | try { 35 | if (header != null) { 36 | final int space = header.indexOf(' '); 37 | if (space > 0) { 38 | final String method = header.substring(0, space); 39 | if (prefix.equalsIgnoreCase(method)) { 40 | final AuthToken token = new AuthToken(header.substring(space + 1)); 41 | try { 42 | Optional

principal = authenticator.authenticate(token); 43 | if (principal.isPresent()) { 44 | requestContext.setSecurityContext(new AuthSecurityContext

(principal.get(), false)); 45 | return; 46 | } 47 | } catch (AuthenticationException e) { 48 | LOGGER.warn("Error authenticating auth token", e); 49 | throw new InternalServerErrorException(); 50 | } 51 | } 52 | } 53 | } 54 | } catch (IllegalArgumentException e) { 55 | LOGGER.warn("Error decoding auth token", e); 56 | } 57 | throw new WebApplicationException(unauthorizedHandler.buildResponse(prefix, realm)); 58 | } 59 | 60 | public static class Builder

extends AuthFilter.AuthFilterBuilder> { 61 | 62 | @Override 63 | protected TokenAuthFilter

newInstance() { 64 | return new TokenAuthFilter<>(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/ApnConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.configuration; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | import org.hibernate.validator.constraints.NotEmpty; 21 | 22 | 23 | public class ApnConfiguration { 24 | 25 | @JsonProperty 26 | private String pushCertificate; 27 | 28 | @JsonProperty 29 | private String pushKey; 30 | 31 | @JsonProperty 32 | private String bundleId; 33 | 34 | @JsonProperty 35 | private boolean sandbox = false; 36 | 37 | public String getPushCertificate() { 38 | return pushCertificate; 39 | } 40 | 41 | public String getPushKey() { 42 | return pushKey; 43 | } 44 | 45 | public String getBundleId() { 46 | return bundleId; 47 | } 48 | 49 | public boolean isSandboxEnabled() { 50 | return sandbox; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/FederationConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.configuration; 18 | 19 | 20 | import com.fasterxml.jackson.annotation.JsonProperty; 21 | import org.whispersystems.textsecuregcm.federation.FederatedPeer; 22 | 23 | import java.util.List; 24 | 25 | public class FederationConfiguration { 26 | 27 | @JsonProperty 28 | private List peers; 29 | 30 | @JsonProperty 31 | private String name; 32 | 33 | public List getPeers() { 34 | return peers; 35 | } 36 | 37 | public String getName() { 38 | return name; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/FirebaseConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.configuration; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | import java.io.ByteArrayInputStream; 21 | import java.io.InputStream; 22 | import java.util.Base64; 23 | 24 | 25 | public class FirebaseConfiguration { 26 | @JsonProperty 27 | private String config; // Base64 encoded json config 28 | 29 | public boolean hasConfig() { 30 | return config != null; 31 | } 32 | 33 | public InputStream getStream() { 34 | return new ByteArrayInputStream(Base64.getDecoder().decode(config.getBytes())); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/GraphiteConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.configuration; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | 21 | public class GraphiteConfiguration { 22 | @JsonProperty 23 | private String host; 24 | 25 | @JsonProperty 26 | private int port; 27 | 28 | public String getHost() { 29 | return host; 30 | } 31 | 32 | public int getPort() { 33 | return port; 34 | } 35 | 36 | public boolean isEnabled() { 37 | return host != null && port != 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/MaxDeviceConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.configuration; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import org.hibernate.validator.constraints.NotEmpty; 6 | 7 | import javax.validation.constraints.NotNull; 8 | 9 | public class MaxDeviceConfiguration { 10 | 11 | @JsonProperty 12 | @NotEmpty 13 | private String number; 14 | 15 | @JsonProperty 16 | @NotNull 17 | private int count; 18 | 19 | public String getNumber() { 20 | return number; 21 | } 22 | 23 | public int getCount() { 24 | return count; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/MessageStoreConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.configuration; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import org.hibernate.validator.constraints.NotEmpty; 5 | 6 | public class MessageStoreConfiguration { 7 | @JsonProperty 8 | @NotEmpty 9 | private String url; 10 | 11 | public String getUrl() { 12 | return url; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/PartnerConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.configuration; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import org.whispersystems.textsecuregcm.partner.Partner; 5 | 6 | import java.util.List; 7 | 8 | public class PartnerConfiguration { 9 | 10 | @JsonProperty 11 | private List partners; 12 | 13 | @JsonProperty 14 | private String name; 15 | 16 | public List getPartners() { 17 | return partners; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/PromMetricsConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.configuration; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | 6 | public class PromMetricsConfiguration { 7 | @JsonProperty 8 | public boolean enabled = false; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/PushConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.configuration; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import org.hibernate.validator.constraints.NotEmpty; 5 | 6 | import javax.validation.constraints.Min; 7 | 8 | public class PushConfiguration { 9 | 10 | @JsonProperty 11 | @Min(0) 12 | private int queueSize = 200; 13 | 14 | public int getQueueSize() { 15 | return queueSize; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/RateLimitsConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.configuration; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | 21 | public class RateLimitsConfiguration { 22 | 23 | @JsonProperty 24 | private RateLimitConfiguration attachments = new RateLimitConfiguration(50, 50); 25 | 26 | @JsonProperty 27 | private RateLimitConfiguration contactQueries = new RateLimitConfiguration(50000, 50000); 28 | 29 | @JsonProperty 30 | private RateLimitConfiguration prekeys = new RateLimitConfiguration(3, 1.0 / 10.0); 31 | 32 | @JsonProperty 33 | private RateLimitConfiguration messages = new RateLimitConfiguration(60, 60); 34 | 35 | @JsonProperty 36 | private RateLimitConfiguration allocateDevice = new RateLimitConfiguration(2, 1.0 / 2.0); 37 | 38 | @JsonProperty 39 | private RateLimitConfiguration verifyDevice = new RateLimitConfiguration(2, 2); 40 | 41 | public RateLimitConfiguration getAllocateDevice() { 42 | return allocateDevice; 43 | } 44 | 45 | public RateLimitConfiguration getVerifyDevice() { 46 | return verifyDevice; 47 | } 48 | 49 | public RateLimitConfiguration getMessages() { 50 | return messages; 51 | } 52 | 53 | public RateLimitConfiguration getPreKeys() { 54 | return prekeys; 55 | } 56 | 57 | public RateLimitConfiguration getContactQueries() { 58 | return contactQueries; 59 | } 60 | 61 | public RateLimitConfiguration getAttachments() { 62 | return attachments; 63 | } 64 | 65 | public static class RateLimitConfiguration { 66 | @JsonProperty 67 | private int bucketSize; 68 | 69 | @JsonProperty 70 | private double leakRatePerMinute; 71 | 72 | public RateLimitConfiguration(int bucketSize, double leakRatePerMinute) { 73 | this.bucketSize = bucketSize; 74 | this.leakRatePerMinute = leakRatePerMinute; 75 | } 76 | 77 | public RateLimitConfiguration() {} 78 | 79 | public int getBucketSize() { 80 | return bucketSize; 81 | } 82 | 83 | public double getLeakRatePerMinute() { 84 | return leakRatePerMinute; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/RedPhoneConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.configuration; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.google.common.base.Optional; 5 | import org.apache.commons.codec.DecoderException; 6 | import org.apache.commons.codec.binary.Hex; 7 | 8 | public class RedPhoneConfiguration { 9 | 10 | @JsonProperty 11 | private String authKey; 12 | 13 | public Optional getAuthorizationKey() throws DecoderException { 14 | if (authKey == null || authKey.trim().length() == 0) { 15 | return Optional.absent(); 16 | } 17 | 18 | return Optional.of(Hex.decodeHex(authKey.toCharArray())); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/RedisConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.configuration; 18 | 19 | 20 | import com.fasterxml.jackson.annotation.JsonProperty; 21 | import org.hibernate.validator.constraints.NotEmpty; 22 | import org.hibernate.validator.constraints.URL; 23 | 24 | public class RedisConfiguration { 25 | 26 | @JsonProperty 27 | @NotEmpty 28 | private String url; 29 | 30 | public String getUrl() { 31 | return url; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/S3Configuration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.configuration; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | import org.hibernate.validator.constraints.NotEmpty; 21 | 22 | public class S3Configuration { 23 | 24 | @NotEmpty 25 | @JsonProperty 26 | private String accessKey; 27 | 28 | @NotEmpty 29 | @JsonProperty 30 | private String accessSecret; 31 | 32 | @NotEmpty 33 | @JsonProperty 34 | private String attachmentsBucket; 35 | 36 | @JsonProperty 37 | private String region; 38 | 39 | @JsonProperty 40 | private String endpoint; 41 | 42 | public String getAccessKey() { 43 | return accessKey; 44 | } 45 | 46 | public String getAccessSecret() { 47 | return accessSecret; 48 | } 49 | 50 | public String getAttachmentsBucket() { 51 | return attachmentsBucket; 52 | } 53 | 54 | public String getEndpoint() { 55 | return endpoint; 56 | } 57 | 58 | public String getRegion() { 59 | return region == null ? "us-west-2" : region; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/configuration/TestDeviceConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.configuration; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import org.hibernate.validator.constraints.NotEmpty; 5 | 6 | import javax.validation.constraints.NotNull; 7 | 8 | public class TestDeviceConfiguration { 9 | 10 | @JsonProperty 11 | @NotEmpty 12 | private String number; 13 | 14 | @JsonProperty 15 | @NotNull 16 | private int code; 17 | 18 | public String getNumber() { 19 | return number; 20 | } 21 | 22 | public int getCode() { 23 | return code; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceLimitExceededException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.controllers; 2 | 3 | 4 | public class DeviceLimitExceededException extends Exception { 5 | 6 | private final int currentDevices; 7 | private final int maxDevices; 8 | 9 | public DeviceLimitExceededException(int currentDevices, int maxDevices) { 10 | this.currentDevices = currentDevices; 11 | this.maxDevices = maxDevices; 12 | } 13 | 14 | public int getCurrentDevices() { 15 | return currentDevices; 16 | } 17 | 18 | public int getMaxDevices() { 19 | return maxDevices; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.controllers; 2 | 3 | import org.whispersystems.textsecuregcm.storage.AccountsManager; 4 | 5 | public class FederationController { 6 | 7 | protected final AccountsManager accounts; 8 | protected final AttachmentController attachmentController; 9 | protected final MessageController messageController; 10 | 11 | public FederationController(AccountsManager accounts, 12 | AttachmentController attachmentController, 13 | MessageController messageController) 14 | { 15 | this.accounts = accounts; 16 | this.attachmentController = attachmentController; 17 | this.messageController = messageController; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV2.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.controllers; 2 | 3 | import com.codahale.metrics.annotation.Timed; 4 | import com.google.common.base.Optional; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.whispersystems.textsecuregcm.entities.PreKeyResponse; 8 | import org.whispersystems.textsecuregcm.federation.FederatedPeer; 9 | import org.whispersystems.textsecuregcm.federation.NonLimitedAccount; 10 | import org.whispersystems.textsecuregcm.storage.AccountsManager; 11 | 12 | import javax.ws.rs.GET; 13 | import javax.ws.rs.Path; 14 | import javax.ws.rs.PathParam; 15 | import javax.ws.rs.Produces; 16 | import javax.ws.rs.core.MediaType; 17 | import java.io.IOException; 18 | 19 | import io.dropwizard.auth.Auth; 20 | 21 | @Path("/v2/federation") 22 | public class FederationControllerV2 extends FederationController { 23 | 24 | private final Logger logger = LoggerFactory.getLogger(FederationControllerV2.class); 25 | 26 | private final KeysController keysController; 27 | 28 | public FederationControllerV2(AccountsManager accounts, AttachmentController attachmentController, MessageController messageController, KeysController keysController) { 29 | super(accounts, attachmentController, messageController); 30 | this.keysController = keysController; 31 | } 32 | 33 | @Timed 34 | @GET 35 | @Path("/key/{number}/{device}") 36 | @Produces(MediaType.APPLICATION_JSON) 37 | public Optional getKeysV2(@Auth FederatedPeer peer, 38 | @PathParam("number") String number, 39 | @PathParam("device") String device) 40 | throws IOException 41 | { 42 | try { 43 | return keysController.getDeviceKeys(new NonLimitedAccount("Unknown", -1, peer.getName()), 44 | number, device, Optional.absent()); 45 | } catch (RateLimitExceededException e) { 46 | logger.warn("Rate limiting on federated channel", e); 47 | throw new IOException(e); 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/HealthController.java: -------------------------------------------------------------------------------- 1 | // vim: ts=2:sw=2:expandtab 2 | 3 | package org.whispersystems.textsecuregcm.controllers; 4 | 5 | import com.codahale.metrics.annotation.Timed; 6 | import javax.ws.rs.GET; 7 | import javax.ws.rs.Path; 8 | import javax.ws.rs.core.Response; 9 | 10 | 11 | @Path("/health") 12 | public class HealthController { 13 | 14 | @Timed 15 | @GET 16 | public Response getHealth() { 17 | // TODO: Check databases and perform sanity checks. 18 | return Response.ok().build(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/InvalidDestinationException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.controllers; 2 | 3 | public class InvalidDestinationException extends Exception { 4 | public InvalidDestinationException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/KeepAliveController.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.controllers; 2 | 3 | import com.codahale.metrics.annotation.Timed; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.whispersystems.textsecuregcm.storage.Account; 7 | import org.whispersystems.textsecuregcm.storage.PubSubManager; 8 | import org.whispersystems.textsecuregcm.websocket.WebsocketAddress; 9 | import org.whispersystems.websocket.session.WebSocketSession; 10 | import org.whispersystems.websocket.session.WebSocketSessionContext; 11 | 12 | import javax.ws.rs.GET; 13 | import javax.ws.rs.Path; 14 | import javax.ws.rs.core.Response; 15 | 16 | import io.dropwizard.auth.Auth; 17 | 18 | 19 | @Path("/v1/keepalive") 20 | public class KeepAliveController { 21 | 22 | private final Logger logger = LoggerFactory.getLogger(KeepAliveController.class); 23 | 24 | private final PubSubManager pubSubManager; 25 | 26 | public KeepAliveController(PubSubManager pubSubManager) { 27 | this.pubSubManager = pubSubManager; 28 | } 29 | 30 | @Timed 31 | @GET 32 | public Response getKeepAlive(@Auth Account account, 33 | @WebSocketSession WebSocketSessionContext context) 34 | { 35 | if (account != null) { 36 | WebsocketAddress address = new WebsocketAddress(account.getNumber(), 37 | account.getAuthenticatedDevice().get().getId()); 38 | 39 | if (!pubSubManager.hasLocalSubscription(address)) { 40 | logger.warn("***** No local subscription found for: " + address); 41 | context.getClient().close(1000, "OK"); 42 | } 43 | } 44 | 45 | return Response.ok().build(); 46 | } 47 | 48 | @Timed 49 | @GET 50 | @Path("/provisioning") 51 | public Response getProvisioningKeepAlive() { 52 | return Response.ok().build(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/MismatchedDevicesException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.controllers; 2 | 3 | import java.util.List; 4 | 5 | public class MismatchedDevicesException extends Exception { 6 | 7 | private final List missingDevices; 8 | private final List extraDevices; 9 | 10 | public MismatchedDevicesException(List missingDevices, List extraDevices) { 11 | this.missingDevices = missingDevices; 12 | this.extraDevices = extraDevices; 13 | } 14 | 15 | public List getMissingDevices() { 16 | return missingDevices; 17 | } 18 | 19 | public List getExtraDevices() { 20 | return extraDevices; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/NoSuchUserException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.controllers; 18 | 19 | import java.util.LinkedList; 20 | import java.util.List; 21 | 22 | public class NoSuchUserException extends Exception { 23 | 24 | private List missing; 25 | 26 | public NoSuchUserException(String user) { 27 | super(user); 28 | missing = new LinkedList<>(); 29 | missing.add(user); 30 | } 31 | 32 | public NoSuchUserException(List missing) { 33 | this.missing = missing; 34 | } 35 | 36 | public NoSuchUserException(Exception e) { 37 | super(e); 38 | } 39 | 40 | public List getMissing() { 41 | return missing; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/ProvisioningController.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.controllers; 2 | 3 | import com.codahale.metrics.annotation.Timed; 4 | import org.whispersystems.textsecuregcm.entities.ProvisioningMessage; 5 | import org.whispersystems.textsecuregcm.limits.RateLimiters; 6 | import org.whispersystems.textsecuregcm.push.PushSender; 7 | import org.whispersystems.textsecuregcm.push.WebsocketSender; 8 | import org.whispersystems.textsecuregcm.storage.Account; 9 | import org.whispersystems.textsecuregcm.util.Base64; 10 | import org.whispersystems.textsecuregcm.websocket.InvalidWebsocketAddressException; 11 | import org.whispersystems.textsecuregcm.websocket.ProvisioningAddress; 12 | 13 | import javax.validation.Valid; 14 | import javax.ws.rs.Consumes; 15 | import javax.ws.rs.PUT; 16 | import javax.ws.rs.Path; 17 | import javax.ws.rs.PathParam; 18 | import javax.ws.rs.Produces; 19 | import javax.ws.rs.WebApplicationException; 20 | import javax.ws.rs.core.MediaType; 21 | import javax.ws.rs.core.Response; 22 | import java.io.IOException; 23 | 24 | import io.dropwizard.auth.Auth; 25 | 26 | @Path("/v1/provisioning") 27 | public class ProvisioningController { 28 | 29 | private final RateLimiters rateLimiters; 30 | private final WebsocketSender websocketSender; 31 | 32 | public ProvisioningController(RateLimiters rateLimiters, PushSender pushSender) { 33 | this.rateLimiters = rateLimiters; 34 | this.websocketSender = pushSender.getWebSocketSender(); 35 | } 36 | 37 | @Timed 38 | @Path("/{destination}") 39 | @PUT 40 | @Consumes(MediaType.APPLICATION_JSON) 41 | @Produces(MediaType.APPLICATION_JSON) 42 | public void sendProvisioningMessage(@Auth Account source, 43 | @PathParam("destination") String destinationName, 44 | @Valid ProvisioningMessage message) 45 | throws RateLimitExceededException, InvalidWebsocketAddressException, IOException 46 | { 47 | rateLimiters.getMessagesLimiter().validate(source.getNumber()); 48 | 49 | if (!websocketSender.sendProvisioningMessage(new ProvisioningAddress(destinationName, 0), 50 | Base64.decode(message.getBody()))) 51 | { 52 | throw new WebApplicationException(Response.Status.NOT_FOUND); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/RateLimitExceededException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.controllers; 18 | 19 | public class RateLimitExceededException extends Exception { 20 | public RateLimitExceededException(String number) { 21 | super(number); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/ReceiptController.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.controllers; 2 | 3 | import com.codahale.metrics.annotation.Timed; 4 | import com.google.common.base.Optional; 5 | import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; 6 | import org.whispersystems.textsecuregcm.push.ReceiptSender; 7 | import org.whispersystems.textsecuregcm.push.TransientPushFailureException; 8 | import org.whispersystems.textsecuregcm.storage.Account; 9 | 10 | import javax.ws.rs.PUT; 11 | import javax.ws.rs.Path; 12 | import javax.ws.rs.PathParam; 13 | import javax.ws.rs.QueryParam; 14 | import javax.ws.rs.WebApplicationException; 15 | import javax.ws.rs.core.Response; 16 | import java.io.IOException; 17 | 18 | import io.dropwizard.auth.Auth; 19 | 20 | @Path("/v1/receipt") 21 | public class ReceiptController { 22 | 23 | private final ReceiptSender receiptSender; 24 | 25 | public ReceiptController(ReceiptSender receiptSender) { 26 | this.receiptSender = receiptSender; 27 | } 28 | 29 | @Timed 30 | @PUT 31 | @Path("/{destination}/{messageId}") 32 | public void sendDeliveryReceipt(@Auth Account source, 33 | @PathParam("destination") String destination, 34 | @PathParam("messageId") long messageId, 35 | @QueryParam("relay") Optional relay) 36 | throws IOException 37 | { 38 | try { 39 | receiptSender.sendReceipt(source, destination, messageId, relay); 40 | } catch (NoSuchUserException | NotPushRegisteredException e) { 41 | throw new WebApplicationException(Response.Status.NOT_FOUND); 42 | } catch (TransientPushFailureException e) { 43 | throw new IOException(e); 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/StaleDevicesException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.controllers; 2 | 3 | import java.util.List; 4 | 5 | 6 | public class StaleDevicesException extends Throwable { 7 | private final List staleDevices; 8 | 9 | public StaleDevicesException(List staleDevices) { 10 | this.staleDevices = staleDevices; 11 | } 12 | 13 | public List getStaleDevices() { 14 | return staleDevices; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/TimestampController.java: -------------------------------------------------------------------------------- 1 | // vim: ts=2:sw=2:expandtab 2 | 3 | package org.whispersystems.textsecuregcm.controllers; 4 | 5 | import com.codahale.metrics.annotation.Timed; 6 | import io.dropwizard.auth.Auth; 7 | import javax.ws.rs.GET; 8 | import javax.ws.rs.Path; 9 | import javax.ws.rs.Produces; 10 | import javax.ws.rs.core.MediaType; 11 | import javax.ws.rs.core.Response; 12 | import org.whispersystems.textsecuregcm.storage.Account; 13 | 14 | 15 | @Path("/v1/timestamp") 16 | public class TimestampController { 17 | 18 | @Timed 19 | @GET 20 | @Produces(MediaType.APPLICATION_JSON) 21 | public Response getTimestamp(@Auth Account account) { 22 | return Response.ok().entity(System.currentTimeMillis()).build(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/controllers/ValidationException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.controllers; 18 | 19 | 20 | public class ValidationException extends Exception { 21 | public ValidationException(String s) { 22 | super(s); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | import com.google.common.annotations.VisibleForTesting; 21 | import org.hibernate.validator.constraints.Length; 22 | import org.hibernate.validator.constraints.NotEmpty; 23 | 24 | public class AccountAttributes { 25 | 26 | @JsonProperty 27 | @NotEmpty 28 | private String signalingKey; 29 | 30 | @JsonProperty 31 | private String password; 32 | 33 | @JsonProperty 34 | private boolean fetchesMessages; 35 | 36 | @JsonProperty 37 | private int registrationId; 38 | 39 | @JsonProperty 40 | @Length(max = 50, message = "This field must be less than 50 characters") 41 | private String name; 42 | 43 | @JsonProperty 44 | private String userAgent; 45 | 46 | @JsonProperty 47 | private boolean voice; 48 | 49 | public AccountAttributes() {} 50 | 51 | @VisibleForTesting 52 | public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId) { 53 | this(signalingKey, fetchesMessages, registrationId, null, false); 54 | } 55 | 56 | @VisibleForTesting 57 | public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId, String name, boolean voice) { 58 | this.signalingKey = signalingKey; 59 | this.fetchesMessages = fetchesMessages; 60 | this.registrationId = registrationId; 61 | this.name = name; 62 | this.voice = voice; 63 | } 64 | 65 | public String getSignalingKey() { 66 | return signalingKey; 67 | } 68 | 69 | public boolean getFetchesMessages() { 70 | return fetchesMessages; 71 | } 72 | 73 | public int getRegistrationId() { 74 | return registrationId; 75 | } 76 | 77 | public String getName() { 78 | return name; 79 | } 80 | 81 | public String getUserAgent() { 82 | return userAgent; 83 | } 84 | 85 | public boolean getVoice() { 86 | return voice; 87 | } 88 | 89 | public String getPassword() { 90 | return password; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/AccountCount.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | 21 | public class AccountCount { 22 | 23 | @JsonProperty 24 | private int count; 25 | 26 | public AccountCount(int count) { 27 | this.count = count; 28 | } 29 | 30 | public AccountCount() {} 31 | 32 | public int getCount() { 33 | return count; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/AcknowledgeWebsocketMessage.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | @JsonIgnoreProperties(ignoreUnknown = true) 7 | public class AcknowledgeWebsocketMessage extends IncomingWebsocketMessage { 8 | 9 | @JsonProperty 10 | private long id; 11 | 12 | public AcknowledgeWebsocketMessage() {} 13 | 14 | public AcknowledgeWebsocketMessage(long id) { 15 | this.type = TYPE_ACKNOWLEDGE_MESSAGE; 16 | this.id = id; 17 | } 18 | 19 | public long getId() { 20 | return id; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/ApnRegistrationId.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | import org.hibernate.validator.constraints.NotEmpty; 21 | 22 | public class ApnRegistrationId { 23 | 24 | @JsonProperty 25 | @NotEmpty 26 | private String apnRegistrationId; 27 | 28 | @JsonProperty 29 | private String voipRegistrationId; 30 | 31 | public String getApnRegistrationId() { 32 | return apnRegistrationId; 33 | } 34 | 35 | public String getVoipRegistrationId() { 36 | return voipRegistrationId; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/AttachmentDescriptor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | 21 | public class AttachmentDescriptor { 22 | 23 | @JsonProperty 24 | private long id; 25 | 26 | @JsonProperty 27 | private String location; 28 | 29 | public AttachmentDescriptor(long id, String location) { 30 | this.id = id; 31 | this.location = location; 32 | } 33 | 34 | public AttachmentDescriptor() {} 35 | 36 | public long getId() { 37 | return id; 38 | } 39 | 40 | public String getLocation() { 41 | return location; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/AttachmentUri.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | 21 | import java.net.MalformedURLException; 22 | import java.net.URI; 23 | import java.net.URL; 24 | 25 | public class AttachmentUri { 26 | 27 | @JsonProperty 28 | private String location; 29 | 30 | public AttachmentUri(URL uri) { 31 | this.location = uri.toString(); 32 | } 33 | 34 | public AttachmentUri() {} 35 | 36 | public URL getLocation() throws MalformedURLException { 37 | return URI.create(location).toURL(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/ClientContact.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonInclude; 20 | import com.fasterxml.jackson.annotation.JsonProperty; 21 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 22 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 23 | import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; 24 | 25 | import java.util.Arrays; 26 | 27 | @JsonInclude(JsonInclude.Include.NON_DEFAULT) 28 | public class ClientContact { 29 | 30 | @JsonSerialize(using = ByteArrayAdapter.Serializing.class) 31 | @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) 32 | @JsonProperty 33 | private byte[] token; 34 | 35 | @JsonProperty 36 | private boolean voice; 37 | 38 | private String relay; 39 | private boolean inactive; 40 | 41 | public ClientContact(byte[] token, String relay, boolean voice) { 42 | this.token = token; 43 | this.relay = relay; 44 | this.voice = voice; 45 | } 46 | 47 | public ClientContact() {} 48 | 49 | public byte[] getToken() { 50 | return token; 51 | } 52 | 53 | public String getRelay() { 54 | return relay; 55 | } 56 | 57 | public void setRelay(String relay) { 58 | this.relay = relay; 59 | } 60 | 61 | public boolean isInactive() { 62 | return inactive; 63 | } 64 | 65 | public void setInactive(boolean inactive) { 66 | this.inactive = inactive; 67 | } 68 | 69 | public boolean isVoice() { 70 | return voice; 71 | } 72 | 73 | public void setVoice(boolean voice) { 74 | this.voice = voice; 75 | } 76 | 77 | @Override 78 | public boolean equals(Object other) { 79 | if (other == null) return false; 80 | if (!(other instanceof ClientContact)) return false; 81 | 82 | ClientContact that = (ClientContact)other; 83 | 84 | return 85 | Arrays.equals(this.token, that.token) && 86 | this.inactive == that.inactive && 87 | this.voice == that.voice && 88 | (this.relay == null ? (that.relay == null) : this.relay.equals(that.relay)); 89 | } 90 | 91 | public int hashCode() { 92 | return Arrays.hashCode(this.token); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/ClientContactTokens.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | 21 | import javax.validation.constraints.NotNull; 22 | import java.util.List; 23 | 24 | public class ClientContactTokens { 25 | 26 | @NotNull 27 | @JsonProperty 28 | private List contacts; 29 | 30 | public List getContacts() { 31 | return contacts; 32 | } 33 | 34 | public ClientContactTokens() {} 35 | 36 | public ClientContactTokens(List contacts) { 37 | this.contacts = contacts; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/ClientContacts.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | 21 | import java.util.LinkedList; 22 | import java.util.List; 23 | 24 | public class ClientContacts { 25 | 26 | @JsonProperty 27 | private List contacts; 28 | 29 | public ClientContacts(List contacts) { 30 | if (contacts != null) this.contacts = contacts; 31 | else this.contacts = new LinkedList<>(); 32 | } 33 | 34 | public ClientContacts() { 35 | this.contacts = new LinkedList<>(); 36 | } 37 | 38 | public List getContacts() { 39 | return contacts; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/CryptoEncodingException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | public class CryptoEncodingException extends Exception { 4 | 5 | public CryptoEncodingException(String s) { 6 | super(s); 7 | } 8 | 9 | public CryptoEncodingException(Exception e) { 10 | super(e); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/DeviceInfo.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class DeviceInfo { 6 | @JsonProperty 7 | private long id; 8 | 9 | @JsonProperty 10 | private String name; 11 | 12 | @JsonProperty 13 | private long lastSeen; 14 | 15 | @JsonProperty 16 | private long created; 17 | 18 | public DeviceInfo(long id, String name, long lastSeen, long created) { 19 | this.id = id; 20 | this.name = name; 21 | this.lastSeen = lastSeen; 22 | this.created = created; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/DeviceInfoList.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | public class DeviceInfoList { 8 | 9 | @JsonProperty 10 | private List devices; 11 | 12 | public DeviceInfoList(List devices) { 13 | this.devices = devices; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/DeviceResponse.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.google.common.annotations.VisibleForTesting; 5 | 6 | public class DeviceResponse { 7 | 8 | @JsonProperty 9 | private long deviceId; 10 | 11 | @VisibleForTesting 12 | public DeviceResponse() {} 13 | 14 | public DeviceResponse(long deviceId) { 15 | this.deviceId = deviceId; 16 | } 17 | 18 | public long getDeviceId() { 19 | return deviceId; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/GcmRegistrationId.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | import org.hibernate.validator.constraints.NotEmpty; 21 | 22 | public class GcmRegistrationId { 23 | 24 | @JsonProperty 25 | @NotEmpty 26 | private String gcmRegistrationId; 27 | 28 | @JsonProperty 29 | private boolean webSocketChannel; 30 | 31 | public String getGcmRegistrationId() { 32 | return gcmRegistrationId; 33 | } 34 | 35 | public boolean isWebSocketChannel() { 36 | return webSocketChannel; 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | import org.hibernate.validator.constraints.NotEmpty; 21 | 22 | public class IncomingMessage { 23 | 24 | @JsonProperty 25 | private int type; 26 | 27 | @JsonProperty 28 | private String destination; 29 | 30 | @JsonProperty 31 | private long destinationDeviceId = 1; 32 | 33 | @JsonProperty 34 | private int destinationRegistrationId; 35 | 36 | @JsonProperty 37 | private String body; 38 | 39 | @JsonProperty 40 | private String content; 41 | 42 | @JsonProperty 43 | private String relay; 44 | 45 | @JsonProperty 46 | private long timestamp; 47 | 48 | @JsonProperty 49 | private boolean silent = false; 50 | 51 | 52 | public String getDestination() { 53 | return destination; 54 | } 55 | 56 | public String getBody() { 57 | return body; 58 | } 59 | 60 | public int getType() { 61 | return type; 62 | } 63 | 64 | public String getRelay() { 65 | return relay; 66 | } 67 | 68 | public long getDestinationDeviceId() { 69 | return destinationDeviceId; 70 | } 71 | 72 | public int getDestinationRegistrationId() { 73 | return destinationRegistrationId; 74 | } 75 | 76 | public String getContent() { 77 | return content; 78 | } 79 | 80 | public boolean isSilent() { 81 | return silent; 82 | } 83 | 84 | public long getTimestamp() { 85 | return timestamp; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessageList.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | 21 | import javax.validation.Valid; 22 | import javax.validation.constraints.NotNull; 23 | import java.util.List; 24 | 25 | public class IncomingMessageList { 26 | 27 | @JsonProperty 28 | @NotNull 29 | @Valid 30 | private List messages; 31 | 32 | @JsonProperty 33 | private String relay; 34 | 35 | @JsonProperty 36 | private long timestamp; 37 | 38 | public IncomingMessageList() {} 39 | 40 | public List getMessages() { 41 | return messages; 42 | } 43 | 44 | public String getRelay() { 45 | return relay; 46 | } 47 | 48 | public void setRelay(String relay) { 49 | this.relay = relay; 50 | } 51 | 52 | public long getTimestamp() { 53 | return timestamp; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/IncomingWebsocketMessage.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | @JsonIgnoreProperties(ignoreUnknown = true) 7 | public class IncomingWebsocketMessage { 8 | 9 | public static final int TYPE_ACKNOWLEDGE_MESSAGE = 1; 10 | public static final int TYPE_PING_MESSAGE = 2; 11 | public static final int TYPE_PONG_MESSAGE = 3; 12 | 13 | @JsonProperty 14 | protected int type; 15 | 16 | public IncomingWebsocketMessage() {} 17 | 18 | public IncomingWebsocketMessage(int type) { 19 | this.type = type; 20 | } 21 | 22 | public int getType() { 23 | return type; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/MessageResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import java.util.HashSet; 20 | import java.util.LinkedList; 21 | import java.util.List; 22 | import java.util.Set; 23 | 24 | public class MessageResponse { 25 | private List success; 26 | private List failure; 27 | private Set missingDeviceIds; 28 | 29 | public MessageResponse(List success, List failure) { 30 | this.success = success; 31 | this.failure = failure; 32 | this.missingDeviceIds = new HashSet<>(); 33 | } 34 | 35 | public MessageResponse(Set missingDeviceIds) { 36 | this.success = new LinkedList<>(); 37 | this.failure = new LinkedList<>(missingDeviceIds); 38 | this.missingDeviceIds = missingDeviceIds; 39 | } 40 | 41 | public MessageResponse() {} 42 | 43 | public List getSuccess() { 44 | return success; 45 | } 46 | 47 | public void setSuccess(List success) { 48 | this.success = success; 49 | } 50 | 51 | public List getFailure() { 52 | return failure; 53 | } 54 | 55 | public void setFailure(List failure) { 56 | this.failure = failure; 57 | } 58 | 59 | public Set getNumbersMissingDevices() { 60 | return missingDeviceIds; 61 | } 62 | 63 | public void setNumbersMissingDevices(Set numbersMissingDevices) { 64 | this.missingDeviceIds = numbersMissingDevices; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/MismatchedDevices.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.google.common.annotations.VisibleForTesting; 5 | 6 | import java.util.List; 7 | 8 | public class MismatchedDevices { 9 | 10 | @JsonProperty 11 | public List missingDevices; 12 | 13 | @JsonProperty 14 | public List extraDevices; 15 | 16 | @VisibleForTesting 17 | public MismatchedDevices() {} 18 | 19 | public MismatchedDevices(List missingDevices, List extraDevices) { 20 | this.missingDevices = missingDevices; 21 | this.extraDevices = extraDevices; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/OutgoingMessageEntity.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | public class OutgoingMessageEntity { 7 | 8 | @JsonIgnore 9 | private long id; 10 | 11 | @JsonProperty 12 | private int type; 13 | 14 | @JsonProperty 15 | private String relay; 16 | 17 | @JsonProperty 18 | private long timestamp; 19 | 20 | @JsonProperty 21 | private String source; 22 | 23 | @JsonProperty 24 | private int sourceDevice; 25 | 26 | @JsonProperty 27 | private byte[] message; 28 | 29 | @JsonProperty 30 | private byte[] content; 31 | 32 | @JsonProperty 33 | private long age; 34 | 35 | @JsonProperty 36 | private long received; 37 | 38 | public OutgoingMessageEntity() {} 39 | 40 | public OutgoingMessageEntity(long id, int type, String relay, long timestamp, 41 | String source, int sourceDevice, byte[] message, 42 | byte[] content, long age, long received) 43 | { 44 | this.id = id; 45 | this.type = type; 46 | this.relay = relay; 47 | this.timestamp = timestamp; 48 | this.source = source; 49 | this.sourceDevice = sourceDevice; 50 | this.message = message; 51 | this.content = content; 52 | this.age = age; 53 | this.received = received; 54 | } 55 | 56 | public int getType() { 57 | return type; 58 | } 59 | 60 | public String getRelay() { 61 | return relay; 62 | } 63 | 64 | public long getTimestamp() { 65 | return timestamp; 66 | } 67 | 68 | public String getSource() { 69 | return source; 70 | } 71 | 72 | public int getSourceDevice() { 73 | return sourceDevice; 74 | } 75 | 76 | public byte[] getMessage() { 77 | return message; 78 | } 79 | 80 | public byte[] getContent() { 81 | return content; 82 | } 83 | 84 | public long getAge() { 85 | return age; 86 | } 87 | 88 | public long getReceived() { 89 | return received; 90 | } 91 | 92 | public long getId() { 93 | return id; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/OutgoingMessageEntityList.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.google.common.annotations.VisibleForTesting; 5 | 6 | import java.util.List; 7 | 8 | public class OutgoingMessageEntityList { 9 | 10 | @JsonProperty 11 | private List messages; 12 | 13 | @JsonProperty 14 | private boolean more; 15 | 16 | public OutgoingMessageEntityList() {} 17 | 18 | public OutgoingMessageEntityList(List messages, boolean more) { 19 | this.messages = messages; 20 | this.more = more; 21 | } 22 | 23 | public List getMessages() { 24 | return messages; 25 | } 26 | 27 | public boolean hasMore() { 28 | return more; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/PreKey.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | /** 4 | * Copyright (C) 2014 Open Whisper Systems 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | import com.fasterxml.jackson.annotation.JsonProperty; 21 | import org.hibernate.validator.constraints.NotEmpty; 22 | 23 | import javax.validation.constraints.NotNull; 24 | 25 | public class PreKey { 26 | 27 | @JsonProperty 28 | @NotNull 29 | private long keyId; 30 | 31 | @JsonProperty 32 | @NotEmpty 33 | private String publicKey; 34 | 35 | public PreKey() {} 36 | 37 | public PreKey(long keyId, String publicKey) 38 | { 39 | this.keyId = keyId; 40 | this.publicKey = publicKey; 41 | } 42 | 43 | public String getPublicKey() { 44 | return publicKey; 45 | } 46 | 47 | public void setPublicKey(String publicKey) { 48 | this.publicKey = publicKey; 49 | } 50 | 51 | public long getKeyId() { 52 | return keyId; 53 | } 54 | 55 | public void setKeyId(long keyId) { 56 | this.keyId = keyId; 57 | } 58 | 59 | @Override 60 | public boolean equals(Object object) { 61 | if (object == null || !(object instanceof PreKey)) return false; 62 | PreKey that = (PreKey)object; 63 | 64 | if (publicKey == null) { 65 | return this.keyId == that.keyId && that.publicKey == null; 66 | } else { 67 | return this.keyId == that.keyId && this.publicKey.equals(that.publicKey); 68 | } 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | if (publicKey == null) { 74 | return (int)this.keyId; 75 | } else { 76 | return ((int)this.keyId) ^ publicKey.hashCode(); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyCount.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | public class PreKeyCount { 7 | 8 | @JsonProperty 9 | private int count; 10 | 11 | public PreKeyCount(int count) { 12 | this.count = count; 13 | } 14 | 15 | public PreKeyCount() {} 16 | 17 | public int getCount() { 18 | return count; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonIgnore; 20 | import com.fasterxml.jackson.annotation.JsonProperty; 21 | import com.google.common.annotations.VisibleForTesting; 22 | 23 | import java.util.List; 24 | 25 | public class PreKeyResponse { 26 | 27 | @JsonProperty 28 | private String identityKey; 29 | 30 | @JsonProperty 31 | private List devices; 32 | 33 | public PreKeyResponse() {} 34 | 35 | public PreKeyResponse(String identityKey, List devices) { 36 | this.identityKey = identityKey; 37 | this.devices = devices; 38 | } 39 | 40 | @VisibleForTesting 41 | public String getIdentityKey() { 42 | return identityKey; 43 | } 44 | 45 | @VisibleForTesting 46 | @JsonIgnore 47 | public PreKeyResponseItem getDevice(int deviceId) { 48 | for (PreKeyResponseItem device : devices) { 49 | if (device.getDeviceId() == deviceId) return device; 50 | } 51 | 52 | return null; 53 | } 54 | 55 | @VisibleForTesting 56 | @JsonIgnore 57 | public int getDevicesCount() { 58 | return devices.size(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyResponseItem.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | import com.google.common.annotations.VisibleForTesting; 21 | 22 | public class PreKeyResponseItem { 23 | 24 | @JsonProperty 25 | private long deviceId; 26 | 27 | @JsonProperty 28 | private int registrationId; 29 | 30 | @JsonProperty 31 | private SignedPreKey signedPreKey; 32 | 33 | @JsonProperty 34 | private PreKey preKey; 35 | 36 | public PreKeyResponseItem() {} 37 | 38 | public PreKeyResponseItem(long deviceId, int registrationId, SignedPreKey signedPreKey, PreKey preKey) { 39 | this.deviceId = deviceId; 40 | this.registrationId = registrationId; 41 | this.signedPreKey = signedPreKey; 42 | this.preKey = preKey; 43 | } 44 | 45 | @VisibleForTesting 46 | public SignedPreKey getSignedPreKey() { 47 | return signedPreKey; 48 | } 49 | 50 | @VisibleForTesting 51 | public PreKey getPreKey() { 52 | return preKey; 53 | } 54 | 55 | @VisibleForTesting 56 | public int getRegistrationId() { 57 | return registrationId; 58 | } 59 | 60 | @VisibleForTesting 61 | public long getDeviceId() { 62 | return deviceId; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyState.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | import com.google.common.annotations.VisibleForTesting; 21 | 22 | import org.hibernate.validator.constraints.NotEmpty; 23 | 24 | import javax.validation.Valid; 25 | import javax.validation.constraints.NotNull; 26 | import java.util.List; 27 | 28 | public class PreKeyState { 29 | 30 | @JsonProperty 31 | @NotNull 32 | @Valid 33 | private List preKeys; 34 | 35 | @JsonProperty 36 | @NotNull 37 | @Valid 38 | private SignedPreKey signedPreKey; 39 | 40 | @JsonProperty 41 | @NotEmpty 42 | private String identityKey; 43 | 44 | public PreKeyState() {} 45 | 46 | @VisibleForTesting 47 | public PreKeyState(String identityKey, SignedPreKey signedPreKey, List keys) { 48 | this.identityKey = identityKey; 49 | this.signedPreKey = signedPreKey; 50 | this.preKeys = keys; 51 | } 52 | 53 | public List getPreKeys() { 54 | return preKeys; 55 | } 56 | 57 | public SignedPreKey getSignedPreKey() { 58 | return signedPreKey; 59 | } 60 | 61 | public String getIdentityKey() { 62 | return identityKey; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/ProvisioningMessage.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import org.hibernate.validator.constraints.NotEmpty; 5 | 6 | public class ProvisioningMessage { 7 | 8 | @JsonProperty 9 | @NotEmpty 10 | private String body; 11 | 12 | public String getBody() { 13 | return body; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/RelayMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.entities; 18 | 19 | 20 | import com.fasterxml.jackson.annotation.JsonProperty; 21 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 22 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 23 | import org.hibernate.validator.constraints.NotEmpty; 24 | import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; 25 | 26 | import javax.validation.constraints.NotNull; 27 | import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; 28 | 29 | public class RelayMessage { 30 | 31 | @JsonProperty 32 | @NotEmpty 33 | private String destination; 34 | 35 | @JsonProperty 36 | @NotEmpty 37 | private long destinationDeviceId; 38 | 39 | @JsonProperty 40 | @NotNull 41 | @JsonSerialize(using = ByteArrayAdapter.Serializing.class) 42 | @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) 43 | private byte[] outgoingMessageSignal; 44 | 45 | public RelayMessage() {} 46 | 47 | public RelayMessage(String destination, long destinationDeviceId, byte[] outgoingMessageSignal) { 48 | this.destination = destination; 49 | this.destinationDeviceId = destinationDeviceId; 50 | this.outgoingMessageSignal = outgoingMessageSignal; 51 | } 52 | 53 | public String getDestination() { 54 | return destination; 55 | } 56 | 57 | public long getDestinationDeviceId() { 58 | return destinationDeviceId; 59 | } 60 | 61 | public byte[] getOutgoingMessageSignal() { 62 | return outgoingMessageSignal; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/SendMessageResponse.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class SendMessageResponse { 6 | 7 | @JsonProperty 8 | private boolean needsSync; 9 | 10 | @JsonProperty 11 | private long received; 12 | 13 | public SendMessageResponse() {} 14 | 15 | public SendMessageResponse(boolean needsSync) { 16 | this.needsSync = needsSync; 17 | this.received = System.currentTimeMillis(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/SignedPreKey.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import org.hibernate.validator.constraints.NotEmpty; 5 | 6 | public class SignedPreKey extends PreKey { 7 | 8 | @JsonProperty 9 | @NotEmpty 10 | private String signature; 11 | 12 | public SignedPreKey() {} 13 | 14 | public SignedPreKey(long keyId, String publicKey, String signature) { 15 | super(keyId, publicKey); 16 | this.signature = signature; 17 | } 18 | 19 | public String getSignature() { 20 | return signature; 21 | } 22 | 23 | @Override 24 | public boolean equals(Object object) { 25 | if (object == null || !(object instanceof SignedPreKey)) return false; 26 | SignedPreKey that = (SignedPreKey) object; 27 | 28 | if (signature == null) { 29 | return super.equals(object) && that.signature == null; 30 | } else { 31 | return super.equals(object) && this.signature.equals(that.signature); 32 | } 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | if (signature == null) { 38 | return super.hashCode(); 39 | } else { 40 | return super.hashCode() ^ signature.hashCode(); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/StaleDevices.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | public class StaleDevices { 8 | 9 | @JsonProperty 10 | private List staleDevices; 11 | 12 | public StaleDevices() {} 13 | 14 | public StaleDevices(List staleDevices) { 15 | this.staleDevices = staleDevices; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/UnregisteredEvent.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import org.hibernate.validator.constraints.NotEmpty; 5 | 6 | import javax.validation.constraints.Min; 7 | 8 | public class UnregisteredEvent { 9 | 10 | @JsonProperty 11 | @NotEmpty 12 | private String registrationId; 13 | 14 | @JsonProperty 15 | private String canonicalId; 16 | 17 | @JsonProperty 18 | @NotEmpty 19 | private String number; 20 | 21 | @JsonProperty 22 | @Min(1) 23 | private int deviceId; 24 | 25 | @JsonProperty 26 | private long timestamp; 27 | 28 | public String getRegistrationId() { 29 | return registrationId; 30 | } 31 | 32 | public String getCanonicalId() { 33 | return canonicalId; 34 | } 35 | 36 | public String getNumber() { 37 | return number; 38 | } 39 | 40 | public int getDeviceId() { 41 | return deviceId; 42 | } 43 | 44 | public long getTimestamp() { 45 | return timestamp; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/entities/UnregisteredEventList.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | public class UnregisteredEventList { 9 | 10 | @JsonProperty 11 | private List devices; 12 | 13 | public List getDevices() { 14 | if (devices == null) return new LinkedList<>(); 15 | else return devices; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClientManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.federation; 18 | 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | import org.whispersystems.textsecuregcm.configuration.FederationConfiguration; 22 | 23 | import java.io.IOException; 24 | import java.util.HashMap; 25 | import java.util.LinkedList; 26 | import java.util.List; 27 | 28 | import io.dropwizard.client.JerseyClientConfiguration; 29 | import io.dropwizard.setup.Environment; 30 | 31 | public class FederatedClientManager { 32 | 33 | private final Logger logger = LoggerFactory.getLogger(FederatedClientManager.class); 34 | 35 | private final HashMap clients = new HashMap<>(); 36 | 37 | public FederatedClientManager(Environment environment, 38 | JerseyClientConfiguration clientConfig, 39 | FederationConfiguration federationConfig) 40 | throws IOException 41 | { 42 | List peers = federationConfig.getPeers(); 43 | String identity = federationConfig.getName(); 44 | 45 | if (peers != null) { 46 | for (FederatedPeer peer : peers) { 47 | logger.info("Adding peer: " + peer.getName()); 48 | clients.put(peer.getName(), new FederatedClient(environment, clientConfig, identity, peer)); 49 | } 50 | } 51 | } 52 | 53 | public FederatedClient getClient(String name) throws NoSuchPeerException { 54 | FederatedClient client = clients.get(name); 55 | 56 | if (client == null) { 57 | throw new NoSuchPeerException(name); 58 | } 59 | 60 | return client; 61 | } 62 | 63 | public List getClients() { 64 | return new LinkedList<>(clients.values()); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/federation/FederatedPeer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.federation; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | import org.hibernate.validator.constraints.NotEmpty; 21 | import org.hibernate.validator.constraints.URL; 22 | 23 | public class FederatedPeer { 24 | 25 | @NotEmpty 26 | @JsonProperty 27 | private String name; 28 | 29 | @NotEmpty 30 | @URL 31 | @JsonProperty 32 | private String url; 33 | 34 | @NotEmpty 35 | @JsonProperty 36 | private String authenticationToken; 37 | 38 | @NotEmpty 39 | @JsonProperty 40 | private String certificate; 41 | 42 | public FederatedPeer() {} 43 | 44 | public FederatedPeer(String name, String url, String authenticationToken, String certificate) { 45 | this.name = name; 46 | this.url = url; 47 | this.authenticationToken = authenticationToken; 48 | this.certificate = certificate; 49 | } 50 | 51 | public String getUrl() { 52 | return url; 53 | } 54 | 55 | public String getName() { 56 | return name; 57 | } 58 | 59 | public String getAuthenticationToken() { 60 | return authenticationToken; 61 | } 62 | 63 | public String getCertificate() { 64 | return certificate; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/federation/NoSuchPeerException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.federation; 18 | 19 | 20 | public class NoSuchPeerException extends Exception { 21 | public NoSuchPeerException(String name) { 22 | super(name); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.federation; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.google.common.base.Optional; 6 | import org.whispersystems.textsecuregcm.storage.Account; 7 | import org.whispersystems.textsecuregcm.storage.Device; 8 | 9 | public class NonLimitedAccount extends Account { 10 | 11 | @JsonIgnore 12 | private final String number; 13 | 14 | @JsonIgnore 15 | private final String relay; 16 | 17 | @JsonIgnore 18 | private final long deviceId; 19 | 20 | public NonLimitedAccount(String number, long deviceId, String relay) { 21 | this.number = number; 22 | this.deviceId = deviceId; 23 | this.relay = relay; 24 | } 25 | 26 | @Override 27 | public String getNumber() { 28 | return number; 29 | } 30 | 31 | @Override 32 | public boolean isRateLimited() { 33 | return false; 34 | } 35 | 36 | @Override 37 | public Optional getRelay() { 38 | return Optional.of(relay); 39 | } 40 | 41 | @Override 42 | public Optional getAuthenticatedDevice() { 43 | return Optional.of(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, System.currentTimeMillis(), System.currentTimeMillis(), false, "NA")); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/liquibase/AbstractLiquibaseCommand.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.liquibase; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import net.sourceforge.argparse4j.inf.Namespace; 5 | 6 | import java.sql.SQLException; 7 | 8 | import io.dropwizard.Configuration; 9 | import io.dropwizard.cli.ConfiguredCommand; 10 | import io.dropwizard.db.DatabaseConfiguration; 11 | import io.dropwizard.db.ManagedDataSource; 12 | import io.dropwizard.db.PooledDataSourceFactory; 13 | import io.dropwizard.setup.Bootstrap; 14 | import liquibase.Liquibase; 15 | import liquibase.exception.LiquibaseException; 16 | import liquibase.exception.ValidationFailedException; 17 | 18 | public abstract class AbstractLiquibaseCommand extends ConfiguredCommand { 19 | 20 | private final DatabaseConfiguration strategy; 21 | private final Class configurationClass; 22 | private final String migrations; 23 | 24 | protected AbstractLiquibaseCommand(String name, 25 | String description, 26 | String migrations, 27 | DatabaseConfiguration strategy, 28 | Class configurationClass) { 29 | super(name, description); 30 | this.migrations = migrations; 31 | this.strategy = strategy; 32 | this.configurationClass = configurationClass; 33 | } 34 | 35 | @Override 36 | protected Class getConfigurationClass() { 37 | return configurationClass; 38 | } 39 | 40 | @Override 41 | @SuppressWarnings("UseOfSystemOutOrSystemErr") 42 | protected void run(Bootstrap bootstrap, Namespace namespace, T configuration) throws Exception { 43 | final PooledDataSourceFactory dbConfig = strategy.getDataSourceFactory(configuration); 44 | dbConfig.asSingleConnectionPool(); 45 | 46 | try (final CloseableLiquibase liquibase = openLiquibase(dbConfig, namespace)) { 47 | run(namespace, liquibase); 48 | } catch (ValidationFailedException e) { 49 | e.printDescriptiveError(System.err); 50 | throw e; 51 | } 52 | } 53 | 54 | private CloseableLiquibase openLiquibase(final PooledDataSourceFactory dataSourceFactory, final Namespace namespace) 55 | throws ClassNotFoundException, SQLException, LiquibaseException 56 | { 57 | final ManagedDataSource dataSource = dataSourceFactory.build(new MetricRegistry(), "liquibase"); 58 | return new CloseableLiquibase(dataSource, migrations); 59 | } 60 | 61 | protected abstract void run(Namespace namespace, Liquibase liquibase) throws Exception; 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/liquibase/CloseableLiquibase.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.liquibase; 2 | 3 | import java.sql.SQLException; 4 | 5 | import io.dropwizard.db.ManagedDataSource; 6 | import liquibase.Liquibase; 7 | import liquibase.database.jvm.JdbcConnection; 8 | import liquibase.exception.LiquibaseException; 9 | import liquibase.resource.ClassLoaderResourceAccessor; 10 | 11 | 12 | public class CloseableLiquibase extends Liquibase implements AutoCloseable { 13 | private final ManagedDataSource dataSource; 14 | 15 | public CloseableLiquibase(ManagedDataSource dataSource, String migrations) 16 | throws LiquibaseException, ClassNotFoundException, SQLException 17 | { 18 | super(migrations, 19 | new ClassLoaderResourceAccessor(), 20 | new JdbcConnection(dataSource.getConnection())); 21 | this.dataSource = dataSource; 22 | } 23 | 24 | @Override 25 | public void close() throws Exception { 26 | dataSource.stop(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/liquibase/DbMigrateCommand.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.liquibase; 2 | 3 | 4 | import com.google.common.base.Charsets; 5 | import com.google.common.base.Joiner; 6 | import net.sourceforge.argparse4j.impl.Arguments; 7 | import net.sourceforge.argparse4j.inf.Namespace; 8 | import net.sourceforge.argparse4j.inf.Subparser; 9 | 10 | import java.io.OutputStreamWriter; 11 | import java.util.List; 12 | 13 | import io.dropwizard.Configuration; 14 | import io.dropwizard.db.DatabaseConfiguration; 15 | import liquibase.Liquibase; 16 | 17 | public class DbMigrateCommand extends AbstractLiquibaseCommand { 18 | 19 | public DbMigrateCommand(String migration, DatabaseConfiguration strategy, Class configurationClass) { 20 | super("migrate", "Apply all pending change sets.", migration, strategy, configurationClass); 21 | } 22 | 23 | @Override 24 | public void configure(Subparser subparser) { 25 | super.configure(subparser); 26 | 27 | subparser.addArgument("-n", "--dry-run") 28 | .action(Arguments.storeTrue()) 29 | .dest("dry-run") 30 | .setDefault(Boolean.FALSE) 31 | .help("output the DDL to stdout, don't run it"); 32 | 33 | subparser.addArgument("-c", "--count") 34 | .type(Integer.class) 35 | .dest("count") 36 | .help("only apply the next N change sets"); 37 | 38 | subparser.addArgument("-i", "--include") 39 | .action(Arguments.append()) 40 | .dest("contexts") 41 | .help("include change sets from the given context"); 42 | } 43 | 44 | @Override 45 | @SuppressWarnings("UseOfSystemOutOrSystemErr") 46 | public void run(Namespace namespace, Liquibase liquibase) throws Exception { 47 | final String context = getContext(namespace); 48 | final Integer count = namespace.getInt("count"); 49 | final Boolean dryRun = namespace.getBoolean("dry-run"); 50 | if (count != null) { 51 | if (dryRun) { 52 | liquibase.update(count, context, new OutputStreamWriter(System.out, Charsets.UTF_8)); 53 | } else { 54 | liquibase.update(count, context); 55 | } 56 | } else { 57 | if (dryRun) { 58 | liquibase.update(context, new OutputStreamWriter(System.out, Charsets.UTF_8)); 59 | } else { 60 | liquibase.update(context); 61 | } 62 | } 63 | } 64 | 65 | private String getContext(Namespace namespace) { 66 | final List contexts = namespace.getList("contexts"); 67 | if (contexts == null) { 68 | return ""; 69 | } 70 | return Joiner.on(',').join(contexts); 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/liquibase/DbStatusCommand.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.liquibase; 2 | 3 | import com.google.common.base.Charsets; 4 | import com.google.common.base.Joiner; 5 | import net.sourceforge.argparse4j.impl.Arguments; 6 | import net.sourceforge.argparse4j.inf.Namespace; 7 | import net.sourceforge.argparse4j.inf.Subparser; 8 | 9 | import java.io.OutputStreamWriter; 10 | import java.util.List; 11 | 12 | import io.dropwizard.Configuration; 13 | import io.dropwizard.db.DatabaseConfiguration; 14 | import liquibase.Liquibase; 15 | 16 | public class DbStatusCommand extends AbstractLiquibaseCommand { 17 | 18 | public DbStatusCommand(String migrations, DatabaseConfiguration strategy, Class configurationClass) { 19 | super("status", "Check for pending change sets.", migrations, strategy, configurationClass); 20 | } 21 | 22 | @Override 23 | public void configure(Subparser subparser) { 24 | super.configure(subparser); 25 | 26 | subparser.addArgument("-v", "--verbose") 27 | .action(Arguments.storeTrue()) 28 | .dest("verbose") 29 | .help("Output verbose information"); 30 | subparser.addArgument("-i", "--include") 31 | .action(Arguments.append()) 32 | .dest("contexts") 33 | .help("include change sets from the given context"); 34 | } 35 | 36 | @Override 37 | @SuppressWarnings("UseOfSystemOutOrSystemErr") 38 | public void run(Namespace namespace, Liquibase liquibase) throws Exception { 39 | liquibase.reportStatus(namespace.getBoolean("verbose"), 40 | getContext(namespace), 41 | new OutputStreamWriter(System.out, Charsets.UTF_8)); 42 | } 43 | 44 | private String getContext(Namespace namespace) { 45 | final List contexts = namespace.getList("contexts"); 46 | if (contexts == null) { 47 | return ""; 48 | } 49 | return Joiner.on(',').join(contexts); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/liquibase/NameableDbCommand.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.liquibase; 2 | 3 | import com.google.common.collect.Maps; 4 | import net.sourceforge.argparse4j.inf.Namespace; 5 | import net.sourceforge.argparse4j.inf.Subparser; 6 | 7 | import java.util.SortedMap; 8 | 9 | import io.dropwizard.Configuration; 10 | import io.dropwizard.db.DatabaseConfiguration; 11 | import liquibase.Liquibase; 12 | 13 | public class NameableDbCommand extends AbstractLiquibaseCommand { 14 | private static final String COMMAND_NAME_ATTR = "subcommand"; 15 | private final SortedMap> subcommands; 16 | 17 | public NameableDbCommand(String name, String migrations, DatabaseConfiguration strategy, Class configurationClass) { 18 | super(name, "Run database migrations tasks", migrations, strategy, configurationClass); 19 | this.subcommands = Maps.newTreeMap(); 20 | addSubcommand(new DbMigrateCommand<>(migrations, strategy, configurationClass)); 21 | addSubcommand(new DbStatusCommand<>(migrations, strategy, configurationClass)); 22 | } 23 | 24 | private void addSubcommand(AbstractLiquibaseCommand subcommand) { 25 | subcommands.put(subcommand.getName(), subcommand); 26 | } 27 | 28 | @Override 29 | public void configure(Subparser subparser) { 30 | for (AbstractLiquibaseCommand subcommand : subcommands.values()) { 31 | final Subparser cmdParser = subparser.addSubparsers() 32 | .addParser(subcommand.getName()) 33 | .setDefault(COMMAND_NAME_ATTR, subcommand.getName()) 34 | .description(subcommand.getDescription()); 35 | subcommand.configure(cmdParser); 36 | } 37 | } 38 | 39 | @Override 40 | public void run(Namespace namespace, Liquibase liquibase) throws Exception { 41 | final AbstractLiquibaseCommand subcommand = subcommands.get(namespace.getString(COMMAND_NAME_ATTR)); 42 | subcommand.run(namespace, liquibase); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/liquibase/NameableMigrationsBundle.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.liquibase; 2 | 3 | import io.dropwizard.Bundle; 4 | import io.dropwizard.Configuration; 5 | import io.dropwizard.db.DatabaseConfiguration; 6 | import io.dropwizard.setup.Bootstrap; 7 | import io.dropwizard.setup.Environment; 8 | import io.dropwizard.util.Generics; 9 | 10 | public abstract class NameableMigrationsBundle implements Bundle, DatabaseConfiguration { 11 | 12 | private final String name; 13 | private final String migrations; 14 | 15 | public NameableMigrationsBundle(String name, String migrations) { 16 | this.name = name; 17 | this.migrations = migrations; 18 | } 19 | 20 | public final void initialize(Bootstrap bootstrap) { 21 | Class klass = Generics.getTypeParameter(this.getClass(), Configuration.class); 22 | bootstrap.addCommand(new NameableDbCommand(name, migrations, this, klass)); 23 | } 24 | 25 | public final void run(Environment environment) { 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/mappers/DeviceLimitExceededExceptionMapper.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.mappers; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import org.whispersystems.textsecuregcm.controllers.DeviceLimitExceededException; 7 | 8 | import javax.ws.rs.core.Response; 9 | import javax.ws.rs.ext.ExceptionMapper; 10 | import javax.ws.rs.ext.Provider; 11 | 12 | @Provider 13 | public class DeviceLimitExceededExceptionMapper implements ExceptionMapper { 14 | @Override 15 | public Response toResponse(DeviceLimitExceededException exception) { 16 | return Response.status(411) 17 | .entity(new DeviceLimitExceededDetails(exception.getCurrentDevices(), 18 | exception.getMaxDevices())) 19 | .build(); 20 | } 21 | 22 | private static class DeviceLimitExceededDetails { 23 | @JsonProperty 24 | private int current; 25 | @JsonProperty 26 | private int max; 27 | 28 | public DeviceLimitExceededDetails(int current, int max) { 29 | this.current = current; 30 | this.max = max; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/mappers/IOExceptionMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.mappers; 18 | 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import javax.ws.rs.core.Response; 23 | import javax.ws.rs.ext.ExceptionMapper; 24 | import javax.ws.rs.ext.Provider; 25 | import java.io.IOException; 26 | 27 | @Provider 28 | public class IOExceptionMapper implements ExceptionMapper { 29 | 30 | private final Logger logger = LoggerFactory.getLogger(IOExceptionMapper.class); 31 | 32 | @Override 33 | public Response toResponse(IOException e) { 34 | if (!(e.getCause() instanceof java.util.concurrent.TimeoutException)) { 35 | logger.warn("IOExceptionMapper", e); 36 | } 37 | return Response.status(503).build(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/mappers/InvalidWebsocketAddressExceptionMapper.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.mappers; 2 | 3 | import org.whispersystems.textsecuregcm.websocket.InvalidWebsocketAddressException; 4 | 5 | import javax.ws.rs.core.Response; 6 | import javax.ws.rs.ext.ExceptionMapper; 7 | import javax.ws.rs.ext.Provider; 8 | 9 | @Provider 10 | public class InvalidWebsocketAddressExceptionMapper implements ExceptionMapper { 11 | @Override 12 | public Response toResponse(InvalidWebsocketAddressException exception) { 13 | return Response.status(Response.Status.BAD_REQUEST).build(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/mappers/RateLimitExceededExceptionMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.mappers; 18 | 19 | import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException; 20 | 21 | import javax.ws.rs.core.Response; 22 | import javax.ws.rs.ext.ExceptionMapper; 23 | import javax.ws.rs.ext.Provider; 24 | 25 | @Provider 26 | public class RateLimitExceededExceptionMapper implements ExceptionMapper { 27 | @Override 28 | public Response toResponse(RateLimitExceededException e) { 29 | return Response.status(413).build(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/metrics/CpuUsageGauge.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.metrics; 2 | 3 | import com.codahale.metrics.Gauge; 4 | import com.sun.management.OperatingSystemMXBean; 5 | 6 | import java.lang.management.ManagementFactory; 7 | 8 | public class CpuUsageGauge implements Gauge { 9 | @Override 10 | public Integer getValue() { 11 | OperatingSystemMXBean mbean = (com.sun.management.OperatingSystemMXBean) 12 | ManagementFactory.getOperatingSystemMXBean(); 13 | 14 | return (int) Math.ceil(mbean.getSystemCpuLoad() * 100); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/metrics/FileDescriptorGauge.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.metrics; 2 | 3 | 4 | import com.codahale.metrics.Gauge; 5 | 6 | import java.io.File; 7 | 8 | public class FileDescriptorGauge implements Gauge { 9 | @Override 10 | public Integer getValue() { 11 | File file = new File("/proc/self/fd"); 12 | 13 | if (file.isDirectory() && file.exists()) { 14 | return file.list().length; 15 | } 16 | 17 | return 0; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/metrics/FreeMemoryGauge.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.metrics; 2 | 3 | import com.codahale.metrics.Gauge; 4 | import com.sun.management.OperatingSystemMXBean; 5 | 6 | import java.lang.management.ManagementFactory; 7 | 8 | public class FreeMemoryGauge implements Gauge { 9 | 10 | @Override 11 | public Long getValue() { 12 | OperatingSystemMXBean mbean = (com.sun.management.OperatingSystemMXBean) 13 | ManagementFactory.getOperatingSystemMXBean(); 14 | 15 | return mbean.getFreePhysicalMemorySize(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/metrics/JsonMetricsReporterFactory.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.metrics; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import com.codahale.metrics.ScheduledReporter; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.annotation.JsonTypeName; 7 | 8 | import javax.validation.constraints.NotNull; 9 | import java.net.UnknownHostException; 10 | 11 | import io.dropwizard.metrics.BaseReporterFactory; 12 | 13 | @JsonTypeName("json") 14 | public class JsonMetricsReporterFactory extends BaseReporterFactory { 15 | 16 | @JsonProperty 17 | @NotNull 18 | private String hostname; 19 | 20 | @JsonProperty 21 | @NotNull 22 | private String token; 23 | 24 | @Override 25 | public ScheduledReporter build(MetricRegistry metricRegistry) { 26 | try { 27 | return JsonMetricsReporter.forRegistry(metricRegistry) 28 | .withHostname(hostname) 29 | .withToken(token) 30 | .convertRatesTo(getRateUnit()) 31 | .convertDurationsTo(getDurationUnit()) 32 | .filter(getFilter()) 33 | .build(); 34 | } catch (UnknownHostException e) { 35 | throw new IllegalArgumentException(e); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkGauge.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.metrics; 2 | 3 | 4 | import com.codahale.metrics.Gauge; 5 | import org.whispersystems.textsecuregcm.util.Pair; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.File; 9 | import java.io.FileReader; 10 | import java.io.IOException; 11 | 12 | public abstract class NetworkGauge implements Gauge { 13 | 14 | protected Pair getSentReceived() throws IOException { 15 | File proc = new File("/proc/net/dev"); 16 | BufferedReader reader = new BufferedReader(new FileReader(proc)); 17 | String header = reader.readLine(); 18 | String header2 = reader.readLine(); 19 | 20 | long bytesSent = 0; 21 | long bytesReceived = 0; 22 | 23 | String interfaceStats; 24 | 25 | while ((interfaceStats = reader.readLine()) != null) { 26 | String[] stats = interfaceStats.split("\\s+"); 27 | 28 | if (!stats[1].equals("lo:")) { 29 | bytesReceived += Long.parseLong(stats[2]); 30 | bytesSent += Long.parseLong(stats[10]); 31 | } 32 | } 33 | 34 | return new Pair<>(bytesSent, bytesReceived); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkReceivedGauge.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.metrics; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.whispersystems.textsecuregcm.util.Pair; 6 | 7 | import java.io.IOException; 8 | 9 | public class NetworkReceivedGauge extends NetworkGauge { 10 | 11 | private final Logger logger = LoggerFactory.getLogger(NetworkSentGauge.class); 12 | 13 | private long lastTimestamp; 14 | private long lastReceived; 15 | 16 | public NetworkReceivedGauge() { 17 | try { 18 | this.lastTimestamp = System.currentTimeMillis(); 19 | this.lastReceived = getSentReceived().second(); 20 | } catch (IOException e) { 21 | logger.warn(NetworkReceivedGauge.class.getSimpleName(), e); 22 | } 23 | } 24 | 25 | @Override 26 | public Double getValue() { 27 | try { 28 | long timestamp = System.currentTimeMillis(); 29 | Pair sentAndReceived = getSentReceived(); 30 | double bytesReceived = sentAndReceived.second() - lastReceived; 31 | double secondsElapsed = (timestamp - this.lastTimestamp) / 1000; 32 | double result = bytesReceived / secondsElapsed; 33 | 34 | this.lastTimestamp = timestamp; 35 | this.lastReceived = sentAndReceived.second(); 36 | 37 | return result; 38 | } catch (IOException e) { 39 | logger.warn("NetworkReceivedGauge", e); 40 | return -1D; 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkSentGauge.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.metrics; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.whispersystems.textsecuregcm.util.Pair; 6 | 7 | import java.io.IOException; 8 | 9 | public class NetworkSentGauge extends NetworkGauge { 10 | 11 | private final Logger logger = LoggerFactory.getLogger(NetworkSentGauge.class); 12 | 13 | private long lastTimestamp; 14 | private long lastSent; 15 | 16 | public NetworkSentGauge() { 17 | try { 18 | this.lastTimestamp = System.currentTimeMillis(); 19 | this.lastSent = getSentReceived().first(); 20 | } catch (IOException e) { 21 | logger.warn(NetworkSentGauge.class.getSimpleName(), e); 22 | } 23 | } 24 | 25 | @Override 26 | public Double getValue() { 27 | try { 28 | long timestamp = System.currentTimeMillis(); 29 | Pair sentAndReceived = getSentReceived(); 30 | double bytesTransmitted = sentAndReceived.first() - lastSent; 31 | double secondsElapsed = (timestamp - this.lastTimestamp) / 1000; 32 | double result = bytesTransmitted / secondsElapsed; 33 | 34 | this.lastSent = sentAndReceived.first(); 35 | this.lastTimestamp = timestamp; 36 | 37 | return result; 38 | } catch (IOException e) { 39 | logger.warn("NetworkSentGauge", e); 40 | return -1D; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/partner/Partner.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.partner; 2 | 3 | import java.util.Objects; 4 | 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import org.hibernate.validator.constraints.NotEmpty; 7 | 8 | public class Partner { 9 | 10 | @NotEmpty 11 | @JsonProperty 12 | private String name; 13 | 14 | @NotEmpty 15 | @JsonProperty 16 | private String token; 17 | 18 | public Partner() {} 19 | 20 | public Partner(String name, String token) { 21 | this.name = name; 22 | this.token = token; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public String getToken() { 30 | return token; 31 | } 32 | 33 | public int hashCode() { 34 | return Objects.hash(token); 35 | } 36 | 37 | public boolean equals(Object obj) { 38 | if (this == obj) { 39 | return true; 40 | } 41 | if (obj == null || getClass() != obj.getClass()) { 42 | return false; 43 | } 44 | final Partner other = (Partner) obj; 45 | return Objects.equals(token, other.getToken()); 46 | } 47 | 48 | public String toString() { 49 | return token.toString(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/providers/RedisClientFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.providers; 18 | 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | import org.whispersystems.dispatch.io.RedisPubSubConnectionFactory; 22 | import org.whispersystems.dispatch.redis.PubSubConnection; 23 | import org.whispersystems.textsecuregcm.util.Util; 24 | 25 | import java.io.IOException; 26 | import java.net.Socket; 27 | import java.net.URI; 28 | import java.net.URISyntaxException; 29 | 30 | import redis.clients.jedis.JedisPool; 31 | import redis.clients.jedis.JedisPoolConfig; 32 | import redis.clients.jedis.Protocol; 33 | 34 | public class RedisClientFactory implements RedisPubSubConnectionFactory { 35 | 36 | private final Logger logger = LoggerFactory.getLogger(RedisClientFactory.class); 37 | 38 | private final String host; 39 | private final int port; 40 | private final String password; 41 | private final JedisPool jedisPool; 42 | 43 | public RedisClientFactory(String url) throws URISyntaxException { 44 | 45 | URI redisURI = new URI(url); 46 | 47 | this.host = redisURI.getHost(); 48 | this.port = redisURI.getPort(); 49 | 50 | String userInfo = redisURI.getUserInfo(); 51 | if (userInfo != null) { 52 | this.password = userInfo.split(":", 2)[1]; 53 | } else { 54 | this.password = null; 55 | } 56 | /* 57 | * Avoid heroku timeouts... 58 | * See: https://devcenter.heroku.com/articles/heroku-redis#connecting-in-java 59 | */ 60 | JedisPoolConfig poolConfig = new JedisPoolConfig(); 61 | poolConfig.setMaxTotal(8); 62 | poolConfig.setMaxIdle(4); 63 | poolConfig.setMinIdle(1); 64 | poolConfig.setTestOnBorrow(true); 65 | poolConfig.setTestOnReturn(true); 66 | poolConfig.setTestWhileIdle(true); 67 | this.jedisPool = new JedisPool(poolConfig, redisURI); 68 | } 69 | 70 | public JedisPool getRedisClientPool() { 71 | return jedisPool; 72 | } 73 | 74 | @Override 75 | public PubSubConnection connect() { 76 | while (true) { 77 | try { 78 | Socket socket = new Socket(host, port); 79 | return new PubSubConnection(socket, this.password); 80 | } catch (IOException e) { 81 | logger.warn("Error connecting", e); 82 | Util.sleep(1000); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/providers/RedisHealthCheck.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.providers; 18 | 19 | import com.codahale.metrics.health.HealthCheck; 20 | 21 | import redis.clients.jedis.Jedis; 22 | import redis.clients.jedis.JedisPool; 23 | 24 | public class RedisHealthCheck extends HealthCheck { 25 | 26 | private final JedisPool clientPool; 27 | 28 | public RedisHealthCheck(JedisPool clientPool) { 29 | this.clientPool = clientPool; 30 | } 31 | 32 | @Override 33 | protected Result check() throws Exception { 34 | try (Jedis client = clientPool.getResource()) { 35 | client.set("HEALTH", "test"); 36 | 37 | if (!"test".equals(client.get("HEALTH"))) { 38 | return Result.unhealthy("fetch failed"); 39 | } 40 | 41 | return Result.healthy(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/providers/TimeProvider.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.providers; 2 | 3 | public class TimeProvider { 4 | public long getCurrentTimeMillis() { 5 | return System.currentTimeMillis(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/push/ApnMessage.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.push; 2 | 3 | public class ApnMessage { 4 | 5 | public static long MAX_EXPIRATION = Integer.MAX_VALUE * 1000L; 6 | 7 | private final String apnId; 8 | private final String number; 9 | private final int deviceId; 10 | private final String message; 11 | private final boolean isVoip; 12 | private final long expirationTime; 13 | 14 | public ApnMessage(String apnId, String number, int deviceId, String message, boolean isVoip, long expirationTime) { 15 | this.apnId = apnId; 16 | this.number = number; 17 | this.deviceId = deviceId; 18 | this.message = message; 19 | this.isVoip = isVoip; 20 | this.expirationTime = expirationTime; 21 | } 22 | 23 | public ApnMessage(ApnMessage copy, String apnId, boolean isVoip, long expirationTime) { 24 | this.apnId = apnId; 25 | this.number = copy.number; 26 | this.deviceId = copy.deviceId; 27 | this.message = copy.message; 28 | this.isVoip = isVoip; 29 | this.expirationTime = expirationTime; 30 | } 31 | 32 | public boolean isVoip() { 33 | return isVoip; 34 | } 35 | 36 | public String getApnId() { 37 | return apnId; 38 | } 39 | 40 | public String getMessage() { 41 | return message; 42 | } 43 | 44 | public long getExpirationTime() { 45 | return expirationTime; 46 | } 47 | 48 | public String getNumber() { 49 | return number; 50 | } 51 | 52 | public int getDeviceId() { 53 | return deviceId; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/push/FcmMessage.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.push; 2 | 3 | public class FcmMessage { 4 | 5 | private final String fcmId; 6 | private final String number; 7 | private final int deviceId; 8 | private final boolean receipt; 9 | 10 | public FcmMessage(String fcmId, String number, int deviceId, boolean receipt) { 11 | this.fcmId = fcmId; 12 | this.number = number; 13 | this.deviceId = deviceId; 14 | this.receipt = receipt; 15 | } 16 | 17 | public String getFcmId() { 18 | return fcmId; 19 | } 20 | 21 | public String getNumber() { 22 | return number; 23 | } 24 | 25 | public boolean isReceipt() { 26 | return receipt; 27 | } 28 | 29 | public int getDeviceId() { 30 | return deviceId; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/push/NotPushRegisteredException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.push; 2 | 3 | public class NotPushRegisteredException extends Exception { 4 | public NotPushRegisteredException(String s) { 5 | super(s); 6 | } 7 | 8 | public NotPushRegisteredException(Exception e) { 9 | super(e); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/push/TransientPushFailureException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.push; 2 | 3 | public class TransientPushFailureException extends Exception { 4 | public TransientPushFailureException(String s) { 5 | super(s); 6 | } 7 | 8 | public TransientPushFailureException(Exception e) { 9 | super(e); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/storage/KeyRecord.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.storage; 2 | 3 | public class KeyRecord { 4 | 5 | private long id; 6 | private String number; 7 | private long deviceId; 8 | private long keyId; 9 | private String publicKey; 10 | private boolean lastResort; 11 | 12 | public KeyRecord(long id, String number, long deviceId, long keyId, 13 | String publicKey, boolean lastResort) 14 | { 15 | this.id = id; 16 | this.number = number; 17 | this.deviceId = deviceId; 18 | this.keyId = keyId; 19 | this.publicKey = publicKey; 20 | this.lastResort = lastResort; 21 | } 22 | 23 | public long getId() { 24 | return id; 25 | } 26 | 27 | public String getNumber() { 28 | return number; 29 | } 30 | 31 | public long getDeviceId() { 32 | return deviceId; 33 | } 34 | 35 | public long getKeyId() { 36 | return keyId; 37 | } 38 | 39 | public String getPublicKey() { 40 | return publicKey; 41 | } 42 | 43 | public boolean isLastResort() { 44 | return lastResort; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/storage/MessagesManager.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.storage; 2 | 3 | 4 | import com.google.common.base.Optional; 5 | import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; 6 | import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity; 7 | import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList; 8 | 9 | import java.util.List; 10 | 11 | public class MessagesManager { 12 | 13 | private final Messages messages; 14 | 15 | public MessagesManager(Messages messages) { 16 | this.messages = messages; 17 | } 18 | 19 | public int insert(String destination, long destinationDevice, Envelope message) { 20 | return this.messages.store(message, destination, destinationDevice) + 1; 21 | } 22 | 23 | public OutgoingMessageEntityList getMessagesForDevice(String destination, long destinationDevice) { 24 | List messages = this.messages.load(destination, destinationDevice); 25 | return new OutgoingMessageEntityList(messages, messages.size() >= Messages.RESULT_SET_CHUNK_SIZE); 26 | } 27 | 28 | public void clear(String destination) { 29 | this.messages.clear(destination); 30 | } 31 | 32 | public void clear(String destination, long deviceId) { 33 | this.messages.clear(destination, deviceId); 34 | } 35 | 36 | public Optional delete(String destination, long destinationDevice, String source, long timestamp) 37 | { 38 | return Optional.fromNullable(this.messages.remove(destination, destinationDevice, source, timestamp)); 39 | } 40 | 41 | public void delete(String destination, long id) { 42 | this.messages.remove(destination, id); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccounts.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.storage; 18 | 19 | import org.skife.jdbi.v2.sqlobject.Bind; 20 | import org.skife.jdbi.v2.sqlobject.SqlQuery; 21 | import org.skife.jdbi.v2.sqlobject.SqlUpdate; 22 | 23 | public interface PendingAccounts { 24 | 25 | @SqlUpdate("WITH upsert AS (UPDATE pending_accounts SET verification_code = :verification_code WHERE number = :number RETURNING *) " + 26 | "INSERT INTO pending_accounts (number, verification_code) SELECT :number, :verification_code WHERE NOT EXISTS (SELECT * FROM upsert)") 27 | void insert(@Bind("number") String number, @Bind("verification_code") String verificationCode); 28 | 29 | @SqlQuery("SELECT verification_code FROM pending_accounts WHERE number = :number") 30 | String getCodeForNumber(@Bind("number") String number); 31 | 32 | @SqlUpdate("DELETE FROM pending_accounts WHERE number = :number") 33 | void remove(@Bind("number") String number); 34 | 35 | @SqlUpdate("VACUUM pending_accounts") 36 | public void vacuum(); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.storage; 18 | 19 | import com.google.common.base.Optional; 20 | 21 | import redis.clients.jedis.Jedis; 22 | import redis.clients.jedis.JedisPool; 23 | 24 | public class PendingAccountsManager { 25 | 26 | private static final String CACHE_PREFIX = "pending_account::"; 27 | 28 | private final PendingAccounts pendingAccounts; 29 | private final JedisPool cacheClient; 30 | 31 | public PendingAccountsManager(PendingAccounts pendingAccounts, JedisPool cacheClient) 32 | { 33 | this.pendingAccounts = pendingAccounts; 34 | this.cacheClient = cacheClient; 35 | } 36 | 37 | public void store(String number, String code) { 38 | memcacheSet(number, code); 39 | pendingAccounts.insert(number, code); 40 | } 41 | 42 | public void remove(String number) { 43 | memcacheDelete(number); 44 | pendingAccounts.remove(number); 45 | } 46 | 47 | public Optional getCodeForNumber(String number) { 48 | Optional code = memcacheGet(number); 49 | 50 | if (!code.isPresent()) { 51 | code = Optional.fromNullable(pendingAccounts.getCodeForNumber(number)); 52 | 53 | if (code.isPresent()) { 54 | memcacheSet(number, code.get()); 55 | } 56 | } 57 | 58 | return code; 59 | } 60 | 61 | private void memcacheSet(String number, String code) { 62 | try (Jedis jedis = cacheClient.getResource()) { 63 | jedis.set(CACHE_PREFIX + number, code); 64 | } 65 | } 66 | 67 | private Optional memcacheGet(String number) { 68 | try (Jedis jedis = cacheClient.getResource()) { 69 | return Optional.fromNullable(jedis.get(CACHE_PREFIX + number)); 70 | } 71 | } 72 | 73 | private void memcacheDelete(String number) { 74 | try (Jedis jedis = cacheClient.getResource()) { 75 | jedis.del(CACHE_PREFIX + number); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevices.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.storage; 18 | 19 | import org.skife.jdbi.v2.sqlobject.Bind; 20 | import org.skife.jdbi.v2.sqlobject.SqlQuery; 21 | import org.skife.jdbi.v2.sqlobject.SqlUpdate; 22 | 23 | public interface PendingDevices { 24 | 25 | @SqlUpdate("WITH upsert AS (UPDATE pending_devices SET verification_code = :verification_code WHERE number = :number RETURNING *) " + 26 | "INSERT INTO pending_devices (number, verification_code) SELECT :number, :verification_code WHERE NOT EXISTS (SELECT * FROM upsert)") 27 | void insert(@Bind("number") String number, @Bind("verification_code") String verificationCode); 28 | 29 | @SqlQuery("SELECT verification_code FROM pending_devices WHERE number = :number") 30 | String getCodeForNumber(@Bind("number") String number); 31 | 32 | @SqlUpdate("DELETE FROM pending_devices WHERE number = :number") 33 | void remove(@Bind("number") String number); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.storage; 18 | 19 | import com.google.common.base.Optional; 20 | 21 | import redis.clients.jedis.Jedis; 22 | import redis.clients.jedis.JedisPool; 23 | 24 | public class PendingDevicesManager { 25 | 26 | private static final String CACHE_PREFIX = "pending_devices::"; 27 | 28 | private final PendingDevices pendingDevices; 29 | private final JedisPool cacheClient; 30 | 31 | public PendingDevicesManager(PendingDevices pendingDevices, 32 | JedisPool cacheClient) 33 | { 34 | this.pendingDevices = pendingDevices; 35 | this.cacheClient = cacheClient; 36 | } 37 | 38 | public void store(String number, String code) { 39 | memcacheSet(number, code); 40 | pendingDevices.insert(number, code); 41 | } 42 | 43 | public void remove(String number) { 44 | memcacheDelete(number); 45 | pendingDevices.remove(number); 46 | } 47 | 48 | public Optional getCodeForNumber(String number) { 49 | Optional code = memcacheGet(number); 50 | 51 | if (!code.isPresent()) { 52 | code = Optional.fromNullable(pendingDevices.getCodeForNumber(number)); 53 | 54 | if (code.isPresent()) { 55 | memcacheSet(number, code.get()); 56 | } 57 | } 58 | 59 | return code; 60 | } 61 | 62 | private void memcacheSet(String number, String code) { 63 | try (Jedis jedis = cacheClient.getResource()) { 64 | jedis.set(CACHE_PREFIX + number, code); 65 | } 66 | } 67 | 68 | private Optional memcacheGet(String number) { 69 | try (Jedis jedis = cacheClient.getResource()) { 70 | return Optional.fromNullable(jedis.get(CACHE_PREFIX + number)); 71 | } 72 | } 73 | 74 | private void memcacheDelete(String number) { 75 | try (Jedis jedis = cacheClient.getResource()) { 76 | jedis.del(CACHE_PREFIX + number); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/storage/PubSubAddress.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.storage; 2 | 3 | public interface PubSubAddress { 4 | public String serialize(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/util/BlockingThreadPoolExecutor.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.util; 2 | 3 | import java.util.concurrent.LinkedBlockingQueue; 4 | import java.util.concurrent.Semaphore; 5 | import java.util.concurrent.ThreadPoolExecutor; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | public class BlockingThreadPoolExecutor extends ThreadPoolExecutor { 9 | 10 | private final Semaphore semaphore; 11 | 12 | public BlockingThreadPoolExecutor(int threads, int bound) { 13 | super(threads, threads, 1, TimeUnit.SECONDS, new LinkedBlockingQueue()); 14 | this.semaphore = new Semaphore(bound); 15 | } 16 | 17 | @Override 18 | public void execute(Runnable task) { 19 | semaphore.acquireUninterruptibly(); 20 | 21 | try { 22 | super.execute(task); 23 | } catch (Throwable t) { 24 | semaphore.release(); 25 | throw new RuntimeException(t); 26 | } 27 | } 28 | 29 | @Override 30 | protected void afterExecute(Runnable r, Throwable t) { 31 | semaphore.release(); 32 | } 33 | 34 | public int getSize() { 35 | return ((LinkedBlockingQueue)getQueue()).size(); 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/util/ByteArrayAdapter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.util; 18 | 19 | 20 | import com.fasterxml.jackson.core.JsonGenerator; 21 | import com.fasterxml.jackson.core.JsonParser; 22 | import com.fasterxml.jackson.core.JsonProcessingException; 23 | import com.fasterxml.jackson.databind.DeserializationContext; 24 | import com.fasterxml.jackson.databind.JsonDeserializer; 25 | import com.fasterxml.jackson.databind.JsonSerializer; 26 | import com.fasterxml.jackson.databind.SerializerProvider; 27 | 28 | import java.io.IOException; 29 | 30 | public class ByteArrayAdapter { 31 | 32 | public static class Serializing extends JsonSerializer { 33 | @Override 34 | public void serialize(byte[] bytes, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) 35 | throws IOException, JsonProcessingException 36 | { 37 | jsonGenerator.writeString(Base64.encodeBytesWithoutPadding(bytes)); 38 | } 39 | } 40 | 41 | public static class Deserializing extends JsonDeserializer { 42 | @Override 43 | public byte[] deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) 44 | throws IOException, JsonProcessingException 45 | { 46 | return Base64.decodeWithoutPadding(jsonParser.getValueAsString()); 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/util/Constants.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.util; 2 | 3 | public class Constants { 4 | 5 | public static final String METRICS_NAME = "textsecure"; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/util/IterablePair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.util; 18 | 19 | import java.util.Iterator; 20 | import java.util.List; 21 | 22 | public class IterablePair implements Iterable> { 23 | private final List first; 24 | private final List second; 25 | 26 | public IterablePair(List first, List second) { 27 | this.first = first; 28 | this.second = second; 29 | } 30 | 31 | @Override 32 | public Iterator> iterator(){ 33 | return new ParallelIterator<>( first.iterator(), second.iterator() ); 34 | } 35 | 36 | public static class ParallelIterator implements Iterator> { 37 | 38 | private final Iterator it1; 39 | private final Iterator it2; 40 | 41 | public ParallelIterator(Iterator it1, Iterator it2) { 42 | this.it1 = it1; this.it2 = it2; 43 | } 44 | 45 | @Override 46 | public boolean hasNext() { return it1.hasNext() && it2.hasNext(); } 47 | 48 | @Override 49 | public Pair next() { 50 | return new Pair<>(it1.next(), it2.next()); 51 | } 52 | 53 | @Override 54 | public void remove(){ 55 | it1.remove(); 56 | it2.remove(); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/util/Pair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.util; 18 | 19 | import static com.google.common.base.Objects.equal; 20 | 21 | public class Pair { 22 | private final T1 v1; 23 | private final T2 v2; 24 | 25 | public Pair(T1 v1, T2 v2) { 26 | this.v1 = v1; 27 | this.v2 = v2; 28 | } 29 | 30 | public T1 first(){ 31 | return v1; 32 | } 33 | 34 | public T2 second(){ 35 | return v2; 36 | } 37 | 38 | public boolean equals(Object o) { 39 | return o instanceof Pair && 40 | equal(((Pair) o).first(), first()) && 41 | equal(((Pair) o).second(), second()); 42 | } 43 | 44 | public int hashCode() { 45 | return first().hashCode() ^ second().hashCode(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.util; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.DeserializationFeature; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | 8 | public class SystemMapper { 9 | 10 | private static final ObjectMapper mapper = new ObjectMapper(); 11 | 12 | static { 13 | mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); 14 | mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); 15 | mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 16 | } 17 | 18 | public static ObjectMapper getMapper() { 19 | return mapper; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/util/VerificationCode.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.textsecuregcm.util; 18 | 19 | import com.fasterxml.jackson.annotation.JsonIgnore; 20 | import com.fasterxml.jackson.annotation.JsonInclude; 21 | import com.fasterxml.jackson.annotation.JsonProperty; 22 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 23 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 24 | import com.google.common.annotations.VisibleForTesting; 25 | 26 | public class VerificationCode { 27 | 28 | @JsonProperty 29 | private String verificationCode; 30 | @JsonIgnore 31 | private String verificationCodeDisplay; 32 | @JsonIgnore 33 | private String verificationCodeSpeech; 34 | 35 | @VisibleForTesting VerificationCode() {} 36 | 37 | public VerificationCode(int verificationCode) { 38 | this.verificationCode = verificationCode + ""; 39 | this.verificationCodeDisplay = this.verificationCode.substring(0, 3) + "-" + 40 | this.verificationCode.substring(3, 6); 41 | this.verificationCodeSpeech = delimit(verificationCode + ""); 42 | } 43 | 44 | public String getVerificationCode() { 45 | return verificationCode; 46 | } 47 | 48 | public String getVerificationCodeDisplay() { 49 | return verificationCodeDisplay; 50 | } 51 | 52 | public String getVerificationCodeSpeech() { 53 | return verificationCodeSpeech; 54 | } 55 | 56 | private String delimit(String code) { 57 | String delimited = ""; 58 | 59 | for (int i=0;i body = Optional.of(outgoingMessage.getContent().toByteArray()); 33 | 34 | ListenableFuture response = client.sendRequest("PUT", "/v1/message", null, body); 35 | 36 | Futures.addCallback(response, new FutureCallback() { 37 | @Override 38 | public void onSuccess(WebSocketResponseMessage webSocketResponseMessage) { 39 | client.close(1001, "All you get."); 40 | } 41 | 42 | @Override 43 | public void onFailure(Throwable throwable) { 44 | client.close(1001, "That's all!"); 45 | } 46 | }); 47 | } 48 | } catch (InvalidProtocolBufferException e) { 49 | logger.warn("Protobuf Error: ", e); 50 | } 51 | } 52 | 53 | @Override 54 | public void onDispatchSubscribed(String channel) { 55 | try { 56 | ProvisioningAddress address = new ProvisioningAddress(channel); 57 | this.client.sendRequest("PUT", "/v1/address", null, Optional.of(ProvisioningUuid.newBuilder() 58 | .setUuid(address.getAddress()) 59 | .build() 60 | .toByteArray())); 61 | } catch (InvalidWebsocketAddressException e) { 62 | logger.warn("Badly formatted address", e); 63 | this.client.close(1001, "Server Error"); 64 | } 65 | } 66 | 67 | @Override 68 | public void onDispatchUnsubscribed(String channel) { 69 | this.client.close(1001, "Closed"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.websocket; 2 | 3 | import com.google.common.base.Optional; 4 | import org.eclipse.jetty.websocket.api.UpgradeRequest; 5 | import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; 6 | import org.whispersystems.textsecuregcm.storage.Account; 7 | import org.whispersystems.textsecuregcm.storage.Device; 8 | import org.whispersystems.websocket.auth.AuthenticationException; 9 | import org.whispersystems.websocket.auth.WebSocketAuthenticator; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | import io.dropwizard.auth.basic.BasicCredentials; 15 | 16 | 17 | public class WebSocketAccountAuthenticator implements WebSocketAuthenticator { 18 | 19 | private final AccountAuthenticator accountAuthenticator; 20 | 21 | public WebSocketAccountAuthenticator(AccountAuthenticator accountAuthenticator) { 22 | this.accountAuthenticator = accountAuthenticator; 23 | } 24 | 25 | @Override 26 | public Optional authenticate(UpgradeRequest request) throws AuthenticationException { 27 | try { 28 | Map> parameters = request.getParameterMap(); 29 | List usernames = parameters.get("login"); 30 | List passwords = parameters.get("password"); 31 | 32 | if (usernames == null || usernames.size() == 0 || 33 | passwords == null || passwords.size() == 0) 34 | { 35 | return Optional.absent(); 36 | } 37 | 38 | BasicCredentials credentials = new BasicCredentials(usernames.get(0).replace(" ", "+"), 39 | passwords.get(0).replace(" ", "+")); 40 | 41 | return accountAuthenticator.authenticate(credentials); 42 | } catch (io.dropwizard.auth.AuthenticationException e) { 43 | throw new AuthenticationException(e); 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionInfo.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.websocket; 2 | 3 | import org.whispersystems.textsecuregcm.storage.PubSubAddress; 4 | import org.whispersystems.textsecuregcm.util.Util; 5 | 6 | public class WebSocketConnectionInfo implements PubSubAddress { 7 | 8 | private final WebsocketAddress address; 9 | 10 | public WebSocketConnectionInfo(WebsocketAddress address) { 11 | this.address = address; 12 | } 13 | 14 | public WebSocketConnectionInfo(String serialized) throws FormattingException { 15 | String[] parts = serialized.split("[:]", 3); 16 | 17 | if (parts.length != 3 || !"c".equals(parts[2])) { 18 | throw new FormattingException("Bad address: " + serialized); 19 | } 20 | 21 | try { 22 | this.address = new WebsocketAddress(parts[0], Long.parseLong(parts[1])); 23 | } catch (NumberFormatException e) { 24 | throw new FormattingException(e); 25 | } 26 | } 27 | 28 | public String serialize() { 29 | return address.serialize() + ":c"; 30 | } 31 | 32 | public WebsocketAddress getWebsocketAddress() { 33 | return address; 34 | } 35 | 36 | public static boolean isType(String address) { 37 | return address.endsWith(":c"); 38 | } 39 | 40 | @Override 41 | public boolean equals(Object other) { 42 | return 43 | other != null && 44 | other instanceof WebSocketConnectionInfo 45 | && ((WebSocketConnectionInfo)other).address.equals(address); 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Util.hashCode(address, "c"); 51 | } 52 | 53 | public static class FormattingException extends Exception { 54 | public FormattingException(String message) { 55 | super(message); 56 | } 57 | 58 | public FormattingException(Exception e) { 59 | super(e); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/websocket/WebsocketAddress.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.websocket; 2 | 3 | import org.whispersystems.textsecuregcm.storage.PubSubAddress; 4 | 5 | public class WebsocketAddress implements PubSubAddress { 6 | 7 | private final String number; 8 | private final long deviceId; 9 | 10 | public WebsocketAddress(String number, long deviceId) { 11 | this.number = number; 12 | this.deviceId = deviceId; 13 | } 14 | 15 | public WebsocketAddress(String serialized) throws InvalidWebsocketAddressException { 16 | try { 17 | String[] parts = serialized.split(":", 2); 18 | 19 | if (parts.length != 2) { 20 | throw new InvalidWebsocketAddressException("Bad address: " + serialized); 21 | } 22 | 23 | this.number = parts[0]; 24 | this.deviceId = Long.parseLong(parts[1]); 25 | } catch (NumberFormatException e) { 26 | throw new InvalidWebsocketAddressException(e); 27 | } 28 | } 29 | 30 | public String getNumber() { 31 | return number; 32 | } 33 | 34 | public long getDeviceId() { 35 | return deviceId; 36 | } 37 | 38 | public String serialize() { 39 | return number + ":" + deviceId; 40 | } 41 | 42 | public String toString() { 43 | return serialize(); 44 | } 45 | 46 | @Override 47 | public boolean equals(Object other) { 48 | if (other == null) return false; 49 | if (!(other instanceof WebsocketAddress)) return false; 50 | 51 | WebsocketAddress that = (WebsocketAddress)other; 52 | 53 | return 54 | this.number.equals(that.number) && 55 | this.deviceId == that.deviceId; 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | return number.hashCode() ^ (int)deviceId; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/whispersystems/textsecuregcm/workers/TrimMessagesCommand.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.workers; 2 | 3 | import net.sourceforge.argparse4j.inf.Namespace; 4 | import org.skife.jdbi.v2.DBI; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.whispersystems.textsecuregcm.WhisperServerConfiguration; 8 | import org.whispersystems.textsecuregcm.storage.Messages; 9 | 10 | import java.util.concurrent.TimeUnit; 11 | 12 | import io.dropwizard.cli.ConfiguredCommand; 13 | import io.dropwizard.db.DataSourceFactory; 14 | import io.dropwizard.jdbi.ImmutableListContainerFactory; 15 | import io.dropwizard.jdbi.ImmutableSetContainerFactory; 16 | import io.dropwizard.jdbi.OptionalContainerFactory; 17 | import io.dropwizard.jdbi.args.OptionalArgumentFactory; 18 | import io.dropwizard.setup.Bootstrap; 19 | 20 | public class TrimMessagesCommand extends ConfiguredCommand { 21 | private final Logger logger = LoggerFactory.getLogger(VacuumCommand.class); 22 | 23 | public TrimMessagesCommand() { 24 | super("trim", "Trim Messages Database"); 25 | } 26 | 27 | @Override 28 | protected void run(Bootstrap bootstrap, 29 | Namespace namespace, 30 | WhisperServerConfiguration config) 31 | throws Exception 32 | { 33 | DataSourceFactory messageDbConfig = config.getMessageStoreConfiguration(); 34 | DBI messageDbi = new DBI(messageDbConfig.getUrl(), messageDbConfig.getUser(), messageDbConfig.getPassword()); 35 | 36 | messageDbi.registerArgumentFactory(new OptionalArgumentFactory(messageDbConfig.getDriverClass())); 37 | messageDbi.registerContainerFactory(new ImmutableListContainerFactory()); 38 | messageDbi.registerContainerFactory(new ImmutableSetContainerFactory()); 39 | messageDbi.registerContainerFactory(new OptionalContainerFactory()); 40 | 41 | Messages messages = messageDbi.onDemand(Messages.class); 42 | long timestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60); 43 | 44 | logger.info("Trimming old messages: " + timestamp + "..."); 45 | messages.removeOld(timestamp); 46 | 47 | Thread.sleep(3000); 48 | System.exit(0); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.dropwizard.metrics.ReporterFactory: -------------------------------------------------------------------------------- 1 | org.whispersystems.textsecuregcm.metrics.JsonMetricsReporterFactory -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | _ _ _ _ _____ 2 | | | | | | (_) / ___| 3 | | | | | |__ _ ___ _ __ ___ _ __\ `--. ___ _ ____ _____ _ __ 4 | | |/\| | '_ \| / __| '_ \ / _ \ '__|`--. \/ _ \ '__\ \ / / _ \ '__| 5 | \ /\ / | | | \__ \ |_) | __/ | /\__/ / __/ | \ V / __/ | 6 | \/ \/|_| |_|_|___/ .__/ \___|_| \____/ \___|_| \_/ \___|_| 7 | | | 8 | |_| 9 | -------------------------------------------------------------------------------- /src/test/java/org/whispersystems/dispatch/redis/protocol/ArrayReplyHeaderTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.dispatch.redis.protocol; 2 | 3 | 4 | import org.junit.Test; 5 | 6 | import java.io.IOException; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | 10 | public class ArrayReplyHeaderTest { 11 | 12 | 13 | @Test(expected = IOException.class) 14 | public void testNull() throws IOException { 15 | new ArrayReplyHeader(null); 16 | } 17 | 18 | @Test(expected = IOException.class) 19 | public void testBadPrefix() throws IOException { 20 | new ArrayReplyHeader(":3"); 21 | } 22 | 23 | @Test(expected = IOException.class) 24 | public void testEmpty() throws IOException { 25 | new ArrayReplyHeader(""); 26 | } 27 | 28 | @Test(expected = IOException.class) 29 | public void testTruncated() throws IOException { 30 | new ArrayReplyHeader("*"); 31 | } 32 | 33 | @Test(expected = IOException.class) 34 | public void testBadNumber() throws IOException { 35 | new ArrayReplyHeader("*ABC"); 36 | } 37 | 38 | @Test 39 | public void testValid() throws IOException { 40 | assertEquals(4, new ArrayReplyHeader("*4").getElementCount()); 41 | } 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/org/whispersystems/dispatch/redis/protocol/IntReplyHeaderTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.dispatch.redis.protocol; 2 | 3 | 4 | import org.junit.Test; 5 | 6 | import java.io.IOException; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | 10 | public class IntReplyHeaderTest { 11 | 12 | @Test(expected = IOException.class) 13 | public void testNull() throws IOException { 14 | new IntReply(null); 15 | } 16 | 17 | @Test(expected = IOException.class) 18 | public void testEmpty() throws IOException { 19 | new IntReply(""); 20 | } 21 | 22 | @Test(expected = IOException.class) 23 | public void testBadNumber() throws IOException { 24 | new IntReply(":A"); 25 | } 26 | 27 | @Test(expected = IOException.class) 28 | public void testBadFormat() throws IOException { 29 | new IntReply("*"); 30 | } 31 | 32 | @Test 33 | public void testValid() throws IOException { 34 | assertEquals(23, new IntReply(":23").getValue()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/org/whispersystems/dispatch/redis/protocol/StringReplyHeaderTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.dispatch.redis.protocol; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | public class StringReplyHeaderTest { 10 | 11 | @Test 12 | public void testNull() { 13 | try { 14 | new StringReplyHeader(null); 15 | throw new AssertionError(); 16 | } catch (IOException e) { 17 | // good 18 | } 19 | } 20 | 21 | @Test 22 | public void testBadNumber() { 23 | try { 24 | new StringReplyHeader("$100A"); 25 | throw new AssertionError(); 26 | } catch (IOException e) { 27 | // good 28 | } 29 | } 30 | 31 | @Test 32 | public void testBadPrefix() { 33 | try { 34 | new StringReplyHeader("*"); 35 | throw new AssertionError(); 36 | } catch (IOException e) { 37 | // good 38 | } 39 | } 40 | 41 | @Test 42 | public void testValid() throws IOException { 43 | assertEquals(1000, new StringReplyHeader("$1000").getStringLength()); 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.tests.entities; 2 | 3 | import org.junit.Test; 4 | import org.whispersystems.textsecuregcm.entities.ClientContact; 5 | import org.whispersystems.textsecuregcm.util.Util; 6 | 7 | import static org.hamcrest.CoreMatchers.equalTo; 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.hamcrest.MatcherAssert.assertThat; 10 | import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.*; 11 | 12 | public class ClientContactTest { 13 | 14 | @Test 15 | public void serializeToJSON() throws Exception { 16 | byte[] token = Util.getContactToken("+14152222222"); 17 | ClientContact contact = new ClientContact(token, null, false); 18 | ClientContact contactWithRelay = new ClientContact(token, "whisper", false); 19 | ClientContact contactWithRelayVox = new ClientContact(token, "whisper", true); 20 | 21 | assertThat("Basic Contact Serialization works", 22 | asJson(contact), 23 | is(equalTo(jsonFixture("fixtures/contact.json")))); 24 | 25 | assertThat("Contact Relay Serialization works", 26 | asJson(contactWithRelay), 27 | is(equalTo(jsonFixture("fixtures/contact.relay.json")))); 28 | 29 | assertThat("Contact Relay Vox Serializaton works", 30 | asJson(contactWithRelayVox), 31 | is(equalTo(jsonFixture("fixtures/contact.relay.voice.json")))); 32 | } 33 | 34 | @Test 35 | public void deserializeFromJSON() throws Exception { 36 | ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"), 37 | "whisper", false); 38 | 39 | assertThat("a ClientContact can be deserialized from JSON", 40 | fromJson(jsonFixture("fixtures/contact.relay.json"), ClientContact.class), 41 | is(contact)); 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.tests.entities; 2 | 3 | import org.junit.Test; 4 | import org.whispersystems.textsecuregcm.entities.ClientContact; 5 | import org.whispersystems.textsecuregcm.entities.PreKey; 6 | import org.whispersystems.textsecuregcm.util.Util; 7 | 8 | import static org.hamcrest.CoreMatchers.equalTo; 9 | import static org.hamcrest.CoreMatchers.is; 10 | import static org.hamcrest.MatcherAssert.assertThat; 11 | import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.*; 12 | 13 | public class PreKeyTest { 14 | 15 | @Test 16 | public void deserializeFromJSONV() throws Exception { 17 | ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"), 18 | "whisper", false); 19 | 20 | assertThat("a ClientContact can be deserialized from JSON", 21 | fromJson(jsonFixture("fixtures/contact.relay.json"), ClientContact.class), 22 | is(contact)); 23 | } 24 | 25 | @Test 26 | public void serializeToJSONV2() throws Exception { 27 | PreKey preKey = new PreKey(1234, "test"); 28 | 29 | assertThat("PreKeyV2 Serialization works", 30 | asJson(preKey), 31 | is(equalTo(jsonFixture("fixtures/prekey_v2.json")))); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/org/whispersystems/textsecuregcm/tests/limits/LeakyBucketTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.tests.limits; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.junit.Test; 5 | import org.whispersystems.textsecuregcm.limits.LeakyBucket; 6 | 7 | import java.io.IOException; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import static org.junit.Assert.assertFalse; 11 | import static org.junit.Assert.assertTrue; 12 | 13 | public class LeakyBucketTest { 14 | 15 | @Test 16 | public void testFull() { 17 | LeakyBucket leakyBucket = new LeakyBucket(2, 1.0 / 2.0); 18 | 19 | assertTrue(leakyBucket.add(1)); 20 | assertTrue(leakyBucket.add(1)); 21 | assertFalse(leakyBucket.add(1)); 22 | 23 | leakyBucket = new LeakyBucket(2, 1.0 / 2.0); 24 | 25 | assertTrue(leakyBucket.add(2)); 26 | assertFalse(leakyBucket.add(1)); 27 | assertFalse(leakyBucket.add(2)); 28 | } 29 | 30 | @Test 31 | public void testLapseRate() throws IOException { 32 | ObjectMapper mapper = new ObjectMapper(); 33 | String serialized = "{\"bucketSize\":2,\"leakRatePerMillis\":8.333333333333334E-6,\"spaceRemaining\":0,\"lastUpdateTimeMillis\":" + (System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2)) + "}"; 34 | 35 | LeakyBucket leakyBucket = LeakyBucket.fromSerialized(mapper, serialized); 36 | assertTrue(leakyBucket.add(1)); 37 | 38 | String serializedAgain = leakyBucket.serialize(mapper); 39 | LeakyBucket leakyBucketAgain = LeakyBucket.fromSerialized(mapper, serializedAgain); 40 | 41 | assertFalse(leakyBucketAgain.add(1)); 42 | } 43 | 44 | @Test 45 | public void testLapseShort() throws Exception { 46 | ObjectMapper mapper = new ObjectMapper(); 47 | String serialized = "{\"bucketSize\":2,\"leakRatePerMillis\":8.333333333333334E-6,\"spaceRemaining\":0,\"lastUpdateTimeMillis\":" + (System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(1)) + "}"; 48 | 49 | LeakyBucket leakyBucket = LeakyBucket.fromSerialized(mapper, serialized); 50 | assertFalse(leakyBucket.add(1)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.tests.storage; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.whispersystems.textsecuregcm.storage.Account; 6 | import org.whispersystems.textsecuregcm.storage.Device; 7 | 8 | import java.util.HashSet; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import static org.junit.Assert.assertFalse; 12 | import static org.junit.Assert.assertTrue; 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.when; 15 | 16 | public class AccountTest { 17 | 18 | private final Device oldFirstDevice = mock(Device.class); 19 | private final Device recentFirstDevice = mock(Device.class); 20 | private final Device agingSecondaryDevice = mock(Device.class); 21 | private final Device recentSecondaryDevice = mock(Device.class); 22 | private final Device oldSecondaryDevice = mock(Device.class); 23 | 24 | @Before 25 | public void setup() { 26 | when(oldFirstDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(366)); 27 | when(oldFirstDevice.isActive()).thenReturn(true); 28 | when(oldFirstDevice.getId()).thenReturn(1L); 29 | 30 | when(recentFirstDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)); 31 | when(recentFirstDevice.isActive()).thenReturn(true); 32 | when(recentFirstDevice.getId()).thenReturn(1L); 33 | 34 | when(agingSecondaryDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31)); 35 | when(agingSecondaryDevice.isActive()).thenReturn(false); 36 | when(agingSecondaryDevice.getId()).thenReturn(2L); 37 | 38 | when(recentSecondaryDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)); 39 | when(recentSecondaryDevice.isActive()).thenReturn(true); 40 | when(recentSecondaryDevice.getId()).thenReturn(2L); 41 | 42 | when(oldSecondaryDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(366)); 43 | when(oldSecondaryDevice.isActive()).thenReturn(false); 44 | when(oldSecondaryDevice.getId()).thenReturn(2L); 45 | } 46 | 47 | @Test 48 | public void testAccountActive() { 49 | Account recentAccount = new Account("+14152222222", new HashSet() {{ 50 | add(recentFirstDevice); 51 | add(recentSecondaryDevice); 52 | }}); 53 | 54 | assertTrue(recentAccount.isActive()); 55 | 56 | Account oldSecondaryAccount = new Account("+14152222222", new HashSet() {{ 57 | add(recentFirstDevice); 58 | add(agingSecondaryDevice); 59 | }}); 60 | 61 | assertTrue(oldSecondaryAccount.isActive()); 62 | 63 | Account agingPrimaryAccount = new Account("+14152222222", new HashSet() {{ 64 | add(oldFirstDevice); 65 | add(agingSecondaryDevice); 66 | }}); 67 | 68 | assertTrue(agingPrimaryAccount.isActive()); 69 | } 70 | 71 | @Test 72 | public void testAccountInactive() { 73 | Account oldPrimaryAccount = new Account("+14152222222", new HashSet() {{ 74 | add(oldFirstDevice); 75 | add(oldSecondaryDevice); 76 | }}); 77 | 78 | assertFalse(oldPrimaryAccount.isActive()); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/org/whispersystems/textsecuregcm/tests/util/BlockingThreadPoolExecutorTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.tests.util; 2 | 3 | import org.junit.Test; 4 | import org.whispersystems.textsecuregcm.util.BlockingThreadPoolExecutor; 5 | import org.whispersystems.textsecuregcm.util.Util; 6 | 7 | import static org.junit.Assert.assertTrue; 8 | 9 | public class BlockingThreadPoolExecutorTest { 10 | 11 | @Test 12 | public void testBlocking() { 13 | BlockingThreadPoolExecutor executor = new BlockingThreadPoolExecutor(1, 3); 14 | long start = System.currentTimeMillis(); 15 | 16 | executor.execute(new Runnable() { 17 | @Override 18 | public void run() { 19 | Util.sleep(1000); 20 | } 21 | }); 22 | 23 | assertTrue(System.currentTimeMillis() - start < 500); 24 | start = System.currentTimeMillis(); 25 | 26 | executor.execute(new Runnable() { 27 | @Override 28 | public void run() { 29 | Util.sleep(1000); 30 | } 31 | }); 32 | 33 | assertTrue(System.currentTimeMillis() - start < 500); 34 | 35 | start = System.currentTimeMillis(); 36 | 37 | executor.execute(new Runnable() { 38 | @Override 39 | public void run() { 40 | Util.sleep(1000); 41 | } 42 | }); 43 | 44 | assertTrue(System.currentTimeMillis() - start < 500); 45 | 46 | start = System.currentTimeMillis(); 47 | 48 | executor.execute(new Runnable() { 49 | @Override 50 | public void run() { 51 | Util.sleep(1000); 52 | } 53 | }); 54 | 55 | assertTrue(System.currentTimeMillis() - start > 500); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/org/whispersystems/textsecuregcm/tests/util/JsonHelpers.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.tests.util; 2 | 3 | import com.fasterxml.jackson.core.JsonParseException; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | 8 | import java.io.IOException; 9 | 10 | import static io.dropwizard.testing.FixtureHelpers.fixture; 11 | 12 | public class JsonHelpers { 13 | 14 | private static final ObjectMapper objectMapper = new ObjectMapper(); 15 | 16 | public static String asJson(Object object) throws JsonProcessingException { 17 | return objectMapper.writeValueAsString(object); 18 | } 19 | 20 | public static T fromJson(String value, Class clazz) throws IOException { 21 | return objectMapper.readValue(value, clazz); 22 | } 23 | 24 | public static String jsonFixture(String filename) throws IOException { 25 | return objectMapper.writeValueAsString(objectMapper.readValue(fixture(filename), JsonNode.class)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/whispersystems/textsecuregcm/tests/util/UrlSignerTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.textsecuregcm.tests.util; 2 | 3 | import com.amazonaws.HttpMethod; 4 | import org.junit.Test; 5 | import org.whispersystems.textsecuregcm.configuration.S3Configuration; 6 | import org.whispersystems.textsecuregcm.util.UrlSigner; 7 | 8 | import java.net.URL; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | import static org.mockito.Mockito.mock; 12 | import static org.mockito.Mockito.when; 13 | 14 | public class UrlSignerTest { 15 | 16 | @Test 17 | public void testTransferAcceleration() { 18 | S3Configuration configuration = mock(S3Configuration.class); 19 | when(configuration.getAccessKey()).thenReturn("foo"); 20 | when(configuration.getAccessSecret()).thenReturn("bar"); 21 | when(configuration.getAttachmentsBucket()).thenReturn("attachments-test"); 22 | 23 | UrlSigner signer = new UrlSigner(configuration); 24 | URL url = signer.getPreSignedUrl(1234, HttpMethod.GET); 25 | 26 | System.out.println("The URL: " + url); 27 | assertThat(url).hasHost("attachments-test.s3-accelerate.amazonaws.com"); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/contact.json: -------------------------------------------------------------------------------- 1 | { 2 | "token" : "BQVVHxMt5zAFXA" 3 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/contact.relay.json: -------------------------------------------------------------------------------- 1 | { 2 | "token" : "BQVVHxMt5zAFXA", 3 | "relay" : "whisper" 4 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/contact.relay.voice.json: -------------------------------------------------------------------------------- 1 | { 2 | "token" : "BQVVHxMt5zAFXA", 3 | "voice" : true, 4 | "relay" : "whisper" 5 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/current_message_extra_device.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages" : [{ 3 | "type" : 1, 4 | "destinationDeviceId" : 1, 5 | "body" : "Zm9vYmFyego", 6 | "timestamp" : 1234 7 | }, 8 | { 9 | "type" : 1, 10 | "destinationDeviceId" : 3, 11 | "body" : "Zm9vYmFyego", 12 | "timestamp" : 1234 13 | }] 14 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/current_message_multi_device.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages" : [{ 3 | "type" : 1, 4 | "destinationDeviceId" : 1, 5 | "destinationRegistrationId" : 222, 6 | "body" : "Zm9vYmFyego", 7 | "timestamp" : 1234 8 | }, 9 | { 10 | "type" : 1, 11 | "destinationDeviceId" : 2, 12 | "destinationRegistrationId" : 333, 13 | "body" : "Zm9vYmFyego", 14 | "timestamp" : 1234 15 | }] 16 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/current_message_registration_id.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages" : [{ 3 | "type" : 1, 4 | "destinationDeviceId" : 1, 5 | "destinationRegistrationId" : 222, 6 | "body" : "Zm9vYmFyego", 7 | "timestamp" : 1234 8 | }, 9 | { 10 | "type" : 1, 11 | "destinationDeviceId" : 2, 12 | "destinationRegistrationId" : 999, 13 | "body" : "Zm9vYmFyego", 14 | "timestamp" : 1234 15 | }] 16 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/current_message_single_device.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages" : [{ 3 | "type" : 1, 4 | "destinationDeviceId" : 1, 5 | "body" : "Zm9vYmFyego", 6 | "timestamp" : 1234 7 | }] 8 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/legacy_message_single_device.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages" : [{ 3 | "type": 1, 4 | "destination": "+14151111111", 5 | "body": "Zm9vYmFyego", 6 | "timestamp": "1234" 7 | }] 8 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/mismatched_registration_id.json: -------------------------------------------------------------------------------- 1 | { 2 | "staleDevices" : [2] 3 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/missing_device_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "missingDevices" : [2], 3 | "extraDevices" : [] 4 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/missing_device_response2.json: -------------------------------------------------------------------------------- 1 | { 2 | "missingDevices" : [2], 3 | "extraDevices" : [3] 4 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/prekey.json: -------------------------------------------------------------------------------- 1 | { 2 | "deviceId" : 1, 3 | "keyId" : 1234, 4 | "publicKey" : "test", 5 | "identityKey" : "identityTest", 6 | "registrationId" : 987 7 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/prekey_v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "keyId" : 1234, 3 | "publicKey" : "test" 4 | } -------------------------------------------------------------------------------- /system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | --------------------------------------------------------------------------------