UNIVERSITY OF THE PHILIPPINES MANILA COLLEGE OF ARTS AND SCIENCES DEPARTMENT OF PHYSICAL SCIENCES AND MATHEMATICS
RELIABLE AND ROBUST FILE TRANSFER SYSTEM OVER SHORT MESSAGING SERVICE
A special problem in partial fulfllment
of the requirements for the degree of
Bachelor of Science in Computer Science
Submitted by:
Axel Philip Advento
May 2018
Permission is given for the following people to have access to this SP:
Available to the general public Yes Available only after consultation with author/SP adviser No Available only to those bound by confdentiality agreement No ACCEPTANCE SHEET
The Special Problem entitled “Reliable and Robust File Transfer System over Short Messaging Service” prepared and submitted by Axel Philip Advento in partial fulfllment of the requirements for the degree of Bachelor of Science in Computer Science has been examined and is recommended for acceptance.
______Gregorio B. Baes, Ph.D. (candidate) Adviser
EXAMINERS: Approved Disapproved 1. Avegail D. Carpio, M.S. ______2. Richard Bryann L. Chua, Ph.D. (candidate) ______3. Perlita E. Gasmen, M.S. (candidate) ______4. Marvin John C. Ignacio, M.S. (candidate) ______5. Ma. Shiela A. Magboo, M.S. ______6. Vincent Peter C. Magboo, M.D., M.S. ______7. Geoffrey A. Solano, Ph.D. (candidate) ______
Accepted and approved as partial fulfllment of the requirements for the degree of Bachelor of Science in Computer Science.
______Ma. Shiela A. Magboo, M.S. Marcelina B. Lirazan, Ph.D. Unit Head Chair Mathematical and Computing Sciences Unit Department of Physical Sciences Department of Physical Sciences and Mathematics and Mathematics
______Leonardo R. Estacio, Jr., Ph.D. Dean College of Arts and Sciences
Abstract
This study presents a reliable and robust method for transferring files over Short Messaging Service. By testing the system against a set of repeated rigid test cases and fault injection methods, it has proven itself reliable and robust enough to survive problems found on an unreliable SMS service. Protection against adversaries are also implemented as a feature using security measures such as encryption and authentication.
Results show an effective alternative for sending and receiving files but show noticeable drawbacks in terms of speed. The work presented here has profound implications for future studies of utilizing low bandwidth but widely accessible means of communications such as Short Messaging Service and may help solve the problem of data transmission in places where Internet connectivity is unavailable or where other means of transmission of data is hazardous or impossible.
Keywords: File transfer, Short Messaging Service, Reliability, Robustness
ii Contents
Abstract ii
I. Introduction 1
A. Background of the Study ...... 1
B. Statement of the Problem ...... 4
C. Objectives of the Study ...... 6
D. Significance of the Study ...... 7
E. Scope, Limitations, and Assumptions ...... 8
II. Review of Related Literature 10
III. Theoretical Framework 13
A. Reliability ...... 13
B. Robustness ...... 13
C. Data Compression ...... 14
D. Binary-to-Text Encoding ...... 15
IV. Design and Implementation 17
A. Protocol ...... 17
iii B. Hardware Setup ...... 22
C. Software and Libraries Used ...... 22
D. Hardware and Software Requirements ...... 24
V. Results 26
VI. Discussions 37
VII. Summary and Conclusions 40
VIII. Recommendations 41
Bibliography 43
Appendix 47
A. Source Code ...... 47
Acknowledgements 157
iv I. Introduction
A. Background of the Study
File Sharing Methods and Technologies
File sharing can be defined as "a method of distributing computer files, … among a large number of users".[24] Such practice enabled users to share various digital data such as documents, multimedia, and computer programs across the world.
Files can be shared in a number of ways. They can be distributed using removable storage media, over wired connections, or wirelessly such as wireless Ethernet and mobile data.
Files were first exchanged using non-volatile physical media. In 1725, Basil Bouchon used punched paper rolls to store instructions for machines. In 1846, punch tapes became viable for use in recording telegrams. Data stored on these storage devices were more of instructions for machines rather than actual recorded data, however. In 1880, Hermal Hollerith invented recording data on punch cards which ultimately became a format that IBM used in the following decades.
Afterwards, various magnetic media such as magnetic tapes (used in 1951 as data storage medium), drum memory (1946), hard disk drives (1956), and floppy disks (1971) were used as storage media. Magnetic tapes and floppy disks saw widespread use as removable media. Around mid-1980s, specifications for storing digital signals on optical discs are completed which led to the development of Compact Discs (CDs), Digital Video Discs (DVDs), and Blu-ray Discs (BD).
1 Electronic flash memory were introduced for storing digital data later on. Examples include CompactFlash cards, Secure Digital (SD) cards, Memory Sticks, and USB Flash Drives.
Concurrently, file sharing has also been a practice through wired connections. During 1970s up to late 1980s, file sharing was mostly done over landline telephones and modems using systems such as Usenet, BBS, and XModem. Internet Service Providers (ISPs) emerged on 1990s and the Internet became fully commercialized by 1995. This gave way to modern methods of distributing files such as the File Transfer Protocol (FTP), the World Wide Web, Internet Relay Chat (IRC) programs, Instant Messaging, and later on, the BitTorrent protocol.[13]
Wireless connectivity made the Internet accessible to people who don't have access to wired connections and people living on locations where setting up infrastructure for wired services would be difficult or impossible. In 1996, access to mobile web was made possible with the introduction of the Nokia 9000 Communicator phone. It was done over 2G cellular networks, which initially do not offer data services. During the early 2000s, Wireless Access Protocol (WAP) and i-mode achieved popularity as means of accessing the mobile web and Internet services. In
2000, General Packet Radio Service (GPRS) was introduced as a packet-switched data service, which extends Global System for Mobile (GSM) Packet circuit-switched data capabilities. While in 2003, Enhanced Data rates for GSM Evolution (EDGE) was deployed on GSM networks which allows improved data transmission rates over GPRS.
2 The first Internet-ready satellite was made available for consumers in 2003. Satellite Internet was targeted mainly to rural areas as an alternative to traditional Internet connections such as dial-up and ADSL. The first commercial Third Generation (3G) network was also finally launched in the same year.
With the advent of 3G cellular networks and its extensions, higher data speeds and better security compared to 2G networks are observed on 3G-capable devices. However, adoption in some countries are slowed down due to the large cost of upgrades and spectrum licensing fees. In 2013, Fourth Generation technologies (4G) started to roll out. Technologies can only be considered 4G if "a substantial level of improvement in performance and capabilities with respect to the initial third generation systems now deployed".[8]
Short Messaging Service (SMS) and Its State in the Philippines
Short Messaging Service is a component of web, telephone, and mobile device networks that enables users to send and receive text messages. SMS is part of standardized communication protocols that were defined in 1985 as part of the GSM standards. [25]
SMS messages are sent to Short Message Service Centers (SMSC), which then attempts to forward the message to the recipient. This delivery however is "best-effort". In case the SMSC cannot reach the intended recipient, it keeps the message for a duration set by the mobile operator and retries until the recipient becomes available again, the message expires, or the retry limit set by the operator has been reached. This also means that there are no guarantees that the message will
3 be delivered on time or be delivered to the recipient at all. Despite being best-effort, SMS is an almost reliable service with an observed failure rate as high as 5.1% during normal operation conditions.[12]
The Philippine Statistics Authority reported in 2017 that an estimated 8,957,952 people are subscribed on an Internet Service Provider which includes wired DSL, cable Internet, and wireless Internet. The reported number of active mobile telephone service subscriptions, on the other hand, is 130,319,459, which is more than the country's total population.[15] Various studies and news sources also consistently place the Philippines as the "text messaging capital of the world".[5]
B. Statement of the Problem
With the current technologies available to consumers today, ideally anyone should be able to send and receive digital data conveniently anytime and anywhere. Unfortunately, these methods and technologies are not as accessible or widely available in the real world.
First, transferring files over wired connections is limited only to areas where wired telephone networks and ISPs had laid out infrastructure for their services.
These areas exclude remote rural communities and areas where building infrastructure would be challenging or just impossible.
Wireless Internet, on the other hand, has its own challenges as well. Satellite Internet typically requires a completely clear line of sight and is sensitive to even minor obstructions. 3G and 4G networks have limited coverage and availability
4 compared to traditional 2G networks. An assessment of 2G and 3G mobile phone networks found that UMTS networks perform worse compared to GSM networks in terms of coverage.[20] These 3G networks will have problems covering an area with an otherwise good GSM coverage unless aided by a significantly increased power consumption and additional equipment. Signals attenuate more and higher noise figures can be observed on higher frequency channels that are used on 3G and 4G networks (~5MHz and 40MHz respectively). 3G and 4G technologies also demands for stricter signal-to-noise ratio requirements.[22]
While network carriers would like to advertise that they have "100% 3G and 4G coverage", that would not be exactly true.[7] As mentioned earlier, theoretically 3G and 4G networks would perform worse than 2G networks in terms of coverage. Additionally, in the Philippines, 3G and 4G are not as widespread as real-world data shows. Over 63 percent of Filipinos are still not connected to the Internet. This is mainly attributed to lack of infrastructure and lack of mobile Internet coverage and network access.[11] On a later report, it was found out that in the Philippines mobile Internet users get 3G or 4G connections only 68.63% of the time.[13] Compared with the rest of the world, the Philippines severely lags behind on modern mobile Internet both in terms of speed and availability.[14]
Transporting files over physical non-volatile removable media has its share of its own problems too. There is still a possibility of the data being corrupted in the removable storage being used. There are also chances of the removable media being damaged or lost during transit. Weather, terrain, and other hazards will heavily affect
5 the success and speed of this method. In case of failure, the whole process of copying to and transporting the physical media needs to be repeated again.
C. Objectives of the Study
With the limited availability and accessibility of technologies that enable people to connect and share digital data, there is a need to produce a method that makes sharing digital data more accessible. For this reason, it appeared reasonable to attempt to develop a system with the following criteria:
1. The system should be able to send and receive files over SMS. In terms of availability and coverage, 2G is a suitable candidate despite being an older technology. SMS is also a viable as data carrier due to its simplicity, low cost, and its ubiquity eliminating the need for specialized equipment and technologies.
2. The protocol to be used should be reliable and robust enough to address the following issues inherent on Short Messaging Service:
a. SMS is still just a best-effort service.[12] The system should be able to request and retransmit lost messages during the file transfer.
b. There is no guarantee that SMS messages will arrive instantaneously nor arrive in order.[12] The system should have a mechanism to sort the messages received in order.
c. There is also a possibility that an SMS message intended for a previous state or session has been received. The system should be able to handle such cases.
6 d. In case the received SMS messages are incomplete or erratic, there should be a verification mechanism to validate each message received by both sending and receiving ends.
3. The protocol must allow the users to transfer files securely barring any adversary to be able to read or manipulate the files and keys being transferred. This can be done by a combination of the use of the Advanced Encryption Standard (AES) and Rivest-Shamir-Adleman (RSA) algorithms, which is used by majority of technologies used today.
4. The system must give the users a convenient way to to pair with another user's system, a requirement for transferring files securely.
5. The system should be able to transfer files over SMS regardless of their type or content.
D. Significance of the Study
The results of this study will benefit people who have the need to share digital data but are unable to due to other methods being unavailable, inconvenient, or hazardous. The system that will be developed will serve as an alternative to other methods of transferring files.
This system can be used by private organizations and government agencies doing data collection that requires information gathered be collected as soon as possible, such as beneficiaries of government welfare programs that require immediate assistance, disaster recovery assistance data, and earthquake/flood mapping data.
7 This can also serve rural government offices that do not have stable Internet connection but still require constant information exchange such as police, local government, etc.
People in the scientific field may utilize this to communicate important metrics such as volcanic activity and weather data when wired or wireless Internet is unavailable and conditions for manual transporting data are unfavorable.
In case 3G and 4G connectivity fails but the location has good reception for sending and receiving SMS messages, in automated election systems this is viable as a last resort for wireless communications before defaulting to manual transport of storage media. This can alleviate concerns over safety of data and personnel when terrain, weather, and other factors increase hazard if transporting the data physically.
Finally, the protocol, the software, and the system developed can be a framework for development of future technologies that can take advantage of SMS and its successors in the future beyond third and fourth generation networks.
E. Scope, Limitations, and Assumptions
This study will be limited to the bounds specified below:
1. While the protocol to be formulated is intended to be platform and hardware independent, the software to be developed for this study will be limited only to the phones and modems supported by the Gammu project and most Android devices.
8 2. There will be a huge drawback in terms of transmission speed which is caused by the non-instantaneous delivery and slow nature of SMS.[6][12][18][19]
3. The software will only allow transmission of files of sizes less than or equal to 10 megabytes at a time due to the limit of 160 7-bit characters for each SMS message, which will constrict the number of characters reserved for both the segment numbers and the payload. This should be enough for common file sizes of 2-12 kilobytes for images, 4-20 kilobytes for documents, and 3-4 megabytes for a full mp3 song.[17]
4. There should be enough signal strength and network coverage for the hardware components to be able to send and receive SMS messages properly.
5. This study assumes that the sender and receiver Subscriber Identification Modules (SIMs) and their respective network subscriptions are allowed to send and receive unlimited SMS messages.
6. Man-in-the-middle (MiTM) attacks would not be entirely impossible. The only security provided by the protocol to be formulated would be authentication and encryption for the keys and files exchanged using the combination of RSA and
AES.
7. Denial-of-Service Attacks by an adversary will not be addressed in this study.
9 II. Review of Related Literature
People who have limited or no access to wired and wireless technologies such as ADSL, 3G networks, and 4G networks will benefit from having a more accessible alternative method of sending data. Sending files over SMS is one possible way to achieve this. Despite the topic being given limited attention, sending binary data over SMS has been explored in various ways.
As early as 2008, an SMS-based transport layer has been designed and developed by Rao et. al.[18] Having GPRS and other technologies being unavailable in some areas has been a motivation for the study with the intention of completely bypassing GPRS networks and mobile ISPs. It was implemented by converting TCP packets and UDP datagrams into payloads and headers carried by SMS messages, sending significantly smaller packets or datagrams in batches, if possible. It was found out that such scheme is efficient for infrequent and non-bulky data transfers such as micro-browsing or Yellow Page searches. Its use for frequent and bulky data transfers is considered expensive and unoptimized however. Additionally, using the scheme for time critical applications is highly discouraged.
An attempt to provide a free Internet access has been developed by using an Internet-connected server and a specialized client browser that communicates with each other via SMS.[19] This method however is intended only for basic web browsing, stripping off images and other unnecessary elements leaving only the HTML source. It was concluded that while web browsing is possible, the service was deemed too slow for practical use despite the attempts of stripping out elements to further speed up the browsing experience.
10 A method of sending images over SMS as an alternative to Multimedia Messaging Service (MMS) has been presented on a separate study.[21] Motivated by the unavailability of MMS in some areas, the study attempts to provide a more accessible alternative. Unlike the previously discussed study by Rao et. al., the method used in this study simply converts the images into a text file using Base 64 encoding and sends the encoded data through several SMS messages. This eliminates the overhead introduced by the TCP or UDP protocols. However, SMS's default 7-bit encoding seems to be underutilized by using Base 64 as the encoding scheme for the data.
Another study tested the viability of data transmission using SMS as an alternative over GPRS. It was found out that SMS in general works in a slow but consistent pace while GPRS using UDP is faster but in an inconsistent manner.[6] Additionally, using the data gathered in the study, it was concluded that UDP will always be faster than SMS when sending large amounts of data even if UDP retries to send the correct data and if SMS had perfect transmission rates. Similar to the method of sending images over SMS previously discussed earlier, the whole file is encoded and split into different SMS messages. This time, a header is included together with a CRC32 checksum. The CRC32 checksum is used to verify the integrity of each message while the header value contains the queue number, which is requested by the receiver in case of timeouts or missing parts.
From the existing studies previously presented and discussed, it can be inferred that SMS as a data bearer will be slow. Additionally, using SMS for time critical applications should be strongly discouraged. An SMS message's size would be too
11 small to fit a single file so splitting a file into smaller parts would be unavoidable. Using an efficient encoding scheme would also be needed due to SMS messages' textual nature. SMS messages aren't guaranteed to be delivered at all times so a method to retransmit missing parts would also be necessary.
12 III. Theoretical Framework
A. Reliability
Reliability can be defined as a quality of a protocol that ensures the accuracy and integrity of transmitted data. Reliable protocols guarantee that the data will be delivered to the recipient. This ensures that at least one copy of the message will be always delivered to the recipient. A reliable protocol will always verify that all data transmitted is received in the correct order and is intact.
On the other hand, reliability can be defined in terms of failure. If a protocol can handle a certain set of well-defined and understood failures, then it can be considered reliable with respect to those failures.[27]
For the purpose of this study, we will use both approaches but with more emphasis on guaranteed delivery, integrity, and correct order of delivered data. During the development and implementation of the protocol, certain set of tests will be done to ensure that the protocol will be reliable against a limited set of well-defined failure scenarios.
B. Robustness
Robustness can be defined as "the degree to which a system or component can function correctly in the presence of invalid inputs or stressful environmental conditions".[26] Fuzz testing is a common tool for demonstrating a system's robustness. Alternatively, fault injection can be used for testing robustness.
13 Fault injection techniques will be used in testing the protocol to be developed, given its effectiveness in testing for error handling in code paths as well as its predictability so that bugs can be easier to track and fix. These will be implemented in test cases where the input is mutated in a controlled manner and the use of custom components in place of the ones expected by the system, which produces repeatable and well-defined faulty scenarios.
C. Data Compression
Data compression is defined as encoding information using fewer bits than the original representation. Compression can be classified as either lossless or lossy compression. Lossy compression minimizes the space needed by removing parts of data that are deemed unnecessary or less important. Lossless compression uses various methods in reducing size without loss of data.
14 The common factors to be considered when choosing a compression algorithm are compression/decompression speed, memory usage, and compression ratio. Compression ratio is defined as the ratio between the uncompressed size and the compressed size of the data:
CompressionRatio = UncompressedSize CompressedSize Since this project deals with SMS, a slow data bearer, compression is a necessary tool to decrease the time needed for a transmission to be completed. This project will also not be using lossy compression.
The compression algorithm that will be used for this project will be selected based on compression ratio alone. Compression/decompression speeds and memory usage do not matter since modern computers can handle these without problems and delays produced by a slow compression algorithm is insignificant if compared to the total time needed to transmit the compressed data. With this, the LZMA algorithm is chosen based on benchmark results.[2][10] The XZ utility program will be used for handling LZMA and LZMA2 compression algorithms.[3]
D. Binary-to-Text Encoding
Binary-to-Text encoding is simply the action of encoding binary data into plain text. This is necessary if the transmission method does not support sending of binary data. Common examples include Base16, Base64, and MIME.
For the purposes of this study, a slightly modified ASCII85 encoding is used due to its high efficiency of 80%, which is more efficient than common encoding
15 schemes like uuencode or Base64. The encoding scheme is also optimal for SMS's default 7-bit character encoding reducing the size of the encoded data while at the same time, taking advantage of a slightly larger character set.
16 IV. Design and Implementation
A. Protocol
Overview
The TransmiSMS file transfer protocol* is designed to be a simple, connection-oriented, reliable, and robust protocol. The protocol aims to facilitate sending and receiving of files over SMS using the least amount of SMS messages sent as possible while providing essential features that provides reliability, robustness, security, privacy, and convenience.
The most basic unit of data used by the TransmiSMS file transfer protocol is called a Protocol Data Unit (PDU). Each PDU is contained in a single SMS message and contains identifiers for its type, Session ID, its segment number, checksum (if already connected), human-readable instructions for force disconnecting (when not yet connected), and user data.
Transferring files and pairing with a peer starts with establishing a connection. The connection process is a three-way handshake, initiated by the sender/initiator.
This prevents the sender from flooding the receiver if the receiver is not yet ready to receive data and to prevent unsolicited file transfers. During the handshake, each PDU contains human-readable instructions on how to reject and force disconnect the connection.
* Not to be confused with FTP; this protocol is not related to the File Transfer Protocol specified by RFC 959.[16]
17 Terminating the connection is also done via a three-way handshake normally initiated by the sender/initiator. This ensures that all parties are notified about each other's disconnection state. After sending the last PDU, the initiator waits for a while to retransmit missing PDUs in case the other end has failed to receive them.
File transfer and key exchange operations are managed via sessions. Each session is assigned a Session ID which is a 128-bit Universally Unique Identifier (UUID) negotiated during the establishment of the connection. UUIDs have an almost zero percent chance of collisions so no two Session IDs will be the same. This is done so that PDUs not belonging to the current Session can be safely ignored and prevent them from interrupting the current operation.
The protocol will also ignore duplicate PDUs and PDUs not appropriate for the current connection state. Invalid and malformed PDUs will also be ignored.
Pairing
Before the sender and the receiver can use security features such as encryption and authentication, they must be paired first prior the file transfer. Pairing means exchanging necessary RSA public keys for encryption and authentication. This can be done over the air or manually by importing the appropriate keys.
Pairing the keys over the air is a three-step process. An initiator party must send a pairing request to the other end in order to establish the connection. After the responder has accepted the request, they both exchange Base85-encoded encryption
18 and authentication keys split into multiple parts. After confirming that both parties have received the keys correctly, the initiator then requests disconnection.
Optionally, after receiving the encryption and authentication keys, the parties are allowed to further authenticate they keys that they have received. They are presented with an easy to read set of words which they communicate and compare over a secure channel to confirm that no man-in-the-middle attack has taken place.
File Transfer
File transfer starts with the sender requesting a file transfer request to the receiver. After the connection is established and the receiver has agreed to receive the file, both ends exchange information for the file transfer.
If security features are enabled, the receiver first generates a new AES key for file encryption and encrypts it using the sender's RSA public key. The receiver then sends the encrypted key split into multiple parts via multiple INIT PDUs. The sender then decrypts the file encryption key using its own RSA private key. The sender can compress the file first using LZMA. The sender also needs to sign the unencrypted file using the receiver's authentication key. These can be done concurrently. The file can then be processed for encryption via the AES key afterwards. Optionally, the sender can further compress the encrypted file, if its size can be further reduced. It can then be Base85-encoded and split into segments. The sender then sends the number of segments, if compression is used, and the encoded RSA signature to the receiver via a series of META PDUs.
19 After receiving the META PDU, confirming the information provided, and allocated the necessary resources for receiving the file, the receiver will send a READY PDU to the sender to start the file transfer. The sender then proceeds to send the processed and encoded data using a series of DATA PDUs and sending and END PDU to signify that the sender has completed sending all segments.
After the receiver has confirmed receiving the all the DATA PDU segments, reordered the DATA PDUs received, assembled and processed the encoded data, and authenticated the signature sent by the sender, it sends its own END PDU to confirm that file transfer is successful. Upon receiving the END PDU, the sender then proceeds with the disconnection procedure.
Without the security features, the receiver just sends an empty INIT PDU to the sender to signify acceptance of the request. The sender first calculates the SHA-512 checksum of the file. The sender then skips the encryption and proceeds with the compression. Using the same encoding, the data is then encoded and split into parts. Using a META PDU, the sender sends the number of segments, the checksum, if compression is used, and the file's encoded checksum to the receiver. The whole process of sending and receiving the DATA PDUs proceeds along with the verification of the received data using SHA-512 instead of an RSA signature.
Retransmission and Timeouts
The protocol assumes that all previously sent PDUs are successfully received by the receiver unless a NACK request for retransmission is received. This is done to reduce the number of PDUs the receiver needs to transmit such as ACK for every
20 PDU received and to take advantage of SMS's low failure rate on normal operational conditions. Retransmission requests are sent in batches inside the least amount of NACK PDU necessary. On some states during the transmission, resending a PDU signals a request for retransmission of the previously sent PDU. PDUs can also retransmitted in case of timeouts.
The maximum PDU lifetime is set as 60 seconds. Not receiving any PDU for the duration of the maximum PDU lifetime x 2 will result in a timeout. The user will then be prompted if he/she wants to continue or if the connection must be terminated immediately.
Security
For file encryption, a 128-bit symmetric AES key will be used. Cipher Block Chaining (CBC) mode of operation for AES will be used as well. Additionally, Public Key Cryptography Standards #7 (PKCS#7) will be used for padding for added security. For file authentication, a 2048-bit asymmetric RSA key will be used together with PKCS#1 with SHA-512 hashing. The AES key that will be transmitted will be further encrypted using a 2048-bit asymmetric RSA key with Optimal Asymmetric Encryption Padding (OAEP) with SHA-256 for Mask Generation Function 1 (MGF1) padding.
To reduce predictability, for every file transfer session, a new AES key is generated. Additionally, only public keys are exposed during the pairing process. The user is also warned if the already paired peer requests to redo the pairing process.
21 During the file transfer, when receiving the encrypted AES key, the sender can immediately verify the receiver's authenticity. The receiver, however, can only verify the sender's authenticity if the sender failed to decrypt the encryption key sent or after all the parts of the file is sent successfully but the receiver cannot decrypt the file using its own keys. The receiver must be able to verify the authenticity of the decrypted file using the stored keys the receiver and the sender has exchanged prior the transmission.
Because the protocol allows a kill switch for the connection, an adversary with enough resources to spoof the other end can always terminate the connection prematurely. However, the adversary won't be able to fake a file transfer successfully nor view the contents of the keys and the files being exchanged.
B. Hardware Setup
The Hardware to be used for this project will consist of a sender and a receiver host. Each host will be a computer connected to either a compatible mobile phone or an Internet dongle which will function as a GSM modem. There should also be ample signal coverage for these modems to operate normally. The sender and receiver hosts can also switch roles depending on the tests needed to be performed.
C. Software and Libraries Used
In this study, Java is used as the programming language due to its cross-platform nature and the number of libraries available to be used. It has a native support for classes, events, and threading which is useful for the development of the
22 system and its components. The JavaFX platform is used for the Graphical User Interface (GUI) due to its consistent look and feel across different operating systems and its logical separation between program logic, GUI layout, and design.
As mentioned earlier, LZMA is used as the compression algorithm and a modified implementation of ASCII85 is used as the binary-to-text encoder due to their excellent compression ratio and encoding efficiency.
Gammu SMSD will be used as a service layer to interface with various mobile phones, GSM modems, and Internet dongles from different vendors removing the need to send AT commands directly.[4] A custom Android companion app was also developed to communicate with Android devices.
A combination of RSA and AES is used for encrypting data to be transfered over SMS. AES is used for encrypting the raw data to be sent while RSA is for encrypting the AES key to be shared between the sender and receiver. RSA is also used for authentication.
For verifying each PDU's integrity, CRC8 is used for its simplicity and small size. RSA authentication will also serve as verification for the file received. SHA-512 will be used in place of RSA for verifying files if RSA authentication is disabled.
Pretty Good Privacy (PGP) word list is used when manually authenticating key exchange over the air to detect man-in-the-middle attacks.
23 D. Hardware and Software Requirements
In order for the system to be fully operational, it requires the sender and receiver computers to meet the following requirements:
Hardware
• GSM modem or mobile phone capable of sending and receiving SMS messages with Gammu SMS support or an Android device capable of sending and receiving SMS (running at least Android 4.4)
• Bluetooth interface or USB/Serial Cable that comes with the GSM modem or mobile phone
• An available USB/Serial port when using USB/Serial interfaces
• 200 MB of available RAM or higher
• 20 MB of available hard disk space or higher
Software
• GNU+Linux, BSD, Mac OS, or Windows operating systems
• Java Runtime Environment 8 or newer
• Gammu 1.3 or newer (and a complete setup for the chosen Gammu backend)
24 • Drivers for the devices to be used
25 V. Results
The TransmiSMS File Transfer program is a JavaFX-based GUI frontend that enables users to send and receive files reliably over SMS. This file transfer system uses the TransmiSMS protocol designed for the purposes of this study.
The program features contact management, which allows the user to add, edit, and remove contacts. The user can then send and receive files, as well as pair and exchange security keys from these contacts. The program's toolbar allow the user to do the previously mentioned actions as well as the option to send files either via a secure or an unsecure connection.
Figure 1. Main Window with no Contacts Added yet
26 Figure 2. Add/Edit Contact Dialog
As soon as the program starts, it starts listening for connection requests from both registered contacts and non-registered users. It can be disabled by pressing the "Stop accepting" button on the infobar.
27 Figure 3. Main Window, Listening for New Connections
Figure 4. Main Window, Not Listening for New Connections
28 The program also detects if there are problems encountered from the SMS service being used. If such problems exist, the user must resolve these issues first before proceeding. Supported SMS services are Gammu SMSD via PostgreSQL and the Android Companion app developed mainly to enable the program to interact with Android devices.
Figure 5. Detected Problems with SMS Service
The program automatically saves the files received on a predetermined location. The user can change this on the preferences window, along with SMS backend-specific settings.
29 Figure 6. Preferences Window
The user will be prompted if he/she receives a file transfer request. A detailed prompt containing the senders' name, the sender's number if the sender is not yet in the user's contact list, the file name, and if the transfer will use a secure connection will be displayed.
30 Figure 7. Secure File Transfer Request Prompt
Figure 8. Unsecure File Transfer Request Prompt
The user will also be prompted in case he/she receives a pairing request. The sender's details are displayed on the prompt and if the pairing request has already been done before.
31 Figure 9. Pairing Request Prompt
Figure 10. Renew Pairing Request Prompt
If the receiver rejects the file transfer or pairing request, the sender is prompted immediately that the receiver has rejected the request.
32 Figure 11. Peer Reject/Disconnect Notification
Upon accepting the request, the connection will be established and the sender and receiver will then begin exchanging information. At this point, the progress of the key exchange or the file transfer will not yet be displayed.
Figure 12. Connection Established
33 After successfully exchanging information with each other, the sender and receiver will begin the transmission. The transmission progress will be both displayed on the sender's and receiver's GUI. The users will be then notified if the file transfer has been successful.
Figure 13. Sending File in Progress
34 Figure 14. Receiving File in Progress
Another feature implemented on this program is enabling users to manually verify keys exchanged by comparing the displayed PGP words on their screen. This can be done over a secure channel or at least on a trusted voice channel. The words are designed to be distinct from each other even when dictated over a phone call. This prevents man-in-the-middle attacks by an adversary.
35 Figure 15. Key Exchange Verification using PGP Word List
36 VI. Discussions
The conceived TransmiSMS program and protocol aims to provide users a more accessible alternative for sharing digital data. It uses SMS as a data bearer to make transmission of files possible without the need for an Internet connection provided by a wired or wireless provider. It can even take advantage of 2G's larger coverage that wired, 3G, and 4G connections are not able to achieve.
Using a set of repeated rigid test cases and fault injection during the development process and testing, both simulated and real world tests have all been successful. The following scenarios are tested during the simulations and if possible, on real hardware:
• delayed delivery of messages
• out of order delivery of messages
• tests if timeouts due to a faulty component will trigger in a timely manner
• occasional sending of duplicate PDUs
• occasional sending of PDUs not appropriate for the receiver's current state
• occasional sending of PDUs with invalid CRCs
• occasional corruption of PDUs received
• occasional sending of unrecognizable PDUs
37 • sending of PDUs to the wrong intended recipient
• consistent sending of extra PDUs with wrong Session IDs during an operation
• sending of invalid security information
• all combinations of invalid, empty, truncated, and missing values for all PDUs used by the protocol
The system proposed and developed proved to be reliable and robust enough to survive all these problems presented to it.
While the system can cope up with hardware problems up to a limited extent, total hardware failure and severe network problems can still render it useless. For instance, if the SMS device used suddenly hangs up, the user can still intervene, reset the device, and retry the connection. Network outages, however, won't even let the system to send a single message in the first place.
Secure connections are also offered by the system as an additional feature. SMS, a relatively old technology, is not entirely secure when faced with an adversary equipped with specialized hardware such as a Stingray phone tracker and sniffer to perform a MiTM attacks. The protocol designed introduced security measures to protect sensitive data being transmitted from such adversaries. While attackers can read plaintext SMS data and possibly interrupt ongoing file transfers, integrity of the files and keys sent and received will not be compromised. These adversaries will never be able to successfully spoof the sender's or receiver's identity nor sniff the encrypted data as long as the security features are enabled.
38 Connection setup times are measured to be around 5 seconds up to a full minute. Transfer speeds can be variable as SMS transmission speeds can vary depending on the network condition or if the SMS device decides to slow down sending of messages. Assuming an uncompressed and unencrypted file is sent over transmission speeds of one SMS per 5 seconds, it can reach speeds up to 11.1 kbps sans the connection and teardown phases. That is around 20% of the maximum speed of a 56.6kbps dial-up modem.
Unfortunately, SMS's sluggishness as a data bearer still became apparent despite the fact that measures to reduce the total amount of data to be transmitted are done. However, such measures are still invaluable in making transmission speeds more palatable to the end user.
39 VII. Summary and Conclusions
A reliable and robust file transfer system over SMS has been developed to address the needs of users with limited or no access to technologies normally used to send and receive data. It was proven reliable and robust enough to survive problems related to SMS's unreliable nature on both simulated and real world tests.
Various methods to reduce the total size of data transmitted has been implemented. Additional security measures such as encryption and authentication has also been implemented to provide protection against well-funded adversaries.
Despite successes in file transmission over SMS, its speed remains as its greatest drawbacks. Setup and teardown times are less than ideal in some cases. On a very congested or faulty network, the system would probably be still running but would run on a greatly reduced speed.
It is not recommended to use this for everyday use if other faster technologies are readily available. This system can still prove useful as an effective alternative in remote places where such technologies won't be accessible or when physically transporting data to another location might be hazardous or impossible.
40 VIII. Recommendations
While the resulting protocol and system proved to be reliable and robust for file transfers via SMS messages, there are still other areas that can be further improved:
• Ability to pause and resume file transfer sessions especially for larger files.
• An Android implementation to eliminate the need for a separate computer when transferring files. The implementation must also consider quirks present when developing on Android. This includes reduced performance during sleep, applications and services being dynamically woken, paused, or killed on memory-intensive loads, and an entirely different UI framework that also affects decisions when implementing multithreaded tasks.
• The use of Content Providers, which are companies and organizations that are allowed by mobile network operators to send and receive SMS messages at a different priority and at a faster rate. The source code is made to be easily extensible in case a need to implement a new SMS Service for a new device or backend arises.
• Further improvements to speed up connection setup time and teardown.
• Fully secured communications which might be possibly provided using open-source projects such as Signal and Silence
41 • Applications other than file sharing that requires exchange of binary data.
• Possible reuse of the protocol for future iterations and successors of SMS over future generation networks.
42 Bibliography
[1] Augustyniak, P. & Tadeusiewicz, R. (2009). Ubiquitous Cardiology: Emerging Wireless Telemedical Applications: Emerging Wireless Telemedical Applications. IGI Global.
[2] Collin, L. (2005). A Quick Benchmark: GZIP vs BZIP2 vs LZMA. Retrieved from https://tukaani.org/lzma/benchmarks.html
[3] Collin, L., (2016). XZ Utils. Retrieved from https://tukaani.org/xz/
[4] Čihař, M. (2018). [GW]ammu. Retrieved from https://wammu.eu/gammu/
[5] Dimacali, T. (2010). Philippines still Text Messaging Champ -US Study. GMA News. Retrieved from http://www.gmanetwork.com/news/scitech/content/19883 2/philippines-still-text-messaging-champ-us-study/story/
[6] Dykimching, A.M., Lee, J.A.A., & Yu, W.E. (2011). A Study on the aspect of data transmission on SMS interface. Journal of Information Security Research, vol. 2, no. 1, pp. 30-39.
[7] Globe Telecom (2014). Globe Telecom completes HSPA+ rollout; network now 100% 4G. Globe Telecom Press Release. Retrieved from http://www.globe.com.ph/press-room/network-now-100-4g
[8] International Telecommunication Union (2010). ITU World Radiocommunication Seminar highlights future communication technologies. Retrieved from https://www.itu.int/net/pressoffice/press_releases/2010/48.aspx
43 [9] Internet World Stats (2017). Asia Marketing Research, Internet Usage, Population Statistics and Facebook Subscribers. Retrieved from https://www.internetworldstats.com/asia.htm#ph
[10] Klausmann, T. (2008). GZIP, BZIP2, and LZMA Compared. Retrieved from http s://web.archive.org/web/20130106193958/blog.i-no.de//archives/2008/05/08/ind ex.html
[11] McKinsey & Company (2015). Offline and falling behind: Barriers to Internet adoption. Retrieved from https://www.mckinsey.com/industries/high-tech/our-in sights/offline-and-falling-behind-barriers-to-internet-adoption
[12] Meng, X., Zerfos, P., Samanta, V., Wong, S.H.Y., & Lu, S. (2007). Analysis of the Reliability of a Nationwide Short Message Service. 26th IEEE International Conference on Computer Communications, pp. 1811-1819.
[13] OpenSignal (2016). Global State of Mobile Networks, August 2016. Retrieved from https://opensignal.com/reports/2016/08/global-state-of-the-mobile-network
[14] OpenSignal (2018). The State of LTE, February 2018. Retrieved from https://opensignal.com/reports/2018/02/state-of-lte
[15] Philippine Statistics Authority (2017). Philippines in Figures 2017 p. 11. Retrieved from https://psa.gov.ph/sites/default/files/PIF_2017.pdf
[16] Postel, J., & Reynolds, J. (1985). RFC 959. FILE TRANSFER PROTOCOL (FTP)(Oct. 1985), Retrieved from ftp://ftp.rfc-editor.org/innotes/rfc959.txt
44 [17] QaamGo Media GmbH. Average File Sizes. Retrieved from https://blog.online-convert.com/average-file-sizes
[18] Rao, S., Vora, R., Dhar, S., Salvi, S., & Kumar, V. (2008). Development of a Transport Layer using SMS. 7th International Conference on Cognitive Systems, India.
[19] Sam, A. (2009). Free Wireless Internet through SMS. ADSA Life Development, Sweden.
[20] Scharnhorst, W., Hilty, & L., Jolliet, O. (2006). Life Cycle Assessment of Second Generation (2G) and Third Generation (3G) Mobile Phone Networks. Environment International 32.5 (2006) pp. 656-675.
[21] Shirali-Shahreza, M. & Shirali-Shahreza, S. (2009). Sending Pictures by SMS. 2009 11th International Conference on Advanced Communication Technology.
[22] Tolstrup, M. (2015). Indoor Radio Planning: A Practical Guide for 2G, 3G and 4G pp. 328-329, 349-350. John Wiley & Sons.
[23] Yahoo! & The Nielsen Company (2009). Yahoo Nielsen Internet Habits Study for
the Philippines. Yahoo!-Nielsen Press Release. Retrieved from https://www.scrib d.com/doc/13661672/Yahoo-Nielsen-Internet-Habits-Study-for-the-Philippines
[24] Collins English Dictionary - Complete & Unabridged 2012 Digital Edition. William Collins Sons & Co. Ltd., HarperCollins Publishers.
45 [25] GSM Doc 28/85 "Services and Facilities to be provided in the GSM System" rev2, June 1985
[26] IEEE Standard Glossary of Software Engineering Terminology, IEEE Std 610.12-1990
[27] Reliable Request-Reply Patterns. ØMQ - The Guide. Retrieved from http://zguide.zeromq.org/page:all#reliable-request-reply
46 /* Appendix compileJava { //options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" options.compilerArgs << "-Xlint:all" } */
jar { A. Source Code manifest { attributes 'Implementation-Title': 'Transmisms-core', 'Implementation-Version': version, 'Main-Class': mainClassName } exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA' // exclude Dummy SMS Service Backend and digests to avoid problems Simulator: } task fatJar(type:OneJar) { mainClass = mainClassName } from gevent import monkey; monkey.patch_all() # monkey all the things! import zmq.green as zmq artifacts { import gevent archives(jar) { group 'com.transmisms.smsftp' name 'smsftp-protocol' } initiator_address = 'tcp://127.0.0.1:8768' } responder_address = 'tcp://127.0.0.1:8769' mediator_address = 'inproc://dummymediator' repositories { jcenter() // because we love https and supersets mavenCentral() mavenLocal() def filter_ping(address1, address2, identity): } ctx = zmq.Context.instance() s1 = ctx.socket(zmq.PAIR) configurations { s1.bind(address1) integrationCompile.extendsFrom testCompile s1.linger = 0 integrationRuntime.extendsFrom testRuntime stagingCompile.extendsFrom testCompile s2 = ctx.socket(zmq.PAIR) stagingRuntime.extendsFrom testRuntime if(identity == 1): } s2.bind(address2) else: buildscript { s2.connect(address2) repositories { s2.linger = 0 jcenter() // because we love https and supersets mavenCentral() while True: mavenLocal() r = s1.poll(50) } if r != 0: msg = s1.recv() dependencies { if msg == "PING": classpath 'com.github.rholder:gradle-one-jar:1.0.4' s1.send("PONG") } else: } s2.send(msg) r = s2.poll(50) dependencies { if r != 0: compile group: 'com.github.rholder', name: 'gradle-one-jar', version: msg = s2.recv() '1.0.4' s1.send(msg) print "end of " + identity compile group: 'org.zeromq', name: 'jeromq', version: '0.4.3' compile group: 'com.google.code.gson', name: 'gson', version: '2.8.1' compile group: 'org.apache.commons', name: 'commons-io', version: '1.3.2' if __name__ == "__main__": compile group: 'commons-collections', name: 'commons-collections', version: g1 = gevent.spawn(filter_ping, initiator_address, mediator_address, 1)'3.2' g2 = gevent.spawn(filter_ping, responder_address, mediator_address, 2) compile group: 'commons-codec', name: 'commons-codec', version: '1.10' gs = [g1,g2] compile group: 'org.apache.commons', name: 'commons-compress', version: '1.10' gevent.joinall(gs) compile group: 'org.tukaani', name: 'xz', version: '1.5' compile group: 'org.apache.pdfbox', name: 'pdfbox', version: '1.8.11' compile group: 'net.sourceforge.javaflacencoder', name: 'java-flac-encoder', version: '0.3.7' compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.1' JavaFx UI: compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.1' compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.5.5' compile group: 'org.yaml', name: 'snakeyaml', version: '1.12' ------compile group: 'technology.zeroalpha.security', name: 'j-pgp-wordlist', Filename: build.gradle version: '0.1.1' ------compile group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: '1.9.0' compile group: 'org.kordamp.ikonli', name: 'ikonli-material-pack', version: apply plugin: 'java' // add java tasks '1.9.0' apply plugin: 'eclipse' // create eclipse project compile group: 'org.kordamp.ikonli', name: 'ikonli-materialdesign-pack', apply plugin: 'application' // prepare for packaging as application version: '1.9.0' apply plugin: 'gradle-one-jar' // for single self-contained jar outputs compile group: 'org.postgresql', name: 'postgresql', version: '42.1.4' // java jar configuration (manifest.mf in jar) //sourceCompatibility = '1.7' // NOTE: version lock on 1.56 due to constant API changes sourceCompatibility = '1.8' // See src/main/java/com/transmisms/core/util/crypto/RSA.java version = '0.9' compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.56' mainClassName = 'com.transmisms.ui.javafx.Main' compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.56' applicationDefaultJvmArgs = [] // set as empty for now
47 testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'org.testng', name: 'testng', version: '6.9.6' testCompile group: 'org.mockito', name: 'mockito-core', version: '1.10.19' ------integrationCompile sourceSets.main.output Filename: src/main/java/com/transmisms/core/protocol/BinaryPDUDecoder.java integrationCompile sourceSets.test.output ------integrationCompile configurations.testCompile integrationCompile configurations.testRuntime package com.transmisms.core.protocol;
stagingCompile sourceSets.main.output import com.transmisms.core.protocol.CoreProtocolFacade; stagingCompile sourceSets.test.output import com.transmisms.core.protocol.CoreProtocolFacade.DataSizes; stagingCompile configurations.testCompile import com.transmisms.core.protocol.CoreProtocolFacade.DataStrSizes; stagingCompile configurations.testRuntime import com.transmisms.core.protocol.PDU; } import com.transmisms.core.protocol.PDUType; import com.transmisms.core.protocol.PDUMalformedException; test { import com.transmisms.core.protocol.InvalidCRCException; include "**/**Suite.class" import com.transmisms.smsftp.protocol.SmsftpPDUType; } import com.transmisms.core.util.codec.Base85; sourceSets { import net.sourceforge.javaflacencoder.CRC8; test { java.srcDir 'src/test/unit/java' import java.nio.ByteBuffer; } import java.util.Arrays; import java.util.ArrayList; integration { import java.util.Hashtable; java.srcDir file('src/test/integration/java') import java.util.List; resources.srcDir file('src/test/resources') import java.util.Map; } import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; staging { import java.util.function.Function; java.srcDir file('src/test/staging/java') resources.srcDir file('src/test/resources') import java.io.IOException; } import java.io.UnsupportedEncodingException; } task integration(type: Test) { group 'Verification' public class BinaryPDUDecoder { description 'Runs the integration tests.' private Function
48 bb.put(segmentNumBytes).flip(); // flip() is required for reading public final Character pduTypeToPrefix(PDUType pduType) { return bb.getInt(); // then return the value Character retPrefix = } BinaryPDUDecoder.baseTypeToPrefixFunc.apply(pduType); if(retPrefix == null && this.typeToPrefixFunc != null) { protected List
49 crc.updateCRC8(crcBody.getBytes("UTF-8"), 0, case CORE_RETRANSMISSION: { crcBody.getBytes("UTF-8").length); AtomicInteger pointer = new AtomicInteger(0); byte computedCRC = crc.checksum(); String computedCrcStr = String.format("%02x", computedCRC); while(pointer.get() < body.length()) { if(!givenCRCStr.equalsIgnoreCase(computedCrcStr)) { char retPrefix = body.charAt( throw new InvalidCRCException("Invalid CRC! Got: " + pointer.getAndIncrement()); givenCRCStr + "; Expected: " + computedCrcStr, PDUType retPduType = this.prefixToPduType(retPrefix); encoded); } if(retPduType != null) { // if valid } Object segmentId = null; catch(UnsupportedEncodingException e) { try { // this should never happen; crash early if(retPduType == SmsftpPDUType.SMSFTP_DATA) { throw new IllegalStateException(e.getMessage(), e); String b85SegmentIdStr = body.substring( } pointer.getAndAdd( DataStrSizes.SEGMENTID.getSize()), String body = encoded.substring(1, encoded.length()-2); pointer.get()); // just to make sure that pduType is "initialized" Integer b85SegmentId = base85ToInteger( PDUType pduType = null; b85SegmentIdStr, encoded, { // get PDU type pduType.toString()); char firstChar = encoded.charAt(0); segmentId = b85SegmentId; pduType = this.prefixToPduType(firstChar); } if(pduType == null) { else { throw new PDUMalformedException( String hexSegmentIdStr = body.substring( "Invalid Binary PDU Prefix: " + firstChar, encoded); pointer.getAndAdd( } } DataStrSizes.HEXSEGMENTID.getSize()), pointer.get()); try { Integer hexSegmentId = getHexSegmentId( decodedData.addAll( hexSegmentIdStr, 256, encoded, this.defaultDecodeBody(pduType, encoded)); pduType.toString(), true); } segmentId = hexSegmentId; catch(PDUMalformedException e) { } List
// some sanity checks before parsing REQUEST_SENT, if(encoded == null) { REQUEST_SENT_2, // ignore for now throw new NullPointerException( REQUEST_SENT_3, // ignore for now "Binary encoded string cannot be null"); } CLOSE_WAIT, else if(encoded.equals("")) { FIN_WAIT, throw new PDUMalformedException( CLOSING_1, "Binary encoded string cannot be empty", encoded); CLOSING_2, } TIME_WAIT else if(encoded.length() < DataStrSizes.CRC.getSize()+1) { }; throw new PDUMalformedException("Binary encoded string too short", encoded); public final Role role; } public final String localMin; public String peerMin; try { // determine if checksum matched PDU body public int maxPduLifetime = 60000; // it doesn't matter that much if the last 2 chars of the string is // a valid hex string; they are just compared as strings private final AtomicReference
50 protected String finStatusComboReceived; CoreLookupTable.createResendCoreRepTask(t, u)); protected String encodedRepPdu; assert null == evalValue; protected String encodedHeadPdu; evalValue = lookupTable.put( protected String encodedFinPdu; new PdlEntry( protected String encodedFinackPdu; ConnectionState.REQUEST_RECEIVED, CorePDUType.CORE_HEAD), (t, u) -> private Connection(String localMin, String peerMin, Role role) { CoreLookupTable.createEstConnAsResponderTask(t, u)); this.localMin = localMin; assert null == evalValue; this.peerMin = peerMin; evalValue = lookupTable.put( this.role = role; new PdlEntry( } ConnectionState.REQUEST_SENT, CorePDUType.CORE_CONNECTION_RESPONSE), public static Connection createResponderConnection(String localMin) { (t, u) -> return new Connection(localMin, null, Role.RESPONDER); CoreLookupTable.createEstConnAsInitiatorTask(t, u)); } assert null == evalValue; public static Connection createInitiatorConnection(String localMin, evalValue = lookupTable.put( String peerMin) { new PdlEntry( return new Connection(localMin, peerMin, Role.INITIATOR); ConnectionState.ESTABLISHED, } CorePDUType.CORE_CONNECTION_RESPONSE), (t, u) ->CoreLookupTable.createResendCoreHeadIfValidTask( t, u)); assert null == evalValue; public int getErrorCount() { evalValue = lookupTable.put( return this.errorCount.get(); new PdlEntry( } ConnectionState.ESTABLISHED, CorePDUType.CORE_HEAD), (t, u) -> CoreLookupTable.createDoNothingTask(t, u)); public int incrementErrorCount() { assert null == evalValue; return this.errorCount.incrementAndGet(); evalValue = lookupTable.put( } new PdlEntry( ConnectionState.ESTABLISHED, CorePDUType.CORE_FIN), public ConnectionState getConnectionState() { (t, u) -> CoreLookupTable.createFinReceivedTask(t, u)); return this.connectionStateRef.get(); assert null == evalValue; } evalValue = lookupTable.put( new PdlEntry( public void setConnectionState(ConnectionState connectionState) { ConnectionState.FIN_WAIT, CorePDUType.CORE_FIN), if(connectionState == null) { (t, u) -> CoreLookupTable.createSimulFinReceivedTask(t, u)); throw new NullPointerException("Connection state cannot be null"); assert null == evalValue; } evalValue = lookupTable.put( this.connectionStateRef.set(connectionState); new PdlEntry( } ConnectionState.FIN_WAIT, CorePDUType.CORE_FINACK), } (t, u) -> CoreLookupTable.createFinwaitDisconnectTask(t, u)); assert null == evalValue; evalValue = lookupTable.put( new PdlEntry( ------ConnectionState.FIN_WAIT, CorePDUType.CORE_LAST_ACK), Filename: src/main/java/com/transmisms/core/protocol/CoreLookupTable.java (t, u) -> CoreLookupTable.createSimulLastackReceivedTask( ------t, u)); assert null == evalValue; package com.transmisms.core.protocol; evalValue = lookupTable.put( // from extended Termination FSM draft new PdlEntry( import com.transmisms.core.protocol.Connection.ConnectionState; ConnectionState.FIN_WAIT, import com.transmisms.core.protocol.Presenter.MessageType; CorePDUType.CORE_CONNECTION_RESPONSE), import com.transmisms.core.protocol.Presenter.PromptType; (t, u) -> import com.transmisms.core.protocol.Presenter.StatusType; CoreLookupTable.createResendCoreHeadIfValidTask(t, u)); assert null == evalValue; import java.util.Hashtable; evalValue = lookupTable.put( import java.util.List; new PdlEntry( import java.util.Map; ConnectionState.CLOSE_WAIT, CorePDUType.CORE_FIN), import java.util.UUID; (t, u) -> CoreLookupTable.createResendFinackTask(t, u)); import java.util.concurrent.Future; assert null == evalValue; import java.util.function.BiFunction; evalValue = lookupTable.put( new PdlEntry( ConnectionState.CLOSE_WAIT, CorePDUType.CORE_LAST_ACK), (t, u) -> CoreLookupTable.createDisconnectTask(t, u)); public class CoreLookupTable { assert null == evalValue; private static final Map
51 (t, u) -> CoreLookupTable.createResendLastackTask(t, u)); ConnectionState.CLOSING_2, assert null == evalValue; CorePDUType.CORE_CONNECTION_RESPONSE), evalValue = lookupTable.put( new PdlEntry( new PdlEntry( ConnectionState.CLOSING_2, CorePDUType.CORE_HEAD), ConnectionState.TIME_WAIT, CorePDUType.CORE_FINACK), new PdlEntry( (t, u) -> CoreLookupTable.createResendLastackTask(t, u)); ConnectionState.CLOSING_2, CorePDUType.CORE_FINACK), assert null == evalValue; }; for(PdlEntry e : errorEntries) { //// cases for retransmission evalValue = lookupTable.put(e, ConnectionState[] retransStates = { ConnectionState.ESTABLISHED, (t, u) -> CoreLookupTable.createPDUMismatchTask(t, u)); ConnectionState.CLOSE_WAIT, assert null == evalValue; ConnectionState.FIN_WAIT, } ConnectionState.CLOSING_1, } ConnectionState.CLOSING_2, ConnectionState.TIME_WAIT }; for(ConnectionState cs : retransStates) { evalValue = lookupTable.put(new PdlEntry(cs, //// "action" functions CorePDUType.CORE_RETRANSMISSION), public static Runnable createDoNothingTask( (t, u) -> CoreLookupTable.createRetransmissionTask(t, u)); CoreProtocolFacade tObject, PDU uObject) { assert null == evalValue; return () -> {}; } }
//// error cases for lookupTable public static Runnable createHumanRejectTask( PdlEntry[] errorEntries = { CoreProtocolFacade tObject, PDU uObject) { new PdlEntry( return () -> { ConnectionState.LISTENING, // simply "close" the connection CorePDUType.CORE_CONNECTION_RESPONSE), CoreProtocolFacade.setStateAndNotify(tObject, new PdlEntry( ConnectionState.CLOSED); ConnectionState.LISTENING, CorePDUType.CORE_HEAD), }; new PdlEntry( } ConnectionState.LISTENING, CorePDUType.CORE_FIN), new PdlEntry( public static Runnable createPeerTimeoutTask( ConnectionState.LISTENING, CorePDUType.CORE_FINACK), CoreProtocolFacade tObject, PDU uObject) { new PdlEntry( return () -> { ConnectionState.LISTENING, CorePDUType.CORE_LAST_ACK), // pause all operations (sms consumption only) new PdlEntry( tObject.waitingForPrompt.set(true); ConnectionState.REQUEST_RECEIVED, // notify observers CorePDUType.CORE_CONNECTION_RESPONSE), Object[] timeoutPrompt = {MessageType.PROMPT, PromptType.TIMEOUT}; /* CoreProtocolFacade.setChangedAndNotifyObservers(tObject, //commented out due to changes from extended Handshake FSM timeoutPrompt); new PdlEntry( }; ConnectionState.REQUEST_RECEIVED, CorePDUType.CORE_FIN), } */ new PdlEntry( // NOTE: being used by resend*Tasks ConnectionState.REQUEST_RECEIVED, CorePDUType.CORE_FINACK), public static Runnable createPDUMismatchTask( new PdlEntry( CoreProtocolFacade tObject, PDU uObject) { ConnectionState.REQUEST_RECEIVED, CorePDUType.CORE_LAST_ACK), return () -> { new PdlEntry( Connection connection = ConnectionState.REQUEST_SENT, CorePDUType.CORE_FIN), tObject.coreSession.connection; new PdlEntry( connection.incrementErrorCount(); ConnectionState.REQUEST_SENT, CorePDUType.CORE_FINACK), if(connection.getErrorCount() > 10) { new PdlEntry( CoreProtocolFacade.logAndNotify(tObject, MessageType.WARN, ConnectionState.REQUEST_SENT, CorePDUType.CORE_LAST_ACK), "Connection got too many errors"); new PdlEntry( } ConnectionState.ESTABLISHED, CorePDUType.CORE_FINACK), }; new PdlEntry( } ConnectionState.ESTABLISHED, CorePDUType.CORE_LAST_ACK), new PdlEntry( public static Runnable createRequestReceivedTask( ConnectionState.FIN_WAIT, CorePDUType.CORE_CONNECTION_REQUEST), CoreProtocolFacade tObject, PDU uObject) { /* return () -> { //commented out due to changes from extended Termination FSM CoreProtocolFacade.logAndNotify(tObject, MessageType.DEBUG, new PdlEntry( "Received CONNECTION_REQUEST"); ConnectionState.FIN_WAIT, String[] payloadArr = tObject.processReqAndGenRepPayload( CorePDUType.CORE_CONNECTION_RESPONSE), uObject.getData()); */ new PdlEntry( String repPayload; ConnectionState.FIN_WAIT, CorePDUType.CORE_HEAD), if(payloadArr.length == 1) { new PdlEntry( repPayload = generateStatusPayload(payloadArr[0]); ConnectionState.CLOSE_WAIT, } CorePDUType.CORE_CONNECTION_REQUEST), else if(payloadArr.length == 2) { new PdlEntry( repPayload = payloadArr[0] + "/" + payloadArr[1]; ConnectionState.CLOSE_WAIT, } CorePDUType.CORE_CONNECTION_RESPONSE), else { // invalid new PdlEntry( return; // do nothing ConnectionState.CLOSE_WAIT, CorePDUType.CORE_HEAD), } new PdlEntry( // use the received Session ID from sender ConnectionState.CLOSE_WAIT, CorePDUType.CORE_FINACK), if(uObject == null || uObject.getData() == null || new PdlEntry( uObject.getData().length < 2 || ConnectionState.CLOSING_1, !(uObject.getData()[1] instanceof UUID)) { // invalid CorePDUType.CORE_CONNECTION_REQUEST), return; // do nothing new PdlEntry( } ConnectionState.CLOSING_1, tObject.coreSession.sessionId = (UUID)(uObject.getData()[1]); CorePDUType.CORE_CONNECTION_RESPONSE), // respond with REP new PdlEntry( PDUEncoder encoder = new PDUEncoder(false); ConnectionState.CLOSING_1, CorePDUType.CORE_HEAD), encoder.appendVersion(CoreProtocolFacade.VERSION) new PdlEntry( .appendSessionId(tObject.coreSession ConnectionState.CLOSING_1, CorePDUType.CORE_FINACK), .sessionId).appendPayload( new PdlEntry( "rep/" + tObject.getSubprotocolName() + "/" + repPayload) ConnectionState.CLOSING_2, .appendAdditionalMessage( CorePDUType.CORE_CONNECTION_REQUEST), CoreProtocolFacade.DEFAULT_ADDITIONAL_MESSAGE) new PdlEntry( .finalizePDU();
52 tObject.sendMessageRepeatedly(encoder.getEncoded()); }; tObject.coreSession.connection.encodedRepPdu = } encoder.getEncoded(); CoreProtocolFacade.setStateAndNotify(tObject, public static Runnable createResendCoreHeadIfValidTask( ConnectionState.REQUEST_RECEIVED); CoreProtocolFacade tObject, PDU uObject) { }; return () -> { } // check if ESTABLISHED state is acquired via a // REP PDU from a REQUEST_SENT state public static Runnable createResendCoreRepTask( if(tObject.coreSession.connection.lastPduBeforeEst == CoreProtocolFacade tObject, PDU uObject) { CorePDUType.CORE_CONNECTION_RESPONSE && return () -> { tObject.coreSession.connection.encodedHeadPdu != null) { CoreProtocolFacade.logAndNotify(tObject, MessageType.DEBUG, CoreProtocolFacade.logAndNotify(tObject, MessageType.DEBUG, "Resending CONNECTION_RESPONSE"); "Resending HEAD"); // just resend stored REP tObject.smsService.sendMessage( if(tObject.coreSession.connection.encodedRepPdu != null) { tObject.coreSession.connection.encodedHeadPdu); tObject.smsService.sendMessage( } tObject.coreSession.connection.encodedRepPdu); else { // increment error count and notify listeners for } // subsequent actions, if needed // just maintain state CoreLookupTable.createPDUMismatchTask( }; tObject, uObject).run(); } } // just maintain state public static Runnable createEstConnAsInitiatorTask( }; CoreProtocolFacade tObject, PDU uObject) { } return () -> { Object[] data = uObject.getData(); public static Runnable createRetransmissionTask( // if peer accepted the connection CoreProtocolFacade tObject, PDU uObject) { if(data != null && data.length >= 4 && return () -> { data[3] instanceof String && Object[] data = uObject.getData(); CoreProtocolFacade.USER_ACCEPT_STATUS_CODE.equals( if(data == null || data.length < 2) { (String)data[3])) { return; CoreProtocolFacade.logAndNotify(tObject, MessageType.DEBUG, } "Responder has accepted the connection request"); // loop through the PDUType, Integer pairs } for(int i = 0; i < data.length/2; i++) { else if(data != null && data.length >= 4 && if(!(data[0] instanceof PDUType data[3] instanceof String) { // non-accept status code && data[1] instanceof Integer)) { String bufferPlus = (String)data[3] + " " + return; // exit asap on errors ((data.length >= 5 && data[4] instanceof String) ? } (String)data[4] : ""); // extract data CoreProtocolFacade.logAndNotify(tObject, MessageType.DEBUG, PDUType t = (PDUType)data[0]; "Peer has rejected the connection request with " + int n = (Integer)data[1]; "status: " + bufferPlus); } // determine if n is for a multipart else { // something else List
53 // respond with FINACK CoreProtocolFacade.logAndNotify(tObject, MessageType.DEBUG, PDUEncoder encoder = new PDUEncoder(true); "Resending FIN"); encoder.appendPrefix(CorePDUType.CORE_FINACK, tObject.smsService.sendMessage( BinaryPDUDecoder.getInstance()) tObject.coreSession.connection.encodedFinPdu); .appendSessionId(tObject.coreSession.sessionId) } .finalizePDU(); // just maintain state tObject.sendMessageRepeatedly(encoder.getEncoded()); }; // store the message somewhere } tObject.coreSession.connection.encodedFinackPdu = encoder.getEncoded(); public static Runnable createResendLastackTask( // notify observers of the received statusCode and message CoreProtocolFacade tObject, PDU uObject) { CoreProtocolFacade.setChangedAndNotifyObservers(tObject, return () -> { MessageType.STATUS, StatusType.NOTICE, CoreProtocolFacade.logAndNotify(tObject, MessageType.DEBUG, ConnectionState.CLOSE_WAIT, statusCode, statusMessage); "Resending LAST_ACK"); // then set to CLOSE_WAIT state afterwards // respond with LAST_ACK CoreProtocolFacade.setStateAndNotify(tObject, String lastackStr = generateEncodedLastack(tObject); ConnectionState.CLOSE_WAIT); tObject.smsService.sendMessage(lastackStr); }; // just maintain state } }; } public static Runnable createSimulFinReceivedTask( CoreProtocolFacade tObject, PDU uObject) { public static Runnable createClosing1DisconnectTask( return () -> { CoreProtocolFacade tObject, PDU uObject) { // respond with LAST_ACK return () -> { String lastackStr = generateEncodedLastack(tObject); // set to TIME_WAIT afterwards tObject.smsService.sendMessage(lastackStr); CoreProtocolFacade.setStateAndNotify(tObject, // set to CLOSING_1 afterwards ConnectionState.TIME_WAIT); CoreProtocolFacade.setStateAndNotify(tObject, }; ConnectionState.CLOSING_1); } }; } public static Runnable createClosing2DisconnectTask( CoreProtocolFacade tObject, PDU uObject) { public static Runnable createFinwaitDisconnectTask( return () -> { CoreProtocolFacade tObject, PDU uObject) { // respond with LAST_ACK return () -> { String lastackStr = generateEncodedLastack(tObject); CoreProtocolFacade.logAndNotify(tObject, MessageType.DEBUG, tObject.smsService.sendMessage(lastackStr); "Peer acknowledged disconnection request"); // set to TIME_WAIT afterwards // respond with LAST_ACK CoreProtocolFacade.setStateAndNotify(tObject, String lastackStr = generateEncodedLastack(tObject); ConnectionState.TIME_WAIT); tObject.smsService.sendMessage(lastackStr); }; // set to TIME_WAIT afterwards } CoreProtocolFacade.setStateAndNotify(tObject, ConnectionState.TIME_WAIT); public static Runnable createDisconnectTask( CoreProtocolFacade tObject, PDU uObject) { // transition from a proper TIME_WAIT to CLOSED transition on a return () -> { // background thread // just disconnect (set state to CLOSED) Future delayedFuture = tObject.pool.submit(() -> { CoreProtocolFacade.setStateAndNotify(tObject, try { // wait until the maxPduLifetime*3/2 ConnectionState.CLOSED); // trip timeoutLoopFuture CoreProtocolFacade.logAndNotify(tObject, MessageType.INFO, tObject.timeoutLoopFuture.cancel(true); "Disconnected"); // then sleep for a while before triggering disconnection // kill all unneeded threads Thread.sleep( tObject.shutdownNow(); tObject.coreSession.connection.maxPduLifetime); }; } } catch(InterruptedException e) { // do nothing; just proceed with shutdown procedure public static Runnable createTimeoutDisconnectTask( } CoreProtocolFacade tObject, PDU uObject) { CoreLookupTable.createDisconnectTask( return () -> { tObject, uObject).run(); // send a LAST_ACK message with timeout as reason }); String statusCombo = }; CoreProtocolFacade.REQUEST_TIMEOUT_STATUS_CODE + "/" + } CoreProtocolFacade.getStatusMessage( CoreProtocolFacade.REQUEST_TIMEOUT_STATUS_CODE); public static Runnable createSimulLastackReceivedTask( tObject.smsService.sendMessage(generateEncodedLastack(tObject, CoreProtocolFacade tObject, PDU uObject) { statusCombo)); return () -> { // finaly, disconnect (set state to CLOSED) // set to CLOSING_2 afterwards CoreProtocolFacade.setStateAndNotify(tObject, CoreProtocolFacade.setStateAndNotify(tObject, ConnectionState.CLOSED); ConnectionState.CLOSING_2); CoreProtocolFacade.logAndNotify(tObject, MessageType.INFO, }; "Disconnected"); } // and kill all unneeded threads tObject.shutdownNow(); public static Runnable createResendFinackTask( }; CoreProtocolFacade tObject, PDU uObject) { } return () -> { // just resend stored FINACK if(tObject.coreSession.connection.encodedFinackPdu != null) { CoreProtocolFacade.logAndNotify(tObject, MessageType.DEBUG, //// utility functions "Resending FINACK"); private static String generateEncodedLastack(CoreProtocolFacade tObject) { tObject.smsService.sendMessage( return generateEncodedLastack(tObject, tObject.coreSession.connection.encodedFinackPdu); tObject.coreSession.connection.finStatusComboReceived); } } // just maintain state }; private static String generateEncodedLastack(CoreProtocolFacade tObject, } String statusCombo) { PDUEncoder encoder = new PDUEncoder(false); public static Runnable createResendFinTask( encoder.appendVersion(CoreProtocolFacade.VERSION) CoreProtocolFacade tObject, PDU uObject) { .appendSessionId(tObject.coreSession.sessionId) return () -> { .appendPayload("last/" + tObject.getSubprotocolName() + "/" + // just resend stored FIN statusCombo) if(tObject.coreSession.connection.encodedFinPdu != null) { .appendAdditionalMessage(
54 CoreProtocolFacade.DEFAULT_ADDITIONAL_MESSAGE) ------.finalizePDU(); return encoder.getEncoded(); package com.transmisms.core.protocol; } import com.transmisms.core.protocol.Connection.ConnectionState; private static String generateStatusPayload(String statusCode) { import com.transmisms.core.protocol.Presenter.MessageType; return statusCode + "/" + import com.transmisms.core.protocol.Presenter.PromptType; CoreProtocolFacade.getStatusMessage(statusCode); import com.transmisms.core.util.commons.FIFOEntry; } import java.util.Arrays; import java.util.Hashtable; // convenience get() function import java.util.List; public static BiFunction
import java.util.concurrent.ExecutionException; public enum CorePDUType implements PDUType
@Override private DataStrSizes(int size) { public int getSessionIdPosition() { this.size = size; return this.sessionIdPosition; } } } public int getSize() { return this.size; } }
protected static final Map
55 statusCodes.put("0001", "OK; Completed without error"); ExecutorService pool) { statusCodes.put("0002", "Accepted"); this(coreSession, binaryDecoder, textBasedDecoder, smsService, pool, -1); statusCodes.put("1001", "User timeout"); } statusCodes.put("1002", "Cancelled by user"); */ statusCodes.put("1003", "Rejected by user"); // NOTE: only subclasses should be able to use the base class's statusCodes.put("1101", "Blacklisted"); // constructor(s) statusCodes.put("1102", "Peer unknown"); /** * statusCodes.put("2001", "PDU not acceptable"); * @param maxPduLifetime time in seconds to indicate the Maximum PDU statusCodes.put("2002", "Request timeout"); * Lifetime */ statusCodes.put("3001", "Protocol not supported"); protected CoreProtocolFacade(Session coreSession, statusCodes.put("3002", "Version not supported"); BinaryPDUDecoder binaryDecoder, statusCodes.put("3003", "Filesystem error"); TextBasedPDUDecoder textBasedDecoder, statusCodes.put("3004", "Device error"); SmsService smsService, ExecutorService pool, int maxPduLifetime) { statusCodes.put("4001", "Remote host identification mismatch"); this.coreSession = coreSession; statusCodes.put("4101", "Key malformed"); this.binaryDecoder = binaryDecoder; statusCodes.put("4201", "File decryption failed"); this.textBasedDecoder = textBasedDecoder; statusCodes.put("4202", "File authentication failed"); this.smsService = smsService; statusCodes.put("4203", "File signature mismatch"); this.pool = pool; statusCodes.put("4901", "Too many PDU errors"); if(maxPduLifetime >= 0) { } this.coreSession.connection.maxPduLifetime = maxPduLifetime * 1000; } public static final String COMPLETED_STATUS_CODE = "0001"; } public static final String USER_ACCEPT_STATUS_CODE = "0002"; public static final String REQUEST_TIMEOUT_STATUS_CODE = "2002"; public static final String DEFAULT_ADDITIONAL_MESSAGE = "To stop receiving messages, reply with NO"; //// member methods public abstract String getSubprotocolName(); public static final Version VERSION = new Version(0, 7, 0); public static final Version MIN_VERSION = new Version(0, 7, 0); protected abstract String generateHeadPayload(); public static final Version MAX_VERSION = new Version(0, 7, 0); /** * * @return the default status message String for the statusCode // non-constant members start here */ public static String getStatusMessage(String statusCode) { public final Session coreSession; return CoreProtocolFacade.statusCodes.get(statusCode); private final BinaryPDUDecoder binaryDecoder; } private final TextBasedPDUDecoder textBasedDecoder; protected abstract void processHeadPayload(Object[] headData); public final SmsService smsService; protected abstract String[] processReqAndGenRepPayload( protected final Map
56 default: { "PduConsumer interrupted while waiting"); BiFunction
57 default: // should never happen // schedule timeout procedure encoderC[0].appendHexSegmentId(0); this.pduThreadPool.submit( } CoreLookupTable.createPeerTimeoutTask( } this, null)); }); } // finally send the last message } this.smsService.sendMessage(encoderC[0].finalizePDU()); catch(InterruptedException e) { } // Thread.interrupt(); // preserve interrupt status } /** * // check if we need to exit the loop */ cs = this.coreSession.connection.getConnectionState(); protected void sendMessageRepeatedly(String msg) { if(cs.equals(ConnectionState.CLOSED) || // set new value first cs.equals(ConnectionState.TIME_WAIT) || this.dumbRetransMsg.set(msg); cs.equals(ConnectionState.CLOSE_WAIT)) { this.dumbRetransPhaser.arrive(); logAndNotify(this, MessageType.TRACE, "Exiting TimeoutLoop..."); Future msgRepeatFuture = this.pool.submit(() -> { break; // then break the loop while(msg != null && this.dumbRetransMsg.get() != null && } msg.equals(this.dumbRetransMsg.get()) && else { !this.coreSession.connection.getConnectionState().equals( continue; // just loop and reset again ConnectionState.TIME_WAIT)) { } try { } // send message before "sleeping" }); this.smsService.sendMessage(msg); } // wait for maxPduLifetime*2 seconds or until interrupt int prevPhase = this.dumbRetransPhaser.getPhase(); /** this.dumbRetransPhaser.awaitAdvanceInterruptibly(prevPhase, * this.coreSession.connection.maxPduLifetime*2, */ TimeUnit.MILLISECONDS); protected void startRetransLoop() { } if(this.retransLoopFuture == null) { catch(TimeoutException e) { this.retransLoopFuture = this.pool.submit(() -> { // do nothing // loop until interrupt or SmsService error // just loop and retry on timeout while(!Thread.currentThread().isInterrupted() && } !this.coreSession.connection.getConnectionState() catch(InterruptedException e) { .equals(ConnectionState.TIME_WAIT)) { // just break after getting the signal try { break; // sleep before sending any retransmission PDU(s) } Thread.sleep( } this.coreSession.connection.maxPduLifetime); }); // check if we need to exit ASAP } if(this.coreSession.connection.getConnectionState() .equals(ConnectionState.TIME_WAIT)) { /** break; * } * @throws NullPointerException if there are no observers for this object this.sendRetransPdu(); */ } protected void listen() throws NullPointerException { catch(InterruptedException e) { // set state to listening // preserve interrupt status CoreProtocolFacade.setStateAndNotify(this, ConnectionState.LISTENING); Thread.currentThread().interrupt(); this.initializeConnection(); break; // then break the loop logAndNotify(this, MessageType.INFO, } "Listening for connection requests"); } // wait for request (via the smsConsumer and pduConsumer threads }); // and do nothing on the "main thread" } } // finally, just wait for other threads this.joinAll(); /** } * */ /** public void sendRetransPdu() { * final PDUEncoder[] encoderC = { new PDUEncoder(true) * @throws NullPointerException if there are no observers for this object .appendPrefix( */ CorePDUType.CORE_RETRANSMISSION, protected void connect(String additonalPayload) BinaryPDUDecoder.getInstance()) }; throws NullPointerException { // get each missed pdu type and segment number this.initializeConnection(); this.segmanMap.forEach((k, v) -> { for(Integer n : v.getSkippedSegNums()) { // class "main thread" // cut into parts with 152-character limit this.classMainFuture = this.pool.submit(() -> { if(encoderC[0].getEncoded().length() >= 152) { logAndNotify(this, MessageType.INFO, // send the message "Connecting to peer"); this.smsService.sendMessage( if(this.coreSession.connection.role == encoderC[0].finalizePDU()); Connection.Role.INITIATOR) { // then set up another instance of encoder // construct string to be appended later encoderC[0] = new PDUEncoder(true) String addStr = ""; .appendPrefix( if(additonalPayload != null) { CorePDUType.CORE_RETRANSMISSION, addStr = "/" + additonalPayload; BinaryPDUDecoder.getInstance()); } } // construct and try to send REQ PDUEncoder encoder = new PDUEncoder(false); encoderC[0].appendPrefix(k, encoder.appendVersion(CoreProtocolFacade.VERSION) this.binaryDecoder); .appendSessionId(this.coreSession switch(k.getPduSegmentIdFormat()) { .sessionId).appendPayload( case BINARY: "req/" + this.getSubprotocolName() + addStr) encoderC[0].appendBinarySegmentId(n+1); .appendAdditionalMessage( break; CoreProtocolFacade.DEFAULT_ADDITIONAL_MESSAGE) case HEX: .finalizePDU(); encoderC[0].appendHexSegmentId(n+1); this.sendMessageRepeatedly(encoder.getEncoded()); break; CoreProtocolFacade.setStateAndNotify(this, case NONE: ConnectionState.REQUEST_SENT);
58 } } else { // if receiver } protected SegmentManager registerSegMan(PDUType k, SegmentManager v) { }); return this.segmanMap.put(k, v); // finally, just wait for other threads } this.joinAll(); } protected
59 public class PDU implements Comparable
public PDUType.PDUSubType getPduSubType() { public PdlEntry(ConnectionState s, PDUType t) { return this.pduType.getPduSubType(); this.s = s; } this.t = t; } public Object[] getData() { return this.data; }
@Override // for priority comparisons only public boolean equals(Object o) { public int compareTo(PDU o) { if(o instanceof PdlEntry) { PDUType ownPDUType = this.getPduType(); PdlEntry po = (PdlEntry)o; PDUType otherPDUType = o.getPduType(); // check for nulls before proceeding with equals() Integer ownPriority = pduTypePriorities.get(ownPDUType); if(po.s == null || po.t == null || Integer otherPriority = pduTypePriorities.get(otherPDUType); this.s == null || this.t == null) { if(ownPriority == null) { return false; ownPriority = 0; } } return this.s.equals(po.s) && this.t.equals(po.t); if(otherPriority == null) { } otherPriority = 0; else { } return false; } return otherPriority-ownPriority; } } } @Override public int hashCode() { return this.s.hashCode() + this.t.hashCode(); } // CAUTION: this has no protection against nulls and problematic types------} Filename: src/main/java/com/transmisms/core/protocol/PDUEncoder.java ------
package com.transmisms.core.protocol;
------import com.transmisms.core.protocol.CoreProtocolFacade.DataSizes; Filename: src/main/java/com/transmisms/core/protocol/PDU.java import com.transmisms.core.util.codec.Base85; ------import net.sourceforge.javaflacencoder.CRC8; package com.transmisms.core.protocol; import java.nio.ByteBuffer; import com.transmisms.core.protocol.PDUType; import java.util.Arrays; import com.transmisms.smsftp.protocol.SmsftpPDUType; import java.util.UUID; import java.util.Hashtable; import java.io.UnsupportedEncodingException; import java.util.Map;
60 raw.length-DataSizes.SEGMENTID.getSize(), raw.length); // convenience class to construct PDUs this.appendString(Base85.encode(truncated)); public class PDUEncoder { return this; } private boolean isBinary; private boolean isFinalized; public PDUEncoder appendStatusCode(String statusCode) { private String encoded; this.appendString(statusCode); return this; public PDUEncoder(boolean isBinary) { } this.isBinary = isBinary; // the rest can just use the appendString() method this.isFinalized = false; this.encoded = ""; /* UTILITY FUNCTIONS */
if(!isBinary) { public static String UUIDToBase85(UUID uuidObject) { this.encoded+="?transmismsv"; Long msb = uuidObject.getMostSignificantBits(); } Long lsb = uuidObject.getLeastSignificantBits(); } byte[] bytes = ByteBuffer.allocate(2*Long.BYTES).putLong(msb) .putLong(lsb).array(); public String getEncoded() { return Base85.encode(bytes); return this.encoded; } } }
public String finalizePDU() { if(!this.isFinalized) { if(this.isBinary) { // append the CRC for this PDU ------CRC8 crc = new CRC8(); Filename: src/main/java/com/transmisms/core/protocol/PDUMalformedException.java try { ------crc.updateCRC8(this.getEncoded().getBytes("UTF-8"), 0, this.getEncoded().getBytes("UTF-8").length); package com.transmisms.core.protocol; this.appendString(String.format("%02x", crc.checksum())); } catch(UnsupportedEncodingException e) { public class PDUMalformedException extends Exception { // this should never happen; crash early throw new IllegalStateException(e.getMessage(), e); private String malformedPDU = null; } } public PDUMalformedException(String message, String malformedPDU) { this.isFinalized = true; super(message); } this.malformedPDU = malformedPDU; return this.getEncoded(); } } public String getMalformedPDU() { public PDUEncoder appendString(String s) { return this.malformedPDU; if(!this.isFinalized) { } this.encoded+=s; } } return this; }
/* TEXT-BASED PDUS */ ------Filename: src/main/java/com/transmisms/core/protocol/PDUType.java public PDUEncoder appendVersion(Version v) { ------this.appendString(v + "\n"); return this; package com.transmisms.core.protocol; }
public PDUEncoder appendSessionId(UUID sessionId) { this.appendString(PDUEncoder.UUIDToBase85(sessionId)); public interface PDUType
public PDUEncoder appendPrefix(PDUType x, BinaryPDUDecoder b) { this.appendString(b.pduTypeToPrefix(x) + ""); return this; ------} Filename: src/main/java/com/transmisms/core/protocol/Presenter.java ------public PDUEncoder appendHexSegmentId(int segmentId) { this.appendString(String.format("%02x", segmentId)); package com.transmisms.core.protocol; return this; } import java.util.Observable; import java.util.Observer; public PDUEncoder appendBinarySegmentId(int segmentId) { ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); buffer.putInt(segmentId); byte[] raw = buffer.array(); public abstract class Presenter implements Observer { byte[] truncated = Arrays.copyOfRange(raw, public enum MessageType {
61 // UI-related throw new IllegalArgumentException( PROMPT, "Segment number should not be negative"); STATUS, } // logging if(segNum >= this.segments.length) { FATAL, throw new IllegalArgumentException( ERROR, "Segment number should not be greater than or equal " + WARN, "to the number of segments"); INFO, } DEBUG, TRACE // check if can be updated } if(this.segments[segNum] != null) { return true; public enum PromptType { } CONNECTION_REQUEST, // add if null/update if existing TIMEOUT, this.segments[segNum] = segment; SMS_SERVICE_ERROR, PEER_ERROR_EXCEEDED_LIMIT // update missingnos } this.missingnos.remove(segNum); if(this.lastSegmentReceived < segNum) { public enum StatusType { // add skipped segments to missingnos, if any STATE_CHANGED, for(int i = lastSegmentReceived+1; i < segNum; i++) { NOTICE if(this.segments[i] == null) { } this.missingnos.add(i); } @Override } public abstract void update(Observable o, Object arg); // do nothing // add the next segNum to missingnos } if(segNum+1 < this.segCount) { this.missingnos.add(segNum+1); } this.lastSegmentReceived = segNum; // update last segment number } ------Filename: src/main/java/com/transmisms/core/protocol/SegmentManager.java // preemptive consolidation ------this.getCompletedString(); package com.transmisms.core.protocol; return true; } import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicReference; public Integer[] getSkippedSegNums() { return this.missingnos.toArray(new Integer[0]); } public class SegmentManager { public int getLastSegmentReceived() { private final String[] segments; return this.lastSegmentReceived; public final int segCount; }
private final AtomicReference
private synchronized String getSegment(int segNum) { if(segNum < 0) { throw new IllegalArgumentException( "Segment number should not be negative"); ------} Filename: src/main/java/com/transmisms/core/protocol/Session.java if(segNum >= this.segments.length) { ------throw new IllegalArgumentException( "Segment number should not be greater than or equal " package+ com.transmisms.core.protocol; "to the number of segments"); } import java.util.UUID; return this.segments[segNum]; import java.nio.ByteBuffer; }
/** * public abstract class Session { * @return true if segment is updated; false if segment already exists */ public final Connection connection; public synchronized boolean updateSegment(String segment, int segNum) { if(segment == null) { public UUID sessionId; throw new IllegalArgumentException( "Segment cannot be null"); } if(segNum < 0) { /**
62 * Creates a new Session with the specified Session Id */ public Session(Connection connection, UUID sessionId) { this.sessionId = sessionId; public class SmsServiceException extends Exception { this.connection = connection; public interface Reason
public final Reason reason;
public SmsServiceException(Reason reason) { ------super(reason.toString()); Filename: src/main/java/com/transmisms/core/protocol/SmsEntry.java this.reason = reason; ------} package com.transmisms.core.protocol; public Reason getReason() { return this.reason; import java.util.Date; } } public class SmsEntry { private String sender; private String body; ------private Date timestamp; Filename: src/main/java/com/transmisms/core/protocol/TextBasedPDUDecoder.java ------public SmsEntry(String sender, String body) { this(sender, body, new Date()); package com.transmisms.core.protocol; } import com.transmisms.core.protocol.PDU; public SmsEntry(String sender, String body, Date timestamp) { import com.transmisms.core.protocol.PDUType; this.sender = sender; import com.transmisms.core.protocol.PDUMalformedException; this.body = body; this.timestamp = timestamp; import java.util.Arrays; } import java.util.ArrayList; import java.util.List; public String getSender() { import java.util.regex.Pattern; return this.sender; } import java.util.UUID; import java.nio.ByteBuffer; public String getBody() { import java.io.IOException; return this.body; import com.transmisms.core.util.codec.Base85; } import com.transmisms.core.protocol.CoreProtocolFacade.DataSizes;
public Date getTimestamp() { return this.timestamp; } public class TextBasedPDUDecoder { } private static final Pattern LF_PATTERN = Pattern.compile("\n"); private static final Pattern LFLF_PATTERN = Pattern.compile("\n\n"); private static final Pattern DOT_PATTERN = Pattern.compile("\\."); private static final Pattern SLASH_PATTERN = Pattern.compile("/");
------private String[] knownProtocols; Filename: src/main/java/com/transmisms/core/protocol/SmsService.java ------private static TextBasedPDUDecoder thisInstance; static { package com.transmisms.core.protocol; String[] emptyArray = {}; TextBasedPDUDecoder.thisInstance = new TextBasedPDUDecoder(emptyArray); import java.util.concurrent.TimeUnit; }
protected TextBasedPDUDecoder(String[] knownProtocols) { this.knownProtocols = knownProtocols; // NOTE: handling queuing, setting destination number, etc. should be done } // by the implementer public interface SmsService { public static TextBasedPDUDecoder getInstance() { return TextBasedPDUDecoder.thisInstance; public void sendMessage(String message); }
public SmsEntry pollMessage(long timeout, TimeUnit unit) throws SmsServiceException, InterruptedException; private static UUID base85ToUUID(String b85String, String encodedStr, // for running methods that the service might require (to be run from String pduTypeName) // background threads) throws PDUMalformedException { public boolean start() throws SmsServiceException; // extract byte[] format from base85 public boolean stop(); byte[] uuidBytes = null; try { public boolean isRunning(); uuidBytes = Base85.decode(b85String); } // NOTE: we don't need to check for a ready state here as the service might catch(IOException e) { // become unavailable immediately after we have verified that the throw new PDUMalformedException( // service is available pduTypeName + "'s Session ID is corrupt", encodedStr); } } // verify if sessionId's length is correct if(uuidBytes.length != DataSizes.SESSIONID.getSize()) { throw new PDUMalformedException( pduTypeName + "'s Session ID's byte length is incorrect", ------null); Filename: src/main/java/com/transmisms/core/protocol/SmsServiceException.java } ------// split the byte[] into msb and lsb package com.transmisms.core.protocol; byte[] msb = Arrays.copyOfRange(uuidBytes, 0, 8);
63 byte[] lsb = Arrays.copyOfRange(uuidBytes, 8, 16); } // convert the msb and lsb byte[] into long and return afterwards else { ByteBuffer msbBuffer = ByteBuffer.allocate(Long.BYTES).put(msb); throw new PDUMalformedException("Invalid Payload Type: " + ByteBuffer lsbBuffer = ByteBuffer.allocate(Long.BYTES).put(lsb); payloadParts[0], encoded); msbBuffer.flip(); // required for reading } lsbBuffer.flip(); // required for reading return new UUID(msbBuffer.getLong(), lsbBuffer.getLong()); { // check if protocol name is valid } String protocolName = payloadParts[1]; boolean validProtocolName = false; protected Object[] decodePayloadSubparts(PDUType pduType, for(int i = 0; i < this.knownProtocols.length; i++) { String[] payloadSubparts, String encoded) if(this.knownProtocols[i].equals(protocolName)) { throws PDUMalformedException { validProtocolName = true; // does nothing alone; just return back the payloadSubparts } return payloadSubparts; } } if(validProtocolName) { decodedData.add(protocolName); public final PDU decodeTextBased(String encoded) } throws PDUMalformedException { else { CorePDUType pduType = null; throw new PDUMalformedException("Invalid Protocol name: " + List
64 else if(this.data[start] > o.data[start]) { return 1; /** } * Decodes ASCII85 encoded string else { * if(start == 2) { * @param data String to be decoded from ASCII85 return 0; * @return decoded data } */ else { public static byte[] decode(String data) throws IOException { return this.compare(o, start+1); // remove all unused whitespaces } String encoded = data.trim() } // and replace back all replaced characters on encoding to ASCII } .replace("v", "[") .replace("w", "\\") @Override .replace("x", "]") public String toString() { .replace("y", "^") return this.data[0] + "." + this.data[1] + "." + this.data[2]; .replace("£", "`"); } } // re-add the ~ suffix if missing if(!encoded.endsWith("~") || !encoded.endsWith("~>")) { encoded = encoded + "~"; }
------ByteArrayInputStream bais = new Filename: src/main/java/com/transmisms/core/util/codec/Base85.java ByteArrayInputStream(encoded.getBytes("UTF-8")); ------ASCII85InputStream a85is = new ASCII85InputStream(bais); package com.transmisms.core.util.codec; ArrayList
/** a85is.close(); * */ return decoded; public class Base85 { }
/** } * Encodes data to ASCII85 * * @param data data to be encoded into ASCII85 * @return ASCII85 encoded string */ ------public static String encode(byte[] data) { Filename: src/main/java/com/transmisms/core/util/commons/FIFOEntry.java ByteArrayOutputStream baos = new ByteArrayOutputStream(); ------ASCII85OutputStream a85os = new ASCII85OutputStream(baos); package com.transmisms.core.util.commons; String encoded = null; try { a85os.write(data); import java.util.concurrent.atomic.AtomicLong; a85os.flush();
encoded = baos.toString("UTF-8").trim(); // because pdfbox's ascii85 appendspublic class FIFOEntry
65 import org.apache.commons.io.IOUtils; * Encrypts InputStream is with the object's key and outputs it to import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; * OutputStream os. import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream; * * @param is data to be encrypted will be read from import java.io.InputStream; * @param os encrypted data will be written to import java.io.OutputStream; */ public static void encrypt(byte[] key, byte[] iv, InputStream is, import java.io.IOException; OutputStream os) throws IOException { // read key from input stream /** SecretKeySpec sks = byteArrayToKeySpec(key); * Cipher c = AES.getCipher(); */ // encrypt the stream public class XZ { try { c.init(Cipher.ENCRYPT_MODE, sks, new IvParameterSpec(iv)); /** } * catch(InvalidKeyException e) { */ System.err.println("InvalidKeyException at AES.encrypt() !!!"); public static void compress(InputStream is, OutputStream os) } throws IOException { catch(InvalidAlgorithmParameterException e) { // we're going to use default compression level: 6 System.err.println( XZCompressorOutputStream xzos = new XZCompressorOutputStream(os); "InvalidAlgorithmParameterException at AES.encrypt() !!!"); } xzos.write(IOUtils.toByteArray(is)); CipherOutputStream cos = new CipherOutputStream(os, c); xzos.flush(); IOUtils.copy(is, cos); cos.close(); xzos.close(); } } /** /** * Decrypts InputStream is with the object's key and outputs it to * * OutputStream os. */ * public static void decompress(InputStream is, OutputStream os) * @param is data to be decrypted will be read from throws IOException { * @param os decrypted data will be written to XZCompressorInputStream xzis = new XZCompressorInputStream(is); */ public static void decrypt(byte[] key, byte[] iv, InputStream is, os.write(IOUtils.toByteArray(xzis)); OutputStream os) throws IOException { // read key from input stream xzis.close(); SecretKeySpec sks = byteArrayToKeySpec(key); } Cipher c = AES.getCipher(); } // decrypt the stream try { c.init(Cipher.DECRYPT_MODE, sks, new IvParameterSpec(iv)); } ------catch(InvalidKeyException e) { Filename: src/main/java/com/transmisms/core/util/crypto/AES.java System.err.println("InvalidKeyException at AES.decrypt() !!!"); ------} catch(InvalidAlgorithmParameterException e) { package com.transmisms.core.util.crypto; System.err.println( "InvalidAlgorithmParameterException at AES.decrypt() !!!"); import java.security.SecureRandom; } import javax.crypto.Cipher; CipherInputStream cis = new CipherInputStream(is, c); import javax.crypto.CipherInputStream; IOUtils.copy(cis, os); import javax.crypto.CipherOutputStream; cis.close(); import javax.crypto.KeyGenerator; } import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; /** import javax.crypto.spec.SecretKeySpec; * Generates a 128-bit AES key * import org.apache.commons.io.IOUtils; * @return a 128-bit AES key */ import java.io.InputStream; public static byte[] generateKey() { import java.io.OutputStream; SecretKey key = null; try { import java.security.GeneralSecurityException; KeyGenerator kg = KeyGenerator.getInstance(_AES_KEYGEN_ALGO); import java.security.InvalidAlgorithmParameterException; kg.init(_AES_DEFAULT_KEY_SIZE, new SecureRandom()); import java.security.InvalidKeyException; key = kg.generateKey(); import java.io.IOException; }
// this arises from KeyGenerator.getInstance() // this should be impossible to reach! /** catch(NullPointerException e) { * System.err.println( */ "NullPointerException at AES.generateKey() !!!"); public final class AES { } // see AES() cipher.getInstance() /* private final static String _AES_KEYGEN_ALGO = "AES"; * Covers the following: private final static String _AES_CIPHER_ALGO = "AES"; * - NoSuchAlgorithmException from KeyGenerator.getInstance() private final static String _AES_CIPHER_ALGO_MODE_CBC = "CBC"; * - InvalidAlgorithmParameterException from kg.init() // NOTE: this is actually a PKCS7Padding internally, see Sun's padding */ // security documentation (more of a historical artifact to prevent // this should be impossible to reach! // breaking things; > 8 bytes would yield as PKCS7 instead of catch(GeneralSecurityException e) { // PKCS5) System.err.println( private final static String _AES_CIPHER_ALGO_PADDING_PKCS7 = "GeneralSecurityException at AES.generateKey() !!!"); "PKCS5Padding"; } private final static int _AES_DEFAULT_KEY_SIZE = 128; return key.getEncoded(); }
/** /** * Generates a SecretKeySpec using this class's parameters from the
66 * provided key Filename: src/main/java/com/transmisms/core/util/crypto/RSA.java * ------* @return a SecretKeySpec using this class's parameters from the * provided key package com.transmisms.core.util.crypto; */ public static SecretKeySpec byteArrayToKeySpec(byte[] key) { import org.bouncycastle.util.io.pem.PemReader; return new SecretKeySpec(key, _AES_KEYGEN_ALGO); import org.bouncycastle.util.io.pem.PemWriter; } import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.encoders.DecoderException;
import java.security.KeyFactory; private static Cipher getCipher() { import java.security.KeyPair; try { import java.security.KeyPairGenerator; Cipher c = Cipher.getInstance(_AES_CIPHER_ALGO + "/" + import java.security.PrivateKey; _AES_CIPHER_ALGO_MODE_CBC + "/" import+ java.security.PublicKey; _AES_CIPHER_ALGO_PADDING_PKCS7);import java.security.Signature; return c; import java.security.spec.PKCS8EncodedKeySpec; } import java.security.spec.X509EncodedKeySpec; /* import javax.crypto.Cipher; * Covers the following: * - NoSuchAlgorithmException from Cipher.getInstance() import org.apache.commons.io.IOUtils; * - NoSuchPaddingException from Cipher.getInstance() */ import java.io.InputStream; // this should be impossible to reach! import java.io.OutputStream; catch(GeneralSecurityException e) { import java.io.InputStreamReader; assert false; // this should never happen import java.io.OutputStreamWriter; return null; } import java.security.GeneralSecurityException; } import java.security.NoSuchAlgorithmException; } import java.security.spec.InvalidKeySpecException; import java.io.IOException;
/** ------* Filename: */ src/main/java/com/transmisms/core/util/crypto/BiometricFingerprint.java public final class RSA { ------private final static int _RSA_DEFAULT_KEY_SIZE = 2048; private final static String _RSA_ALGO = "RSA"; package com.transmisms.core.util.crypto; private final static String _RSA_CIPHER_ALGO = "RSA"; // NOTE: ECB on RSA is interpreted by java security as a null cipher algo import technology.zeroalpha.security.pgpwordlist.PGPWordListConverter; private final static String _RSA_CIPHER_ALGO_MODE_ECB = "ECB"; import technology.zeroalpha.security.pgpwordlist.InvalidHexValueException; private final static String _RSA_ALGO_PADDING_OAEP = import org.apache.commons.codec.digest.DigestUtils; "OAEPWithSHA-256AndMGF1Padding"; private final static String _SHA512_WITH_RSA = "SHA512withRSA";
/** private static KeyFactory keyFactory; * */ static { public final class BiometricFingerprint { try { RSA.keyFactory = KeyFactory.getInstance(_RSA_ALGO); private static final PGPWordListConverter converter = } new PGPWordListConverter(); catch(NoSuchAlgorithmException e) { // this should never happen assert false; /** RSA.keyFactory = null; * } */ } public static String getBiometricFingerprint(byte[] data) throws NullPointerException { private static Cipher generateCipher() { // get SHA1 checksum of data try { String sha1Hex = DigestUtils.sha1Hex(data); return Cipher.getInstance(_RSA_CIPHER_ALGO + "/" + _RSA_CIPHER_ALGO_MODE_ECB + "/" + _RSA_ALGO_PADDING_OAEP); // translate hex string fingerprint to PGP words } boolean oddWord = true; /* String bioFingerprint = ""; * Covers the following: try { * - NoSuchAlgorithmException for(int i = 0; i < sha1Hex.length()/2 ; i++) { * - NoSuchPaddingException String currentWord = sha1Hex.substring(i*2,(i*2)+2); */ bioFingerprint += i==0 ? "" : " "; catch(GeneralSecurityException e) { // this should never happen bioFingerprint += oddWord ? assert false; BiometricFingerprint.converter.getOddWordForHexValue( return null; currentWord) : } BiometricFingerprint.converter.getEvenWordForHexValue( } currentWord); oddWord = !oddWord; // next odd/even word private static Signature generateSignature() { } try { } return Signature.getInstance(_SHA512_WITH_RSA); // this should never happen as the hex returned by DigestUtils should } // always be a valid hex catch(NoSuchAlgorithmException e) { // this should never happen catch(InvalidHexValueException e) { assert false; throw new NullPointerException("Got InvalidHexValueException!"); return null; } } } return bioFingerprint; } public static PrivateKey generatePrivate(byte[] key) } throws InvalidKeySpecException { PKCS8EncodedKeySpec pkcs8Spec = new PKCS8EncodedKeySpec(key); return RSA.keyFactory.generatePrivate(pkcs8Spec); }
------public static PublicKey generatePublic(byte[] key)
67 throws InvalidKeySpecException { } X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(key); /* return RSA.keyFactory.generatePublic(x509Spec); * Covers the following: } * - InvalidKeySpecException from (RSA.)KeyFactory.generatePrivate() * - InvalidKeyException from Cipher.init() /** * - IllegalBlockSizeException from Cipher.doFinal() * */ */ catch(GeneralSecurityException e) { public static byte[] sign(byte[] key, InputStream is) throws IOException { System.err.println("GeneralSecurityException at decrypt() !!!"); Signature sig = RSA.generateSignature(); } try { } PrivateKey privKey = RSA.generatePrivate(key); sig.initSign(privKey); /** sig.update(IOUtils.toByteArray(is)); * return sig.sign(); */ } // this does not guarantee however that the read data will be correct /* public static byte[] readPublicKeyFromPEM(InputStream source) * Covers the following: throws RSADecoderException { * - InvalidKeySpecException from (RSA.)KeyFactory.generatePrivate() PemReader pemReader = new PemReader(new InputStreamReader(source)); * - InvalidKeyException from Signature.initSign() PemObject obj = null; * - SignatureException from Signature.update() and Signature.sign() try { */ obj = pemReader.readPemObject(); catch(GeneralSecurityException e) { } System.err.println("GeneralSecurityException at sign() !!!"); catch(DecoderException e) { } throw new RSADecoderException(e.getMessage(), e.getCause()); return null; // catch all scenario if something has gone wrong } } catch(IOException e) { // NOTE: this method of "filtering" exceptions is only for(StackTraceElement el : e.getStackTrace()) { /** if(el.getClassName() == * "org.bouncycastle.util.io.pem.PemReader" */ && el.getMethodName() == "loadObject") { public static boolean verify(byte[] key, InputStream is, byte[] sigToVer) throw new RSADecoderException(e.getMessage(), throws IOException { e.getCause()); Signature sig = RSA.generateSignature(); } try { } PublicKey pubKey = RSA.generatePublic(key); } sig.initVerify(pubKey); if(obj == null) { sig.update(IOUtils.toByteArray(is)); throw new RSADecoderException("Empty key read from PEM file"); return sig.verify(sigToVer); } } return obj.getContent(); /* } * Covers the following: * - InvalidKeySpecException from (RSA.)KeyFactory.generatePublic() /** * - InvalidKeyException from Signature.initVerify() * * - SignatureException from Signature.update() and Signature.verify() */ */ // this does not guarantee that the bytes written are valid public keys catch(GeneralSecurityException e) { public static void writePublicKeyToPEM(byte[] pubKey, System.err.println("GeneralSecurityException at verify() !!!"); OutputStream destination) throws IOException { } PemWriter pemWriter = new PemWriter( return false; // catch all scenario if something has gone wrong new OutputStreamWriter(destination)); } pemWriter.writeObject(new PemObject("PUBLIC KEY", pubKey)); pemWriter.close(); /** } * */ /** public static void encrypt(byte[] key, InputStream is, OutputStream os) * throws IOException { */ byte[] src = IOUtils.toByteArray(is); public static void generateKeys(OutputStream privKeyOs, OutputStream pubKeyOs) throws IOException { Cipher cipher = RSA.generateCipher(); KeyPairGenerator kpg = null; try { try { PublicKey pubKey = RSA.generatePublic(key); kpg = KeyPairGenerator.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, pubKey); } os.write(cipher.doFinal(src)); catch(NoSuchAlgorithmException e) { os.flush(); System.err.println( } "NoSuchAlgorithmException at RSA.generateKeys() !!!"); /* } * Covers the following: * - InvalidKeySpecException from (RSA.)KeyFactory.generatePublic() kpg.initialize(_RSA_DEFAULT_KEY_SIZE); * - InvalidKeyException from Cipher.init() KeyPair kp = kpg.genKeyPair(); * - IllegalBlockSizeException from Cipher.doFinal() */ byte[] pubKey = kp.getPublic().getEncoded(); catch(GeneralSecurityException e) { byte[] privKey = kp.getPrivate().getEncoded(); System.err.println("GeneralSecurityException at encrypt() !!!"); } pubKeyOs.write(pubKey); } pubKeyOs.flush(); privKeyOs.write(privKey); /** privKeyOs.flush(); * } */ } public static void decrypt(byte[] key, InputStream is, OutputStream os) throws IOException { byte[] src = IOUtils.toByteArray(is);
Cipher cipher = RSA.generateCipher(); ------try { Filename: src/main/java/com/transmisms/core/util/crypto/RSADecoderException.java PrivateKey privKey = RSA.generatePrivate(key); ------cipher.init(Cipher.DECRYPT_MODE, privKey); os.write(cipher.doFinal(src)); package com.transmisms.core.util.crypto; os.flush();
68 public final class RSADecoderException extends Exception { import java.io.IOException;
/** * @see Exception#Exception(String) Exception */ public class SmsftpBinaryPduDecoder extends BinaryPDUDecoder { public RSADecoderException(String message) { private final static List
// populate custom lookup maps SmsftpBinaryPduDecoder.biDiAddToLookup('a', // access is between public and protected, see Java documentation SmsftpPDUType.SMSFTP_INIT); final class PdlEntry { SmsftpBinaryPduDecoder.biDiAddToLookup('b', public final SessionState s; SmsftpPDUType.SMSFTP_META); public final PDUType t; SmsftpBinaryPduDecoder.biDiAddToLookup('c', SmsftpPDUType.SMSFTP_READY); SmsftpBinaryPduDecoder.biDiAddToLookup('d', SmsftpPDUType.SMSFTP_DATA); public PdlEntry(SessionState s, PDUType t) { SmsftpBinaryPduDecoder.biDiAddToLookup('e', this.s = s; SmsftpPDUType.SMSFTP_END); this.t = t; SmsftpBinaryPduDecoder.biDiAddToLookup('E', } SmsftpPDUType.SMSFTP_END_WAIT); SmsftpBinaryPduDecoder.biDiAddToLookup('i', SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY); SmsftpBinaryPduDecoder.biDiAddToLookup('j', @Override SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY); public boolean equals(Object o) { if(o instanceof PdlEntry) { // populate valid CEC values return this.s == ((PdlEntry)o).s && this.t == ((PdlEntry)o).t; validNoencCec.add("XXC"); } validNoencCec.add("XXX"); else { validEncCec.add("XEX"); return false; validEncCec.add("CEX"); } validEncCec.add("XEC"); } validEncCec.add("CEC"); } @Override public int hashCode() { protected SmsftpBinaryPduDecoder( return this.s.hashCode() + this.t.hashCode(); Function
public static SmsftpBinaryPduDecoder getInstance() { return SmsftpBinaryPduDecoder.thisInstance; } ------Filename: private static String getSubstrAndIncrement(String body, src/main/java/com/transmisms/smsftp/protocol/SmsftpBinaryPduDecoder.java AtomicInteger pointer, int substrSize) { ------return body.substring(pointer.get(), pointer.addAndGet(substrSize)); } package com.transmisms.smsftp.protocol; @Override import com.transmisms.core.protocol.BinaryPDUDecoder; protected List
69 int segmentId = getHexSegmentId(body, 3, encoded, "SMSFTP_INIT", true); decodedData.add(segmentId); case SMSFTP_END: case SMSFTP_END_WAIT: // get Session Id case SMSFTP_READY: { String sessionIdStr = getSubstrAndIncrement(body, if(body.length() < DataStrSizes.SESSIONID.getSize()) { pointer, DataStrSizes.SESSIONID.getSize()); throw new PDUMalformedException( UUID sessionId = base85ToUUID(sessionIdStr, encoded, pduType.toString() + "SMSFTP_INIT"); " too short to fit Session ID", encoded); // then add the extracted Session Id } decodedData.add(sessionId); String sessionIdStr = body.substring(0, DataStrSizes.SESSIONID.getSize()); if(segmentId != 0) { // encrypted connections // decode sessionId from here String excessData = body.substring(pointer.get()); UUID sessionId = base85ToUUID(sessionIdStr, encoded, decodedData.add(excessData); pduType.toString()); } // do nothing for unencrypted connections decodedData.add(sessionId); // and add to decoded data break; break; } }
case SMSFTP_META: { case SMSFTP_DATA: { AtomicInteger pointer = new AtomicInteger( // check if size is less than SEGMENTID DataStrSizes.HEXSEGMENTID.getSize()); if(body.length() < DataStrSizes.SEGMENTID.getSize()) { throw new PDUMalformedException( if(body.length() < 1) { "SMSFTP_DATA too short to fit Segment ID", throw new PDUMalformedException( encoded); "SMSFTP_META too short", encoded); } } String segmentIdStr = body.substring(0, DataStrSizes.SEGMENTID.getSize()); // get Segment Id // decode segmentId from here int segmentId = getHexSegmentId(body, 3, encoded, Integer segmentId = null; "SMSFTP_META", true); try { decodedData.add(segmentId); byte[] segmentIdBytes = Base85.decode(segmentIdStr); ByteBuffer bb = ByteBuffer.allocate(Integer.BYTES); // get Session Id for(int i = 0; String sessionIdStr = getSubstrAndIncrement(body, i < Integer.BYTES - segmentIdBytes.length; pointer, DataStrSizes.SESSIONID.getSize()); i++) { UUID sessionId = base85ToUUID(sessionIdStr, encoded, bb.put((byte)0); "SMSFTP_META"); } decodedData.add(sessionId); // flip() is required for reading if(segmentId == 1 || segmentId == 0) { bb.put(segmentIdBytes).flip(); // get CEC String cecStr = getSubstrAndIncrement(body, pointer, segmentId = bb.getInt(); DataStrSizes.CEC.getSize()); } // verify CEC catch(IOException e) { if(!(segmentId == 1 ? validEncCec : throw new PDUMalformedException( validNoencCec).contains(cecStr)) { "SMSFTP_DATA's Segment ID is corrupt", throw new PDUMalformedException( encoded); pduType.toString() + } " invalid CEC flag: " + cecStr, encoded); decodedData.add(segmentId); // and add to decoded data } // get data body from here // get Segment Count String dataBody = body.substring( String segmentCountStr = getSubstrAndIncrement(body, DataStrSizes.SEGMENTID.getSize(), body.length()); pointer, DataStrSizes.SEGMENTCOUNT.getSize()); decodedData.add(dataBody); Integer segmentCount = base85ToInteger(segmentCountStr, break; encoded, "SMSFTP_META"); }
// add values afterwards decodedData.add(cecStr); case SMSFTP_INITIATOR_IDENTITY: decodedData.add(segmentCount); case SMSFTP_RESPONDER_IDENTITY: { } AtomicInteger pointer = new AtomicInteger( DataStrSizes.HEXSEGMENTID.getSize()); // extract checksum/signature String excessDataStr = body.substring(pointer.get()); int segmentId = getHexSegmentId(body, 5, encoded, Object excessData = null; pduType.toString(), false); if(segmentId == 0) { // unencrypted connection decodedData.add(segmentId); try { byte[] excessDataBytes String excessData = body.substring(pointer.get()); = Base85.decode(excessDataStr); decodedData.add(excessData); if(excessDataBytes.length != break; DataSizes.SHA1_SUM.getSize()) { } throw new PDUMalformedException( default: { "SMSFTP_META's SHA1 checksum's size // code should NEVER reach here invalid", throw new AssertionError("Uncaught PDU Type: " + pduType); encoded); } } } excessData = excessDataBytes; } } else { catch(IOException e) { return null; throw new PDUMalformedException( } "SMSFTP_META's SHA1 checksum is corrupt", return decodedData; encoded); } } } } else { // encrypted connection excessData = excessDataStr; } // finally, add excess data ------decodedData.add(excessData); Filename: src/main/java/com/transmisms/smsftp/protocol/SmsftpFacade.java break; ------}
70 package com.transmisms.smsftp.protocol; protected void processHeadPayload(Object[] headData) { if(this.getSmsftpSession().getSessionIntent() == import com.transmisms.core.protocol.CoreProtocolFacade; SessionIntent.DATA_TRANSFER) { import com.transmisms.core.protocol.PDU; if(headData != null && headData.length >= 5 && import com.transmisms.core.protocol.PDUEncoder; headData[3] instanceof String) { import com.transmisms.core.protocol.PDUType; String encryptionMode = (String)headData[3]; import com.transmisms.core.protocol.Presenter.MessageType; if(encryptionMode.equals("enc")) { import com.transmisms.core.protocol.Presenter.PromptType; this.getSmsftpSession().setCecEncryption(true); import com.transmisms.core.protocol.Presenter.StatusType; } import com.transmisms.core.protocol.SegmentManager; else if(encryptionMode.equals("noenc")) { import com.transmisms.core.protocol.SmsService; this.getSmsftpSession().setCecEncryption(false); import com.transmisms.core.util.codec.Base85; } import com.transmisms.core.util.crypto.AES; } import com.transmisms.core.util.crypto.RSA; } import com.transmisms.smsftp.protocol.SmsftpSession.Role; else if(this.getSmsftpSession().getSessionIntent() import com.transmisms.smsftp.protocol.SmsftpSession.SessionIntent; == SessionIntent.PAIR) { import com.transmisms.smsftp.protocol.SmsftpSession.SessionState; // do nothing } import java.io.ByteArrayInputStream; else { // should never happen import java.io.ByteArrayOutputStream; } import java.nio.ByteBuffer; } import java.security.KeyPair; import java.security.PrivateKey; @Override import java.security.PublicKey; protected String[] processReqAndGenRepPayload(Object[] requestData) { import java.util.List; if(requestData != null && requestData.length >= 6) { import java.util.Map; if(requestData.length >= 6 && requestData[4] instanceof String && import java.util.concurrent.Callable; requestData[4].equals("send") && import java.util.concurrent.CountDownLatch; requestData[5] instanceof String) { // send intent import java.util.concurrent.Future; this.getSmsftpSession().setSessionIntent( import java.util.function.BiFunction; SessionIntent.DATA_TRANSFER); // store filename in the SmsftpSession object import java.io.IOException; this.getSmsftpSession().setFilename((String)requestData[5]); import java.security.spec.InvalidKeySpecException; } else if(requestData.length >= 5 && requestData[4] instanceof String && requestData[4].equals("pair")) { // pair intent public class SmsftpFacade extends CoreProtocolFacade { this.getSmsftpSession().setSessionIntent(SessionIntent.PAIR); private static final String USER_ACCEPT_STATUS_CODE = "0002"; } public final String SUBPROTOCOL_NAME = "smsftp"; else { // error } public final SmsftpSession smsftpSession; // just accept all valid connections protected final CountDownLatch processingLatch = new CountDownLatch(1); String[] retStr = { USER_ACCEPT_STATUS_CODE }; private final CountDownLatch acceptPromptLatch = new CountDownLatch(1); return retStr; } return null; } public SmsftpFacade(SmsftpSession smsftpSession, SmsService smsService, int maxPduLifetime) { @Override super(smsftpSession, protected final Runnable processPdu(PDU pduObject) { SmsftpBinaryPduDecoder.getInstance(), switch(this.smsftpSession.getSessionState()) { SmsftpTextBasedPduDecoder.getInstance(), case TERMINATING: smsService, return null; maxPduLifetime); default: { this.smsftpSession = smsftpSession; BiFunction
71 } else { // INITIATOR this.smsftpSession.encodedInitPdu = new String[3]; } // construct the INIT PDUs to be sent } this.smsftpSession.encodedInitPdu[0] = (new PDUEncoder(true)) .appendPrefix(SmsftpPDUType.SMSFTP_INIT, SmsftpBinaryPduDecoder.getInstance()) .appendHexSegmentId(1) // multipart ids start at 1 public void receive() { .appendSessionId(this.smsftpSession.sessionId) this.listen(); .appendString(encEncAesKeyParts[0]) } .finalizePDU(); this.smsftpSession.encodedInitPdu[1] = (new PDUEncoder(true)) public void insecureSendFile() { .appendPrefix(SmsftpPDUType.SMSFTP_INIT, this.connect(this.getSmsftpSession().connection.peerMin + SmsftpBinaryPduDecoder.getInstance()) "/send/" + this.getSmsftpSession().getFilename()); .appendHexSegmentId(2) // multipart ids start at 1 } .appendSessionId(this.smsftpSession.sessionId) .appendString(encEncAesKeyParts[1]) public void secureSendFile() { .finalizePDU(); this.connect(this.getSmsftpSession().connection.peerMin + this.smsftpSession.encodedInitPdu[2] = (new PDUEncoder(true)) "/send/" + this.getSmsftpSession().getFilename()); .appendPrefix(SmsftpPDUType.SMSFTP_INIT, } SmsftpBinaryPduDecoder.getInstance()) .appendHexSegmentId(3) // multipart ids start at 1 public void pair() { .appendSessionId(this.smsftpSession.sessionId) // register our own segment manager .appendString(encEncAesKeyParts[2]) this.registerSegMan(SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY, .finalizePDU(); this.smsftpSession.identitySegments); // finally, send the messages // set session waiting state first (similar to CONNECTED) this.smsService.sendMessage( SmsftpFacade.setStateAndNotify(this, SessionState.INITIATOR_WAIT); this.smsftpSession.encodedInitPdu[0]); // then connect as usual this.smsService.sendMessage( this.connect(this.getSmsftpSession().connection.peerMin + this.smsftpSession.encodedInitPdu[1]); "/pair"); this.smsService.sendMessage( } this.smsftpSession.encodedInitPdu[2]);
public void acceptRequestAfterPrompt(byte[] enckey, byte[] authkey) { } SmsftpFacade.logAndNotify(this, MessageType.INFO, else { // unencrypted "User accepted the smsftp request"); this.smsftpSession.encodedInitPdu = new String[1]; // construct and send the INIT PDU // continue all operations if needed this.smsftpSession.encodedInitPdu[0] = (new PDUEncoder(true)) // wake the awaiting thread .appendPrefix(SmsftpPDUType.SMSFTP_INIT, this.acceptPromptLatch.countDown(); SmsftpBinaryPduDecoder.getInstance()) .appendHexSegmentId(0) // non-multipart ids start at 0 if(this.getSmsftpSession().getSessionIntent() == .appendSessionId(this.smsftpSession.sessionId) SessionIntent.DATA_TRANSFER) { .finalizePDU(); // send INIT PDU(s) depending on encryption this.smsService.sendMessage( if(this.smsftpSession.getEncryptionFlag()) { // encrypted this.smsftpSession.encodedInitPdu[0]); // check first if the keys given are legitimate } if(enckey == null || authkey == null) { // register encodedInitPdu Array for retransmission assert false; this.registerRetrans(SmsftpPDUType.SMSFTP_INIT, } this.smsftpSession.encodedInitPdu); try { // finally, set state RSA.generatePublic(enckey); SmsftpFacade.setStateAndNotify(this, SessionState.INIT_SENT); RSA.generatePublic(authkey); } } else if(this.getSmsftpSession().getSessionIntent() catch(InvalidKeySpecException e) { == SessionIntent.PAIR) { assert false; // generate keys and encode them properly } this.smsftpSession.generatePairingIdentity(); byte[] pubEncKey = // set session's authkey this.smsftpSession.localEncKeypair.getPublic() this.getSmsftpSession().authkey = authkey; .getEncoded(); // construct AES key to be used byte[] pubAuthKey = this.getSmsftpSession().fileEncKey = AES.generateKey(); this.smsftpSession.localAuthKeypair.getPublic() // encrypt aesKey .getEncoded(); ByteArrayInputStream is = new ByteArrayInputStream( String concatenatedKeys = this.getSmsftpSession().fileEncKey); Base85.encode(pubEncKey) + "¥" + Base85.encode(pubAuthKey); ByteArrayOutputStream os = new ByteArrayOutputStream(); try { // construct and send the SMSFTP_RESPONDER_IDENTITY PDUs RSA.encrypt(enckey, is, os); int p = 0; os.flush(); for(int i = 0; i < 5; i++) { } // construct each PDU catch(IOException e) { String payload = ""; assert false; // NOTE: should never happen since we are if(p < concatenatedKeys.length()) { // just using ByteArray streams payload = concatenatedKeys.substring(p, } (p+155 < concatenatedKeys.length()) ? p+155 : String encEncAesKey = Base85.encode(os.toByteArray()); concatenatedKeys.length()); String[] encEncAesKeyParts = new String[3]; } encEncAesKeyParts[1] = ""; this.smsftpSession.encodedKeyPdu[i] = (new PDUEncoder(true)) encEncAesKeyParts[2] = ""; .appendPrefix(SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY, if(encEncAesKey.length() < 135) { SmsftpBinaryPduDecoder.getInstance()) encEncAesKeyParts[0] = encEncAesKey; .appendHexSegmentId(i+1) // multipart ids start at 1 } .appendString(payload) else if(encEncAesKey.length() >= 135 && .finalizePDU(); encEncAesKey.length() < 270) { // then send each messasge encEncAesKeyParts[0] = encEncAesKey.substring(0, 135); this.smsService.sendMessage( encEncAesKeyParts[1] = encEncAesKey.substring(135); this.smsftpSession.encodedKeyPdu[i]); } p=p+155; else { } encEncAesKeyParts[0] = encEncAesKey.substring(0, 135); SmsftpFacade.logAndNotify(this, MessageType.INFO, encEncAesKeyParts[1] = encEncAesKey.substring(135, 270); // "Sent last responder identity PDU to receiver"); 135 // register encodedKeyPdu Array for retransmission encEncAesKeyParts[2] = encEncAesKey.substring(270, this.registerRetrans(SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY, encEncAesKey.length()); this.smsftpSession.encodedKeyPdu); } // register our own segment manager
72 this.registerSegMan(SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY, @Override this.smsftpSession.identitySegments); protected Future submitToInternalPool(Runnable task) { // start a new thread for repeated sending of END PDUs return super.submitToInternalPool(task); this.sendEndPduRepeatedly(); } // finally, set state SmsftpFacade.setStateAndNotify(this, SessionState.RESPONDER_KEY_EXCHANGE); } //// utility functions else { // should never happen protected static void logAndNotify(SmsftpFacade self, MessageType t, } String message) { } Object[] logArr = {t, message}; SmsftpFacade.setChangedAndNotifyObservers(self, logArr); public void rejectRequestAfterPrompt(String reason) { } } protected static void setStateAndNotify(SmsftpFacade self, public void rejectRequestAfterPrompt() { SessionState newState) { SmsftpFacade.logAndNotify(this, MessageType.INFO, SmsftpFacade.getAndSetStateAndNotify(self, newState); "User rejected the smsftp request"); } this.rejectRequestAfterPrompt(null); } protected static SessionState getAndSetStateAndNotify(SmsftpFacade self, SessionState newState) { /** SessionState oldState = * self.smsftpSession.getAndSetSessionState(newState); * @return a reference to the internal byte[] object used for storing SmsftpFacade.setChangedAndNotifyObservers(self, MessageType.STATUS, * data to be sent/received StatusType.STATE_CHANGED, oldState, newState); */ return oldState; public byte[] getDataBytes() { } return this.smsftpSession.dataBytes; } protected static void setChangedAndNotifyObservers(CoreProtocolFacade self, Object ... o) { protected void sendEndPduRepeatedly() { CoreProtocolFacade.setChangedAndNotifyObservers(self, o); this.submitToInternalPool(() -> { } SmsftpFacade.logAndNotify(this, MessageType.INFO, } "Waiting until peer has completed"); // loop until interrupt or SmsService error while(!Thread.currentThread().isInterrupted() && !this.smsftpSession.getSessionState().equals( SessionState.TERMINATING)) { ------try { Filename: src/main/java/com/transmisms/smsftp/protocol/SmsftpLookupTable.java this.smsService.sendMessage(new PDUEncoder(true) ------.appendPrefix(SmsftpPDUType.SMSFTP_END, SmsftpBinaryPduDecoder.getInstance()) package com.transmisms.smsftp.protocol; .appendSessionId(this.smsftpSession.sessionId) .finalizePDU()); import com.transmisms.core.protocol.CoreProtocolFacade; // try again after maxPduLifetime import com.transmisms.core.protocol.PDU; Thread.sleep( import com.transmisms.core.protocol.PDUEncoder; this.coreSession.connection.maxPduLifetime); import com.transmisms.core.protocol.Presenter.MessageType; } import com.transmisms.core.protocol.Presenter.StatusType; catch(InterruptedException e) { import com.transmisms.core.protocol.SegmentManager; // preserve interrupt status import com.transmisms.smsftp.protocol.SmsftpSession.SessionState; Thread.currentThread().interrupt(); break; // then break the loop import com.transmisms.core.util.codec.Base85; } import com.transmisms.core.util.compression.XZ; } import com.transmisms.core.util.crypto.AES; }); import com.transmisms.core.util.crypto.RSA; } import com.transmisms.core.util.crypto.RSA;
/* import java.io.ByteArrayInputStream; // operations for prompt feedbacks (for UI, CLI, tester, etc.) import java.io.ByteArrayOutputStream; public void continueOperation(); import java.io.InputStream; public void cancelOperation(); // import java.io.PipedInputStream; // errcode depends on the situation import java.io.PipedOutputStream; // - no prompt: user cancel import java.security.MessageDigest; // - prompt: the real cause import java.util.Arrays; // i.e. timeout import java.util.Hashtable; // import java.util.Map; */ import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import org.apache.commons.io.output.CountingOutputStream; //// exposed functions for use by package and subclasses @Override import java.io.IOException; protected List registerRetrans(PDUType k, List
@Override protected List registerRetrans(PDUType k, String[] a) { public class SmsftpLookupTable { return super.registerRetrans(k, a); /** } * Container class for use by encryptAndCompact() */ @Override private static class CompEncData { protected SegmentManager registerSegMan(PDUType k, SegmentManager v) { public boolean compressAfter = false; return super.registerSegMan(k, v); public byte[] data = null; } }
@Override protected
73 SmsftpLookupTable.createCompletedEndReceivedTask(t, u)); assert null == evalValue; static { // initialize lookupTable PdlEntry[] doNothingEntries = { BiFunction
74 SessionState.INIT_SENT, SmsftpPDUType.SMSFTP_DATA), return () -> {}; new PdlEntry( } SessionState.INIT_SENT, SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY), new PdlEntry( public static Runnable createPDUMismatchTask( SessionState.INIT_SENT, SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY), SmsftpFacade tObject, PDU uObject) { new PdlEntry( return () -> {}; SessionState.INIT_SENT, SmsftpPDUType.SMSFTP_END), } new PdlEntry( SessionState.INIT_SENT, SmsftpPDUType.SMSFTP_END_WAIT), public static Runnable createInitReceivedTask( new PdlEntry( SmsftpFacade tObject, PDU uObject) { SessionState.SENDING_DATA, SmsftpPDUType.SMSFTP_META), return () -> { tObject.submitToInternalPool(() -> { new PdlEntry( // initialize segment manager if not yet initialized SessionState.SENDING_DATA, SmsftpPDUType.SMSFTP_DATA), SmsftpSession sObject = tObject.smsftpSession; new PdlEntry( SessionState.SENDING_DATA, boolean isChanged = false; SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY), if(sObject.getEncryptionFlag()) { new PdlEntry( isChanged = sObject.initSegments.compareAndSet( SessionState.SENDING_DATA, null, new SegmentManager(3)); SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY), } new PdlEntry( else { SessionState.SENDING_DATA, SmsftpPDUType.SMSFTP_END), isChanged = sObject.initSegments.compareAndSet( new PdlEntry( null, new SegmentManager(1)); SessionState.SENDING_DATA, SmsftpPDUType.SMSFTP_END_WAIT), } new PdlEntry( if(isChanged) { // register new segment manager SessionState.RECEIVING_DATA, SmsftpPDUType.SMSFTP_INIT), tObject.registerSegMan( new PdlEntry( SmsftpPDUType.SMSFTP_INIT, sObject.initSegments.get()); SessionState.RECEIVING_DATA, SmsftpPDUType.SMSFTP_READY), } new PdlEntry( SessionState.RECEIVING_DATA, synchronized(sObject.initSegments) { SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY), // validate uObject and extract available data new PdlEntry( Object[] uData = uObject.getData(); SessionState.RECEIVING_DATA, if(uData == null || SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY), uData.length < 2 || !(uData[0] instanceof Integer)) { new PdlEntry( SmsftpFacade.logAndNotify(tObject, SessionState.RECEIVING_DATA, SmsftpPDUType.SMSFTP_END_WAIT), MessageType.TRACE, new PdlEntry( "Got problematic INIT PDU data"); SessionState.INITIATOR_WAIT, SmsftpPDUType.SMSFTP_INIT), return; // just return on invalid cases new PdlEntry( } SessionState.INITIATOR_WAIT, SmsftpPDUType.SMSFTP_META), int segnum = (Integer)(uData[0]); new PdlEntry( SmsftpFacade.logAndNotify(tObject, SessionState.INITIATOR_WAIT, SmsftpPDUType.SMSFTP_READY), MessageType.TRACE, "Received INIT part " + segnum); new PdlEntry( SessionState.INITIATOR_WAIT, SmsftpPDUType.SMSFTP_DATA), // update segment and determine if all PDUs are completed new PdlEntry( final String[][] veriArrC = { null }; SessionState.INITIATOR_WAIT, if(sObject.getEncryptionFlag()) { // if encrypted/multipart SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY), // get encrypted AES segment new PdlEntry( String encAesSeg = null; SessionState.INITIATOR_WAIT, SmsftpPDUType.SMSFTP_END_WAIT), if(uData.length == 3 && uData[2] instanceof String) { new PdlEntry( encAesSeg = (String)(uData[2]); SessionState.INITIATOR_KEY_EXCHANGE, SmsftpPDUType.SMSFTP_INIT), } new PdlEntry( else { // invalid case, just return asap SessionState.INITIATOR_KEY_EXCHANGE, SmsftpPDUType.SMSFTP_META), SmsftpFacade.logAndNotify(tObject, new PdlEntry( MessageType.TRACE, "Erratic INIT part " + SessionState.INITIATOR_KEY_EXCHANGE, SmsftpPDUType.SMSFTP_READY), segnum); new PdlEntry( return; SessionState.INITIATOR_KEY_EXCHANGE, SmsftpPDUType.SMSFTP_DATA), } new PdlEntry( SessionState.INITIATOR_KEY_EXCHANGE, // update segment and check if completed SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY), if(sObject.initSegments.get().updateSegment( new PdlEntry( encAesSeg, segnum-1) && SessionState.INITIATOR_KEY_EXCHANGE, sObject.initSegments.get().getCompletedString() SmsftpPDUType.SMSFTP_END_WAIT), != null) { new PdlEntry( // set lock and exit if already locked SessionState.RESPONDER_KEY_EXCHANGE, SmsftpPDUType.SMSFTP_INIT), if(sObject.processingStarted.getAndSet(true)) { new PdlEntry( SmsftpFacade.logAndNotify(tObject, SessionState.RESPONDER_KEY_EXCHANGE, SmsftpPDUType.SMSFTP_META), MessageType.TRACE, new PdlEntry( "Got another INIT while processing"); SessionState.RESPONDER_KEY_EXCHANGE, SmsftpPDUType.SMSFTP_READY), return; new PdlEntry( } SessionState.RESPONDER_KEY_EXCHANGE, SmsftpPDUType.SMSFTP_DATA), new PdlEntry( SmsftpFacade.logAndNotify(tObject, MessageType.DEBUG, SessionState.RESPONDER_KEY_EXCHANGE, "INIT PDUs complete; reassembling segments"); SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY), new PdlEntry( SessionState.RESPONDER_KEY_EXCHANGE, // process INIT PDU(s) if already completed SmsftpPDUType.SMSFTP_END_WAIT), final CompEncData[] xeRetValC = { null }; new PdlEntry( final CompEncData[] ceRetValC = { null }; SessionState.COMPLETED, SmsftpPDUType.SMSFTP_END_WAIT), final byte[][] aesKeyC = { null }; }; for(PdlEntry e : errorEntries) { //// #p1 evalValue = lookupTable.put(e, Future cecFuture = tObject.submitToInternalPool( (t, u) -> SmsftpLookupTable.createPDUMismatchTask(t, u)); () -> { assert null == evalValue; // extract AES key #p1 } try { } ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] encAesKey = Base85.decode( sObject.initSegments.get() //// "action" functions .getCompletedString()); public static Runnable createDoNothingTask( // decrypt and validate consolidated key SmsftpFacade tObject, PDU uObject) { RSA.decrypt(sObject.enckey,
75 new ByteArrayInputStream(encAesKey), }); baos); aesKeyC[0] = baos.toByteArray(); // join here and compare sizes #p1 } try { catch(IOException e) { xeFuture.get(); assert false; // should never happen ceFuture.get(); } } catch(InterruptedException e) { // log event // check cases for CEC, CEX, and XEC #p1x3 SmsftpFacade.logAndNotify(tObject, Future xeFuture = tObject.submitToInternalPool( MessageType.TRACE, () -> { "CEC processing interrupted"); SmsftpFacade.logAndNotify(tObject, // preserve interrupt status MessageType.INFO, Thread.currentThread().interrupt(); "Encrypting file..."); return; // return ASAP // encrypt file #p1.1 XEX + XEC } xeRetValC[0] = encryptAndCompact( catch(ExecutionException e) { // should never happen new ByteArrayInputStream( // log event sObject.dataBytes), aesKeyC[0], SmsftpFacade.logAndNotify(tObject, sObject.iv); MessageType.TRACE, SmsftpFacade.logAndNotify(tObject, "CEC processing exception: " + e); MessageType.INFO, "Encryption done"); return; // return ASAP }); } { // compare sizes and select the smallest one Future ceFuture = tObject.submitToInternalPool( boolean compressPrior = () -> { ceRetValC[0].data.length > SmsftpFacade.logAndNotify(tObject, xeRetValC[0].data.length; MessageType.INFO, "Compressing file (first pass)..."); sObject.cecPreCompression = compressPrior; // compress file #p1.2 CE? sObject.cecPostCompression = compressPrior ? // prepare Piped streams for use later ceRetValC[0].compressAfter : final PipedInputStream pis = xeRetValC[0].compressAfter; new PipedInputStream(); PipedOutputStream tpos = null; SmsftpFacade.logAndNotify(tObject, try { MessageType.DEBUG, tpos = new PipedOutputStream(pis); "Converted data length: " + } (compressPrior ? catch(IOException e) { ceRetValC[0].data.length : assert false; // should never happen xeRetValC[0].data.length)); } final PipedOutputStream pos = tpos; // convert to Base85 #p1 // spawn to a new thread to avoid SmsftpFacade.logAndNotify(tObject, // Piped* deadlocks MessageType.INFO, Future pipedFuture = "Encoding data..."); tObject.submitToInternalPool(() -> { String encoded = Base85.encode(compressPrior ? try { ceRetValC[0].data : xeRetValC[0].data); XZ.compress(new ByteArrayInputStream( SmsftpFacade.logAndNotify(tObject, sObject.dataBytes), pos); MessageType.INFO, "Encoding done"); } SmsftpFacade.logAndNotify(tObject, catch(IOException e) { MessageType.DEBUG, assert false; // should never happen "Encoded length: " + encoded.length()); // since we're just using // byte arrays or similar // split encoded data } int index = 0; SmsftpFacade.logAndNotify(tObject, final AtomicInteger i = new AtomicInteger(1); MessageType.INFO, while(index < encoded.length()) { "Compression (first pass) done"); String str = encoded.substring(index, SmsftpFacade.logAndNotify(tObject, Math.min(index+153, MessageType.INFO, encoded.length())); "Compressing file (second sObject.dataPdu.add(new PDUEncoder(true) pass)..."); .appendPrefix( }); SmsftpPDUType.SMSFTP_DATA, try { SmsftpBinaryPduDecoder // encrypt and try to further compress .getInstance()) // compressed file #p1.2 CEC + CEX .appendBinarySegmentId( ceRetValC[0] = encryptAndCompact(pis, i.getAndIncrement()) aesKeyC[0], sObject.iv); .appendString(str) // while processing pipedFuture .finalizePDU()); pipedFuture.get(); index+=153; } } catch(InterruptedException e) { SmsftpFacade.logAndNotify(tObject, // log event MessageType.DEBUG, SmsftpFacade.logAndNotify(tObject, "Segment count: " + MessageType.TRACE, sObject.dataPdu.size()); "CEC processing 2 interrupted"); // register dataPdu List for retransmission // preserve interrupt status tObject.registerRetrans( Thread.currentThread().interrupt(); SmsftpPDUType.SMSFTP_DATA, return; // return ASAP sObject.dataPdu); } // should never happen encoded = null; catch(ExecutionException e) { } // log event }); SmsftpFacade.logAndNotify(tObject, MessageType.TRACE, "CEC processing 2 exception: " + //// #p2 e); Future sigFuture = tObject.submitToInternalPool( return; // return ASAP () -> { // generate RSA signature #p2 } SmsftpFacade.logAndNotify(tObject, SmsftpFacade.logAndNotify(tObject, MessageType.INFO, "Signing..."); MessageType.INFO, try { "Compression (second pass) done"); byte[] signature = RSA.sign(sObject.authkey,
76 new ByteArrayInputStream( index+=153; sObject.dataBytes)); } String veriString = Base85.encode(signature); SmsftpFacade.logAndNotify(tObject, veriArrC[0] = new String[3]; MessageType.INFO, "Encoding done"); veriArrC[0][0] = veriString.substring(0, 128); SmsftpFacade.logAndNotify(tObject, veriArrC[0][1] = MessageType.DEBUG, "Segment count: " + veriString.substring(128, 263); sObject.dataPdu.size()); veriArrC[0][2] = veriString.substring(263, // register dataPdu List for retransmission veriString.length()); tObject.registerRetrans(SmsftpPDUType.SMSFTP_DATA, } sObject.dataPdu); catch(IOException e) { }); assert false; // should never happen } // compute SHA512 checksum #q2 SmsftpFacade.logAndNotify(tObject, Future hashFuture = tObject.submitToInternalPool( MessageType.INFO, "Signing done"); () -> { }); SmsftpFacade.logAndNotify(tObject, MessageType.INFO, //// join p1 and p2 "Computing checksum..."); try { veriArrC[0] = new String[1]; cecFuture.get(); MessageDigest md = null; sigFuture.get(); try { } md = MessageDigest.getInstance("SHA-1"); catch(InterruptedException e) { } // log event catch(NoSuchAlgorithmException e) { SmsftpFacade.logAndNotify(tObject, assert false; // should never happen (min JDK7) MessageType.TRACE, } "CEC processing and signing interrupted"); md.update(sObject.dataBytes); // preserve interrupt status veriArrC[0][0] = Base85.encode(md.digest()); Thread.currentThread().interrupt(); SmsftpFacade.logAndNotify(tObject, return; // return ASAP MessageType.INFO, } "Checksum computation done"); catch(ExecutionException e) { // should never happen }); // log event SmsftpFacade.logAndNotify(tObject, //// join q1 and q2 MessageType.TRACE, try { "CEC processing/signing exception: " + e); xxcFuture.get(); return; // return ASAP hashFuture.get(); } } catch(InterruptedException e) { } // log event } SmsftpFacade.logAndNotify(tObject, else { // unencrypted, single part MessageType.TRACE, // update with virtually empty contents (and can be only "XXC processing and hashing interrupted"); // executed once) // preserve interrupt status if(sObject.initSegments.get().updateSegment("", segnum)) { Thread.currentThread().interrupt(); // check cases for XXC and XXX #q1 return; // return ASAP Future xxcFuture = tObject.submitToInternalPool( } () -> { catch(ExecutionException e) { // should never happen SmsftpFacade.logAndNotify(tObject, // log event MessageType.INFO, "Compressing file..."); SmsftpFacade.logAndNotify(tObject, ByteArrayOutputStream baos = MessageType.TRACE, new ByteArrayOutputStream(); "XXC processing/hashing exception: " + e); try { // compress file return; // return ASAP XZ.compress(new ByteArrayInputStream( } sObject.dataBytes), baos); } } catch(IOException e) { } assert false; // should never happen } // if not yet complete, just do nothing and return asap // compare, set flags, and encode if(sObject.initSegments.get().getCompletedString() == null) { String encoded = null; return; if(baos.size() < sObject.dataBytes.length) { } sObject.cecPostCompression = true; encoded = Base85.encode(baos.toByteArray()); // further processing for both encrypted and unencrypted } else { // generate CEC string from session properties sObject.cecPostCompression = false; String cecStr = sObject.cecPreCompression ? "C" : "X"; encoded = Base85.encode(sObject.dataBytes); cecStr += sObject.getEncryptionFlag() ? "E" : "X"; } cecStr += sObject.cecPostCompression ? "C" : "X"; SmsftpFacade.logAndNotify(tObject, MessageType.INFO, "Compression done"); // construct and send META PDU(s) PDUEncoder encoder = new PDUEncoder(true); // split encoded data encoder.appendPrefix(SmsftpPDUType.SMSFTP_META, SmsftpFacade.logAndNotify(tObject, SmsftpBinaryPduDecoder.getInstance()); MessageType.INFO, "Encoding data..."); if(sObject.getEncryptionFlag()) { // encrypted // split encoded data encoder.appendHexSegmentId(1) int index = 0; .appendSessionId(sObject.sessionId) final AtomicInteger i = new AtomicInteger(1); .appendString(cecStr) while(index < encoded.length()) { .appendBinarySegmentId(sObject.dataPdu.size()) String str = encoded.substring(index, .appendString(veriArrC[0][0]) Math.min(index+153, .finalizePDU(); encoded.length())); sObject.encodedMetaPdu[0] = encoder.getEncoded(); sObject.dataPdu.add(new PDUEncoder(true) tObject.smsService.sendMessage(sObject.encodedMetaPdu[0]); .appendPrefix( // do the same for parts 2 and 3 SmsftpPDUType.SMSFTP_DATA, encoder = new PDUEncoder(true); // part 2 SmsftpBinaryPduDecoder encoder.appendPrefix(SmsftpPDUType.SMSFTP_META, .getInstance()) SmsftpBinaryPduDecoder.getInstance()) .appendBinarySegmentId( .appendHexSegmentId(2) i.getAndIncrement()) .appendSessionId(sObject.sessionId) .appendString(str) .appendString(veriArrC[0][1]) .finalizePDU()); .finalizePDU();
77 sObject.encodedMetaPdu[1] = encoder.getEncoded(); return () -> { tObject.smsService.sendMessage(sObject.encodedMetaPdu[1]); SmsftpSession sObject = tObject.smsftpSession; encoder = new PDUEncoder(true); // part 3 encoder.appendPrefix(SmsftpPDUType.SMSFTP_META, // initialize segment manager if not yet initialized SmsftpBinaryPduDecoder.getInstance()) boolean isChanged = false; .appendHexSegmentId(3) if(sObject.getEncryptionFlag()) { .appendSessionId(sObject.sessionId) isChanged = sObject.metaSegments.compareAndSet( .appendString(veriArrC[0][2]) null, new SegmentManager(3)); .finalizePDU(); } sObject.encodedMetaPdu[2] = encoder.getEncoded(); else { tObject.smsService.sendMessage(sObject.encodedMetaPdu[2]); isChanged = sObject.metaSegments.compareAndSet( } null, new SegmentManager(1)); else { // unencrypted } encoder.appendHexSegmentId(0) // use '0' for non-multipart if(isChanged) { // register new segment manager .appendSessionId(sObject.sessionId) tObject.registerSegMan( .appendString(cecStr) SmsftpPDUType.SMSFTP_META, sObject.metaSegments.get()); .appendBinarySegmentId(sObject.dataPdu.size()) } .appendString(veriArrC[0][0]) .finalizePDU(); synchronized(sObject.metaSegments) { // finally, send the META PDU // validate uObject and extract available data sObject.encodedMetaPdu[0] = encoder.getEncoded(); Object[] uData = uObject.getData(); tObject.smsService.sendMessage(sObject.encodedMetaPdu[0]); if(uData == null || } uData.length < 2 || !(uData[0] instanceof Integer)) { return; // just return on invalid cases // register encodedMetaPdu Array for retransmission } tObject.registerRetrans(SmsftpPDUType.SMSFTP_META, int segnum = (Integer)(uData[0]); sObject.encodedMetaPdu); SmsftpFacade.logAndNotify(tObject, MessageType.TRACE, "Received META part " + segnum); // release the processing lock here SmsftpFacade.logAndNotify(tObject, MessageType.DEBUG, // update segment and determine if all PDUs are completed "Releasing data processing latch"); if(sObject.getEncryptionFlag()) { // if encrypted/multipart tObject.processingLatch.countDown(); // get encrypted AES segment and further validation String sigSeg = null; // set state as INIT_RECEIVED afterwards if(segnum == 1 && uData.length == 5 && SmsftpFacade.setStateAndNotify( uData[4] instanceof String) { // first part tObject, SessionState.INIT_RECEIVED); if(!(uData[2] instanceof String && } ((String)uData[2]).length() == 3) || });}; !(uData[3] instanceof Integer)) { } return; // invalid, return asap } public static Runnable createResendMetaTask( sigSeg = (String)(uData[4]); SmsftpFacade tObject, PDU uObject) { } return () -> { else if((segnum == 2 || segnum == 3) && }; uData.length == 3 && } uData[2] instanceof String) { // second+ part sigSeg = (String)(uData[2]); public static Runnable createReadyReceivedTask( } SmsftpFacade tObject, PDU uObject) { else { // invalid case, just return asap return () -> { return; SmsftpSession sObject = tObject.smsftpSession; }
// set state // update segment and check if completed if(SmsftpFacade.getAndSetStateAndNotify( if(sObject.metaSegments.get().updateSegment( tObject, SessionState.SENDING_DATA) sigSeg, segnum-1) && .equals(SessionState.SENDING_DATA)) { sObject.metaSegments.get().getCompletedString() return; // and return if already set != null) { } // process META PDU(s) if already completed
// wait for the processing lock here try { // extract signature and store on session object try { sObject.signature = Base85.decode( SmsftpFacade.logAndNotify(tObject, MessageType.DEBUG, sObject.metaSegments.get() "Waiting for data to be sent to be ready..."); .getCompletedString()); tObject.processingLatch.await(); } SmsftpFacade.logAndNotify(tObject, MessageType.DEBUG, catch(IOException e) { "Data ready"); assert false; // should never happen } } catch(InterruptedException e) { } // preserve interrupt status Thread.currentThread().interrupt(); if(segnum == 1) { return; // exit ASAP // extract the session parameters } String cecStr = (String)(uData[2]); if(cecStr.charAt(0) == 'C') { SmsftpFacade.logAndNotify(tObject, MessageType.INFO, sObject.cecPreCompression = true; "Sending data to receiver..."); } sObject.dataPdu.forEach((item) -> { if(cecStr.charAt(2) == 'C') { tObject.smsService.sendMessage(item); sObject.cecPostCompression = true; }); } SmsftpFacade.logAndNotify(tObject, MessageType.INFO, // prepare the new segment manager, if not "Sent last data PDU to receiver"); // yet initialized int segcount = (Integer)(uData[3]); // set state as COMPLETE_WAIT afterwards boolean dIsChanged = SmsftpFacade.setStateAndNotify( sObject.dataSegments.compareAndSet(null, tObject, SessionState.COMPLETE_WAIT); new SegmentManager(segcount)); if(dIsChanged) { // start a new thread for repeated sending of END PDUs tObject.registerSegMan( tObject.sendEndPduRepeatedly(); SmsftpPDUType.SMSFTP_DATA, }; sObject.dataSegments.get()); } } } public static Runnable createMetaReceivedTask( SmsftpFacade tObject, PDU uObject) { }
78 else { // unencrypted, single part try { // get segment and further validation if(sObject.cecPostCompression) { // --C if(!(uData.length >= 5 && uData[4] instanceof byte[] & & SmsftpFacade.logAndNotify(tObject, MessageType.INFO, uData[2] instanceof String && "Decompressing file (second pass)..."); uData[3] instanceof Integer)) { baos = new ByteArrayOutputStream(); return; // invalid, return asap XZ.decompress(new ByteArrayInputStream(rawBytes), } baos); byte[] sigSeg = (byte[])(uData[4]); rawBytes = baos.toByteArray(); // preemptive cleanup // "update" segment and force "completion" bais = null; sObject.metaSegments.get().updateSegment("", 0); SmsftpFacade.logAndNotify(tObject, MessageType.INFO, "Decompression (second pass) done"); // extract signature and store on session object } sObject.signature = sigSeg; if(sObject.getEncryptionFlag()) { // -E- SmsftpFacade.logAndNotify(tObject, MessageType.INFO, // extract session parameters "Decrypting file..."); String cecStr = (String)(uData[2]); baos = new ByteArrayOutputStream(); if(cecStr.charAt(0) == 'C') { AES.decrypt(sObject.fileEncKey, sObject.iv, return; // invalid, return asap new ByteArrayInputStream(rawBytes), baos); } rawBytes = baos.toByteArray(); if(cecStr.charAt(1) == 'E') { // preemptive cleanup return; // invalid, return asap bais = null; } SmsftpFacade.logAndNotify(tObject, MessageType.INFO, if(cecStr.charAt(2) == 'C') { "Decryption done"); sObject.cecPostCompression = true; } } if(sObject.cecPreCompression) { // C-- SmsftpFacade.logAndNotify(tObject, MessageType.INFO, // prepare the new segment manager, if not yet initialized "Decompressing file (first pass)..."); int segCount = (Integer)(uData[3]); baos = new ByteArrayOutputStream(); boolean dIsChanged = XZ.decompress(new ByteArrayInputStream(rawBytes), sObject.dataSegments.compareAndSet(null, baos); new SegmentManager(segCount)); rawBytes = baos.toByteArray(); if(dIsChanged) { // preemptive cleanup tObject.registerSegMan( bais = null; SmsftpPDUType.SMSFTP_DATA, SmsftpFacade.logAndNotify(tObject, MessageType.INFO, sObject.dataSegments.get()); "Decompression (first pass) done"); } } } } catch(IOException e) { if(sObject.metaSegments.get().getCompletedString() != null) { assert false; // should never happen // send READY PDU } tObject.smsService.sendMessage(new PDUEncoder(true) .appendPrefix(SmsftpPDUType.SMSFTP_READY, // verify rawBytes SmsftpBinaryPduDecoder.getInstance()) SmsftpFacade.logAndNotify(tObject, MessageType.INFO, .appendSessionId(sObject.sessionId) "Verifying..."); .finalizePDU()); boolean authentic = false; // set state as RECEIVING_DATA afterwards try { SmsftpFacade.setStateAndNotify( if(sObject.getEncryptionFlag()) { // encrypted tObject, SessionState.RECEIVING_DATA); authentic = RSA.verify(sObject.authkey, } new ByteArrayInputStream(rawBytes), } sObject.signature); }; } } else { // unencrypted MessageDigest md = null; public static Runnable createResendReadyTask( try { SmsftpFacade tObject, PDU uObject) { md = MessageDigest.getInstance("SHA-1"); return () -> { } }; catch(NoSuchAlgorithmException e) { } assert false; // should never happen (min JDK7) } public static Runnable createProcessDataTask( md.update(rawBytes); SmsftpFacade tObject, PDU uObject) { byte[] checksum = md.digest(); return () -> { authentic = Arrays.equals(checksum, sObject.signature); SmsftpSession sObject = tObject.smsftpSession; } // NOTE: no need to initialize since we have done that on } // receipt of SMSFTP_META PDU catch(IOException e) { assert false; // should never happen // validate contents of PDU } Object[] uData = uObject.getData(); SmsftpFacade.logAndNotify(tObject, MessageType.INFO, if(uData == null || uData.length < 2 || "Verification done"); !(uData[0] instanceof Integer && if(authentic) { uData[1] instanceof String)) { SmsftpFacade.logAndNotify(tObject, MessageType.INFO, return; // just return on invalid cases "File complete and verified"); } // move rawBytes to SmsftpSession.dataBytes int segnum = (Integer)(uData[0]); sObject.dataBytes = rawBytes; String dataSeg = (String)(uData[1]); // notify presenters about the success Object[] noticeArr = { MessageType.STATUS, // update extracted data and process the whole file on completion StatusType.NOTICE, SessionState.COMPLETED, if(sObject.dataSegments.get().updateSegment(dataSeg, segnum-1) & & sObject.dataBytes }; sObject.dataSegments.get().getCompletedString() != null) { SmsftpFacade.setChangedAndNotifyObservers( byte[] rawBytes = null; tObject, noticeArr); try { // reply with END PDU ASAP rawBytes = Base85.decode( if(sObject.endReceived) { sObject.dataSegments.get().getCompletedString()); // for formalities sake, we'll transition to COMPLETED } SmsftpFacade.setStateAndNotify( catch(IOException e) { tObject, SessionState.COMPLETED); assert false; // should never happen // send END PDU } tObject.smsService.sendMessage(new PDUEncoder(true) ByteArrayInputStream bais = new ByteArrayInputStream(rawBytes); .appendPrefix(SmsftpPDUType.SMSFTP_END, ByteArrayOutputStream baos = null; SmsftpBinaryPduDecoder.getInstance()) .appendSessionId(
79 tObject.smsftpSession.sessionId) } .finalizePDU()); tObject.smsftpSession.encodedKeyPdu[i] = (new PDUEncoder(true)) // set state as TERMINATING afterwards .appendPrefix(SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY, SmsftpFacade.setStateAndNotify( SmsftpBinaryPduDecoder.getInstance()) tObject, SessionState.TERMINATING); .appendHexSegmentId(i+1) // multipart ids start at 1 return; // no need for other later calls .appendString(payload) } .finalizePDU(); } // then send each messasge else { // sender's file authentication failed tObject.smsService.sendMessage( SmsftpFacade.logAndNotify(tObject, MessageType.INFO, tObject.smsftpSession.encodedKeyPdu[i]); "File failed verification"); p=p+155; // notify presenters about the failure } Object[] noticeArr = { MessageType.STATUS, SmsftpFacade.logAndNotify(tObject, MessageType.INFO, StatusType.NOTICE, SessionState.COMPLETED, "Sent last initiator identity PDU to receiver"); null }; // register encodedKeyPdu Array for retransmission SmsftpFacade.setChangedAndNotifyObservers( tObject.registerRetrans(SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY, tObject, noticeArr); tObject.smsftpSession.encodedKeyPdu); } }; } // finally, set session state to COMPLETED SmsftpFacade.setStateAndNotify( public static Runnable createProcessRespDataTask( tObject, SessionState.COMPLETED); SmsftpFacade tObject, PDU uObject) { } return () -> { else { // not yet complete; misc actions SmsftpLookupTable.processIdentityData(tObject, uObject, // force sending of CORE_RETRANSMISSION PDUs on SessionState.COMPLETE_WAIT); // certain percentages (optional for spec; added for ui) }; int progress = (sObject.getLastDataSegNumReceived() - } sObject.getSkippedSegNumsLength())*100 / sObject.getDataSegCount(); public static Runnable createProcessInitDataTask( if(progress - sObject.lastDataMilestone >= 20) { SmsftpFacade tObject, PDU uObject) { sObject.lastDataMilestone = progress; return () -> { tObject.sendRetransPdu(); SmsftpLookupTable.processIdentityData(tObject, uObject, } SessionState.COMPLETED); } }; } // notify presenters about arrival of new DATA PDU SmsftpFacade.setChangedAndNotifyObservers(tObject, public static Runnable createCompletedEndReceivedTask( MessageType.STATUS, StatusType.NOTICE, SmsftpFacade tObject, PDU uObject) { SessionState.RECEIVING_DATA); return () -> { }; SmsftpFacade.logAndNotify(tObject, MessageType.INFO, } "Received END request. Finalizing session..."); // send END PDU public static Runnable createSendWaitTask( tObject.smsService.sendMessage(new PDUEncoder(true) SmsftpFacade tObject, PDU uObject) { .appendPrefix(SmsftpPDUType.SMSFTP_END, return () -> { SmsftpBinaryPduDecoder.getInstance()) SmsftpFacade.logAndNotify(tObject, MessageType.INFO, .appendSessionId(tObject.smsftpSession.sessionId) "Not yet finished; requesting peer to wait longer..."); .finalizePDU()); SmsftpSession sObject = tObject.smsftpSession; // set state as TERMINATING afterwards // enable endReceived flag SmsftpFacade.setStateAndNotify( sObject.endReceived = true; tObject, SessionState.TERMINATING); { // send an END_WAIT PDU }; tObject.smsService.sendMessage(new PDUEncoder(true) } .appendPrefix(SmsftpPDUType.SMSFTP_END_WAIT, SmsftpBinaryPduDecoder.getInstance()) public static Runnable createCompleteWaitEndReceivedTask( .appendSessionId(sObject.sessionId) SmsftpFacade tObject, PDU uObject) { .finalizePDU()); return () -> { } SmsftpFacade.logAndNotify(tObject, MessageType.INFO, // and request retransmission of missing PDUs "All parties finished. Terminating session..."); tObject.sendRetransPdu(); SmsftpFacade.setStateAndNotify( }; tObject, SessionState.TERMINATING); } // then disconnect asap tObject.disconnect(CoreProtocolFacade.COMPLETED_STATUS_CODE); public static Runnable createProcFirstRespDataTask( }; SmsftpFacade tObject, PDU uObject) { } return () -> { // set to not waiting SmsftpFacade.setStateAndNotify( tObject, SessionState.INITIATOR_KEY_EXCHANGE); //// utility functions // process data received private static void processIdentityData(SmsftpFacade tObject, PDU uObject, SmsftpLookupTable.createProcessRespDataTask( SessionState completeState) { tObject, uObject).run(); SmsftpSession sObject = tObject.smsftpSession; // generate keys and encode them properly // validate contents of PDU tObject.smsftpSession.generatePairingIdentity(); Object[] uData = uObject.getData(); byte[] pubEncKey = if(uData == null || uData.length < 2 || tObject.smsftpSession.localEncKeypair.getPublic() !(uData[0] instanceof Integer && .getEncoded(); uData[1] instanceof String)) { byte[] pubAuthKey = return; // just return on invalid cases tObject.smsftpSession.localAuthKeypair.getPublic() } .getEncoded(); int segnum = (Integer)(uData[0]); String concatenatedKeys = if(segnum < 1 || segnum > 5) { Base85.encode(pubEncKey) + "¥" + Base85.encode(pubAuthKey); return; // just return on invalid cases } // then start sending SMSFTP_INITIATOR_IDENTITY PDUs String dataSeg = (String)(uData[1]); int p = 0; for(int i = 0; i < 5; i++) { // update extracted data and process whole string on completion // construct each PDU if(sObject.identitySegments.updateSegment(dataSeg, segnum-1) && String payload = ""; sObject.identitySegments.getCompletedString() != null) { if(p < concatenatedKeys.length()) { String[] splitStr = sObject.identitySegments payload = concatenatedKeys.substring(p, .getCompletedString().split("¥"); (p+155 < concatenatedKeys.length()) ? p+155 : String b85PubEncKey = splitStr[0]; concatenatedKeys.length()); String b85PubAuthKey = splitStr[1];
80 try { SMSFTP_INIT (PDUSubType.BINARY, PduSegmentIdFormat.HEX, 1), sObject.peerEnckey = Base85.decode(b85PubEncKey); SMSFTP_META (PDUSubType.BINARY, PduSegmentIdFormat.HEX, 1), sObject.peerAuthkey = Base85.decode(b85PubAuthKey); SMSFTP_READY (PDUSubType.BINARY, 0), } SMSFTP_DATA (PDUSubType.BINARY, PduSegmentIdFormat.BINARY), catch(IOException e) { SMSFTP_INITIATOR_IDENTITY (PDUSubType.BINARY, PduSegmentIdFormat.HEX), assert false; // should never happen SMSFTP_RESPONDER_IDENTITY (PDUSubType.BINARY, PduSegmentIdFormat.HEX), } SMSFTP_END (PDUSubType.BINARY, 0), // finally, set necessary session state SMSFTP_END_WAIT (PDUSubType.BINARY, 0); SmsftpFacade.setStateAndNotify( tObject, completeState); // notify presenters about completion SmsftpFacade.setChangedAndNotifyObservers( public final PDUSubType pduSubType; tObject, MessageType.STATUS, StatusType.NOTICE, completeState); public final PduSegmentIdFormat segIdFormat; // start a new thread for repeated sending of END PDUs tObject.sendEndPduRepeatedly(); public final int sessionIdPosition; } }
/** // default access control since enums are restricted * Encrypt stream using given AES key then tries compress data further, if SmsftpPDUType(PDUSubType pduSubType) { * possible this(pduSubType, PduSegmentIdFormat.NONE); * } * @return CompEncData containing details if resulting encrypted data is * compressed and the actual data SmsftpPDUType(PDUSubType pduSubType, PduSegmentIdFormat segIdFormat) { */ this(pduSubType, segIdFormat, -1); private static CompEncData encryptAndCompact(InputStream is, } byte[] aesKey, byte[] iv) { CompEncData retVal = new CompEncData(); SmsftpPDUType(PDUSubType pduSubType, int sessionIdPosition) { ByteArrayOutputStream encBaos = new ByteArrayOutputStream(); this(pduSubType, PduSegmentIdFormat.NONE, sessionIdPosition); } // encrypt bytes -E? try { SmsftpPDUType(PDUSubType pduSubType, PduSegmentIdFormat segIdFormat, AES.encrypt(aesKey, iv, is, encBaos); int sessionIdPosition) { } this.pduSubType = pduSubType; catch(IOException e) { this.segIdFormat = segIdFormat; assert false; // should never happen since we're just using this.sessionIdPosition = sessionIdPosition; // byte arrays } } byte[] encBytes = encBaos.toByteArray(); encBaos = null; // cleanup asap @Override // compress encrypted bytes -EC public SmsftpPDUType valueOf() { ByteArrayOutputStream compEncBaos = new ByteArrayOutputStream(); return valueOf(this.name()); CountingOutputStream compEncCos = } new CountingOutputStream(compEncBaos); try { @Override XZ.compress(new ByteArrayInputStream(encBytes), compEncCos); public PDUSubType getPduSubType() { } return this.pduSubType; catch(IOException e) { } assert false; // should never happen since we're just using // byte arrays @Override } public PduSegmentIdFormat getPduSegmentIdFormat() { return this.segIdFormat; // set proper flags and return values } if(compEncCos.getByteCount() < encBytes.length) { // use encryption retVal.compressAfter = true; @Override retVal.data = compEncBaos.toByteArray(); public int getSessionIdPosition() { } return this.sessionIdPosition; else { // don't use encryption } retVal.compressAfter = false; } retVal.data = encBytes; }
return retVal; } ------Filename: src/main/java/com/transmisms/smsftp/protocol/SmsftpPresenter.java ------
// convenience get() function package com.transmisms.smsftp.protocol; public static BiFunction
import java.util.Arrays; ------import java.util.Observable; Filename: src/main/java/com/transmisms/smsftp/protocol/SmsftpPDUType.java ------package com.transmisms.smsftp.protocol; public /*abstract*/ class SmsftpPresenter extends Presenter { private final SmsftpFacade smsftpFacade; import com.transmisms.core.protocol.PDUType.PDUSubType; import com.transmisms.core.protocol.PDUType;
public SmsftpPresenter(SmsftpFacade smsftpFacade) { this.smsftpFacade = smsftpFacade; public enum SmsftpPDUType implements PDUType
81 this.onStatusUpdate(statusType, sState, carrier); } else if(args[2] instanceof ConnectionState) { public void receive() { ConnectionState cState = this.smsftpFacade.receive(); (ConnectionState)(args[2]); } Object[] carrier = { }; if(args.length >= 4) { public void insecureSendFile() { carrier = Arrays.copyOfRange(args, 3, this.smsftpFacade.insecureSendFile(); args.length); } } this.onStatusUpdate(statusType, cState, carrier); public void secureSendFile() { } this.smsftpFacade.secureSendFile(); else { } return; // just do nothing and return ASAP } public void pair() { break; this.smsftpFacade.pair(); } } default: { assert false; // should never happen @Override } public final void update(Observable o, Object arg) { } // check for unwanted cases break; if(arg == null || !(arg instanceof Object[])) { } return; // do nothing case FATAL: } case ERROR: Object[] args = (Object[])arg; case WARN: if(args.length <= 1 || args.length == 0 || case INFO: !(args[0] instanceof MessageType)) { case DEBUG: return; // do nothing case TRACE: { } // check if the rest of the payload is valid this.processMessage(o, args); if(!(args.length == 2 && args[1] instanceof String)) { } return; // do not log anything that is not loggable } final private void processMessage(Observable o, Object[] args) { // and get logMessage from args // args[0] is guaranteed instanceof MessageType String logMessage = (String)(args[1]); MessageType messageType = (MessageType)args[0]; // finally log message this.onLogMessage(messageType, logMessage); switch(messageType) { break; case PROMPT: { } if(!(args[1] instanceof PromptType)) { // if invalid input default: { return; // just do nothing and return ASAP return; // just do nothing and return ASAP } } PromptType promptType = (PromptType)args[1]; } String promptMessage = ""; } if(args.length >= 3 && args[2] != null && args[2] instanceof String) { protected void onLogMessage(MessageType mType, String message) {} promptMessage = (String)(args[2]); } protected void onStatusUpdate(StatusType sType, SessionState sState, this.onUserPrompt(promptType, promptMessage); Object ... args) {} break; } protected void onStatusUpdate(StatusType sType, ConnectionState oldState, case STATUS: { Object ... args) {} if(args.length < 3 || !(args[1] instanceof StatusType)) { // if invalid input protected void onUserPrompt(PromptType pType, String message) {} return; // just do nothing and return ASAP } } StatusType statusType = (StatusType)args[1]; switch(statusType) { case STATE_CHANGED: { if(args.length < 4) { ------return; // just do nothing and return ASAP Filename: src/main/java/com/transmisms/smsftp/protocol/SmsftpSession.java } ------if((args[2] instanceof SessionState) && (args[3] instanceof SessionState)) { package com.transmisms.smsftp.protocol; SessionState oldState = (SessionState)(args[2]); SessionState newState = (SessionState)(args[3]);import com.transmisms.core.protocol.Connection; this.onStatusUpdate(statusType, import com.transmisms.core.protocol.SegmentManager; oldState, newState); import com.transmisms.core.protocol.Session; } import com.transmisms.core.util.crypto.RSA; else if((args[2] instanceof ConnectionState) && (args[3] instanceof ConnectionState)) { import java.security.KeyPair; ConnectionState oldState = import java.security.PrivateKey; (ConnectionState)(args[2]); import java.security.PublicKey; ConnectionState newState = (ConnectionState)(args[3]); import java.io.ByteArrayOutputStream; this.onStatusUpdate(statusType, import java.util.ArrayList; oldState, newState); import java.util.List; } import java.util.UUID; else { import java.util.concurrent.atomic.AtomicBoolean; return; // just do nothing and return ASAP import java.util.concurrent.atomic.AtomicReference; } break; import java.io.IOException; } import java.security.spec.InvalidKeySpecException; case NOTICE: { if(args[2] instanceof SessionState) { SessionState sState = (SessionState)(args[2]); Object[] carrier = { }; public class SmsftpSession extends Session { if(args.length >= 4) { public enum Role { carrier = Arrays.copyOfRange(args, 3, INITIATOR, // SENDER args.length); RESPONDER // RECEIVER } }
82 this.cecEncryption = cecEncryption; public enum SessionState { CONNECTED, // all if(role == Role.INITIATOR) { INIT_RECEIVED, // snd // check if dataBytes is non-null INIT_SENT, // rcv if(dataBytes == null) { SENDING_DATA, // snd throw new IllegalArgumentException("dataBytes cannot be null"); RECEIVING_DATA, // rcv } INITIATOR_KEY_EXCHANGE, // kx-ini INITIATOR_WAIT, // kx-ini this.dataBytes = dataBytes; RESPONDER_KEY_EXCHANGE, // kx-rsp this.fileSize = dataBytes.length; COMPLETED, // rcv, kx-* } COMPLETE_WAIT, // snd, kx-* else { TERMINATING, // all //this.dataBytes = null; } } this.role = role; public enum SessionIntent { DATA_TRANSFER, PAIR } this.sessionIntent = SessionIntent.DATA_TRANSFER; this.filename = filename;
// if sender on encrypted session, construct necessary keys private final AtomicReference
/** * Constructs an SmsftpSession for sending data /** * * * @param enckey private RSA key to be used in AES key decryption * @param enckey public RSA key to be used in AES key decryption * @param authkey private RSA key to be used in creating the auth signature * @param authkey public RSA key to be used in creating the auth signature * @throws NullPointerException * @throws NullPointerException * @throws IllegalArgumentException * @throws IllegalArgumentException */ */ private SmsftpSession(Connection connection, Role role, String filename, public static SmsftpSession generateSenderDataSession( byte[] dataBytes, byte[] enckey, byte[] authkey) Connection connection, String filename, byte[] dataBytes, throws NullPointerException, IllegalArgumentException { byte[] enckey, byte[] authkey) { super(connection, UUID.randomUUID()); return new SmsftpSession(connection, Role.INITIATOR, filename, dataBytes, enckey, authkey); boolean cecEncryption; } if(enckey == null && authkey == null) { // unencrypted cecEncryption = false; /** } * else if(enckey != null && authkey != null) { // encrypted */ cecEncryption = true; public static SmsftpSession generateInitiatorPairSession( } Connection connection) { else { // one of the keys is missing! return new SmsftpSession(connection, Role.INITIATOR); throw new IllegalArgumentException("enckey and authkey must " + } "be either both non-null or both null"); } /**
83 * } */ } public static SmsftpSession generateReceiverSession(Connection connection) { return new SmsftpSession(connection); public SessionState getSessionState() { } return this.sessionStateRef.get(); } public Role getRole() { return this.role; public void setSessionState(SessionState sessionState) { } this.getAndSetSessionState(sessionState); } public boolean getEncryptionFlag() { return this.cecEncryption; public SessionState getAndSetSessionState(SessionState sessionState) { } if(sessionState == null) { throw new NullPointerException("Session state cannot be null"); public SessionIntent getSessionIntent() { } return this.sessionIntent; return this.sessionStateRef.getAndSet(sessionState); } } } public int getDataSegCount() { return this.dataSegments.get().segCount; } public int getLastDataSegNumReceived() { ------return this.dataSegments.get().getLastSegmentReceived(); Filename: } src/main/java/com/transmisms/smsftp/protocol/SmsftpTextBasedPduDecoder.java ------public int getSkippedSegNumsLength() { return this.dataSegments.get().getSkippedSegNums().length; package com.transmisms.smsftp.protocol; } import com.transmisms.core.protocol.CorePDUType; public int getDataSize() { import com.transmisms.core.protocol.PDU; return this.dataPdu.size(); import com.transmisms.core.protocol.PDUType; } import com.transmisms.core.protocol.TextBasedPDUDecoder; import com.transmisms.core.protocol.PDUMalformedException; /* old methods below */ import com.transmisms.smsftp.protocol.SmsftpFacade; public String getFilename() { import java.util.ArrayList; return this.filename; import java.util.List; } import java.util.regex.Pattern; public void generatePairingIdentity() { ByteArrayOutputStream privBaos = new ByteArrayOutputStream(); ByteArrayOutputStream pubBaos = new ByteArrayOutputStream(); try { public class SmsftpTextBasedPduDecoder extends TextBasedPDUDecoder { // generate encryption key pair private static final Pattern FILENAME_PATTERN = RSA.generateKeys(privBaos, pubBaos); Pattern.compile("[a-zA-Z0-9. \\-#$%&'()+,i=@_]+"); privBaos.flush(); pubBaos.flush(); private static SmsftpTextBasedPduDecoder thisInstance; PrivateKey privKey = RSA.generatePrivate(privBaos.toByteArray()); static { PublicKey pubKey = RSA.generatePublic(pubBaos.toByteArray()); String[] strArray = { "smsftp" }; this.localEncKeypair = new KeyPair(pubKey, privKey); SmsftpTextBasedPduDecoder.thisInstance = new SmsftpTextBasedPduDecoder(strArray); // generate authentication key pair } privBaos = new ByteArrayOutputStream(); pubBaos = new ByteArrayOutputStream(); protected SmsftpTextBasedPduDecoder(String[] knownProtocols) { RSA.generateKeys(privBaos, pubBaos); super(knownProtocols); privBaos.flush(); } pubBaos.flush(); privKey = RSA.generatePrivate(privBaos.toByteArray()); public static SmsftpTextBasedPduDecoder getInstance() { pubKey = RSA.generatePublic(pubBaos.toByteArray()); return SmsftpTextBasedPduDecoder.thisInstance; this.localAuthKeypair = new KeyPair(pubKey, privKey); } } catch(InvalidKeySpecException e) { @Override assert false; // code should never reach here protected Object[] decodePayloadSubparts(PDUType pduType, return; String[] payloadSubparts, String encoded) } throws PDUMalformedException { catch(IOException e) { List
/** // get Receiver MSISDN/MIN * Does nothing if this is an INITIATOR session. String receiverMsisdnMin = payloadSubparts[0]; */ // validate msisdn/min public void setCecEncryption(boolean cecEncryption) { boolean validMsisdnMin = true; if(this.getRole() == Role.RESPONDER) { if((receiverMsisdnMin.charAt(0) != '+' && this.cecEncryption = cecEncryption; !Character.isDigit(receiverMsisdnMin.charAt(0))) || } !receiverMsisdnMin.substring(1).chars() } .allMatch(x -> Character.isDigit(x))) { throw new PDUMalformedException( /** "Invalid MSISDN/MIN for " + * Does nothing if sessionIntent is already set corePduType.toString() + ": " + receiverMsisdnMin, */ encoded); public void setSessionIntent(SessionIntent sessionIntent) { } if(this.sessionIntent == null) { returnData.add(receiverMsisdnMin); this.sessionIntent = sessionIntent; // get Intent
84 String intent = payloadSubparts[1]; } if(!intent.equals("send") && !intent.equals("pair")) { // finally, return the data throw new PDUMalformedException("Invalid Intent for " + return returnData.toArray(); corePduType.toString() + ": " + intent, encoded); } } } returnData.add(intent); // get Filename if send if(intent.equals("send")) { if(payloadSubparts.length != 3) { throw new PDUMalformedException( ------"Payload parts for " + Filename: corePduType.toString() + src/main/java/com/transmisms/smsftp/temputil/SmsftpLoggerPresenter.java " too long or too short", encoded); ------
} package com.transmisms.smsftp.temputil; String filename = payloadSubparts[2]; // validate filename import com.transmisms.core.protocol.Presenter.MessageType; if(!FILENAME_PATTERN.matcher(filename).matches()) { import com.transmisms.smsftp.protocol.SmsftpPresenter; throw new PDUMalformedException( "Invalid Filename for " + import java.util.Observable; corePduType.toString() + ": " + filename, encoded); import org.apache.logging.log4j.Logger;
} returnData.add(filename); } public class SmsftpLoggerPresenter extends SmsftpPresenter { else { private final Logger smsftpLogger; if(payloadSubparts.length != 2) { throw new PDUMalformedException( @Override "Payload parts for " + public void receive() {} // disabled corePduType.toString() + @Override " too long or too short", encoded); public void insecureSendFile() {} // disabled @Override } public void secureSendFile() {} // disabled } @Override break; public void pair() {} // disabled } case CORE_HEAD: { public SmsftpLoggerPresenter(Logger smsftpLogger) { if(payloadSubparts.length != 1) { super(null); // we don't need the actual SmsftpFacade object here throw new PDUMalformedException("Payload parts for " + this.smsftpLogger = smsftpLogger; corePduType.toString() + " too long or too short", } encoded); } @Override // get Encryption Mode protected void onLogMessage(MessageType messageType, String logMessage) { String encMode = payloadSubparts[0]; // finally, log into the respective logger if(encMode.equals("enc") || encMode.equals("noenc")) { switch(messageType) { returnData.add(encMode); case FATAL: { } this.smsftpLogger.fatal(logMessage); else { break; throw new PDUMalformedException( } "Invalid Encryption Mode: " + encMode, case ERROR: { encoded); this.smsftpLogger.error(logMessage); } break; break; } } case WARN: { case CORE_CONNECTION_RESPONSE: this.smsftpLogger.warn(logMessage); case CORE_LAST_ACK: { break; // get Status Code } String statusCode = ""; case INFO: { if(payloadSubparts.length == 1 || payloadSubparts.length == 2) { this.smsftpLogger.info(logMessage); statusCode = payloadSubparts[0]; break; returnData.add(statusCode); } } case DEBUG: { else { this.smsftpLogger.debug(logMessage); throw new PDUMalformedException("Payload parts for " + break; corePduType.toString() + " too long or too short", } encoded); case TRACE: { } this.smsftpLogger.trace(logMessage); // validate status code break; if(SmsftpFacade.getStatusMessage(statusCode) } == null) { default: { throw new PDUMalformedException( // do nothing "Invalid status code from " + break; corePduType.toString() + ": " + statusCode, } encoded); } } } }
if(payloadSubparts.length == 1) { // without status message returnData.add(null); } else if(payloadSubparts.length == 2) { // with status message------// add Status Message Filename: src/main/java/com/transmisms/ui/javafx/ConfigManager.java returnData.add(payloadSubparts[1]); ------} break; package com.transmisms.ui.javafx; } default: { import org.yaml.snakeyaml.DumperOptions; // code should NEVER reach here import org.yaml.snakeyaml.Yaml; throw new AssertionError("Uncaught PDU Type: " + corePduType);import org.apache.logging.log4j.Logger; }
85 import java.io.File; new FileInputStream(f))); import java.io.FileInputStream; } import java.io.FileWriter; catch(FileNotFoundException e) { import java.io.InputStream; } import java.util.HashMap; // refresh configRoot since we used a new config import java.util.Map; Object cfRObj = config.get(ConfigManager.ROOT_KEY); import java.util.Set; if(cfRObj == null) { configExists = false; import java.io.IOException; } import java.io.FileNotFoundException; else { configRoot = (Map
private static Yaml yaml; return configRoot; private static Map
86 public static Map mapDeepCopy(Map src) { this.pairButton.setDisable(currentEntry == null); return ConfigManager.mapDeepCopy(src, new HashMap
public void initialize(Stage contactEditStage, @FXML MainWindowController primaryController) { public void handlePairButtonAction(ActionEvent event) { // set properties // mark proper activeContact this.contactEditStage = contactEditStage; String peer = (String)this.currentEntry.get(ContactManager.MSISDN_KEY); this.primaryController = primaryController; this.primaryController.activeContact.set(peer); this.contactOList = primaryController.contactOList; this.contactEditStage.close(); // cleanup } // then pass this to the MainWindowController this.primaryController.handleExternalPairButtonAction(event); protected void prepareAndShowStage(Map
87 } // enable/disable controls as needed catch(IOException e) { } logger.trace("got IOException!!!"); e.printStackTrace(); @FXML } public void handleOnCloseRequest(WindowEvent event) { // unbind button properties first Map
private static Yaml yaml; return configRoot; private static Map
88 return isSuccessful; } } catch(IOException e) { System.out.println( //// utility functions here "Caught IOException when loading dialog header templates"); public static void sortContactListByName( e.printStackTrace(); List
// check for validity and skip if invalid package com.transmisms.ui.javafx; if(!(name instanceof String && number instanceof String)) { continue; } import javafx.application.Application; import javafx.application.Platform; Map
import java.lang.reflect.Method; import java.util.Map; ------Filename: src/main/java/com/transmisms/ui/javafx/FingerprintDialog.java import java.io.IOException; ------package com.transmisms.ui.javafx; public class Main extends Application { import javafx.scene.Parent; // initialize logging import javafx.scene.control.ButtonType; private final Logger mainLogger = LogManager.getLogger("com.transmisms"); import javafx.scene.control.Dialog; private final Logger protocolLogger = import javafx.scene.control.Label; LogManager.getLogger("com.transmisms.smsftp.protocol"); import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.text.Text; import javafx.stage.StageStyle; public class JavaFXUpdateMethodContainer { Method updateMessage; import javafx.fxml.FXMLLoader; Method updateTitle; Method updateLongProgress; import java.io.IOException; Method updateDoubleProgress;
public JavaFXUpdateMethodContainer(Method updateMessage, Method updateTitle, Method updateLongProgress, public class FingerprintDialog extends Dialog
89 prefsStage.setScene(new Scene(prefsParent, 640, 520)); prefsStage.initOwner(primaryStage); prefsStage.initModality(Modality.APPLICATION_MODAL); public static void main(String[] args) { contactEditStage.setTitle("Contact Details"); Application.launch(args); contactEditStage.setScene(new Scene(contactEditParent, 480, 300)); } contactEditStage.initOwner(primaryStage); contactEditStage.initModality(Modality.APPLICATION_MODAL); primaryStage.setTitle("TransmiSMS"); primaryStage.setScene(new Scene(primaryParent, 640, 480)); // looking for public static void main()? here it is: primaryStage.show(); // finally, show the stage @Override public void start(Stage primaryStage) { this.mainLogger.trace( "Reached end of pseudo main(), JavaFX probably has taken over"); this.mainLogger.info("Starting transmisms-javafx v0.7..."); }
// load and initialize yaml files on startup Map
// initialize other stages Stage prefsStage = new Stage(StageStyle.UTILITY); ------Stage contactEditStage = new Stage(StageStyle.UTILITY); Filename: src/main/java/com/transmisms/ui/javafx/MainWindowController.java ------// get primaryLoader for JavaFx FXMLLoader primaryLoader = new FXMLLoader(getClass().getClassLoader().package com.transmisms.ui.javafx; getResource("fxml-main.fxml")); FXMLLoader prefsLoader = new FXMLLoader(getClass().getClassLoader().import com.transmisms.core.protocol.Connection; getResource("prefs-window.fxml")); import com.transmisms.core.protocol.PDUType; FXMLLoader contactEditLoader = import com.transmisms.core.protocol.Connection.ConnectionState; new FXMLLoader(getClass().getClassLoader(). import com.transmisms.core.util.crypto.BiometricFingerprint; getResource("contact-edit-window.fxml")); import com.transmisms.smsftp.protocol.SmsftpFacade; Parent primaryParent = null; import com.transmisms.smsftp.protocol.SmsftpPDUType; Parent prefsParent = null; import com.transmisms.smsftp.protocol.SmsftpPresenter; Parent contactEditParent = null; import com.transmisms.smsftp.protocol.SmsftpSession; try { import com.transmisms.smsftp.protocol.SmsftpSession.Role; primaryParent = primaryLoader.load(); import com.transmisms.smsftp.protocol.SmsftpSession.SessionIntent; prefsParent = prefsLoader.load(); import com.transmisms.smsftp.protocol.SmsftpSession.SessionState; contactEditParent = contactEditLoader.load(); import com.transmisms.smsftp.temputil.SmsftpLoggerPresenter; } import com.transmisms.ui.javafx.smsservice.AndroidSmsService; catch(IOException e) { import com.transmisms.ui.javafx.smsservice.ExtendedSmsService; this.mainLogger.error(e.getMessage()); import com.transmisms.ui.javafx.smsservice.ExtendedSmsService.ExtendedReason; e.printStackTrace(); import com.transmisms.ui.javafx.smsservice.DummySmsService; Platform.exit(); // exit ASAP import com.transmisms.ui.javafx.smsservice.GammuSmsService; } import org.apache.logging.log4j.Logger; // set css for the parents primaryParent.getStylesheets().add("main.css"); import javafx.application.Platform; prefsParent.getStylesheets().add("main.css"); import javafx.concurrent.Task; prefsParent.getStylesheets().add("prefs-window.css"); import javafx.event.ActionEvent; contactEditParent.getStylesheets().add("main.css"); import javafx.fxml.FXML; contactEditParent.getStylesheets().add("contact-edit-window.css");import javafx.fxml.FXMLLoader; import javafx.geometry.Side; // get controllers and initialize them import javafx.scene.Node; PrefsController prefsController = import javafx.scene.Parent; (PrefsController)prefsLoader.getController(); import javafx.scene.control.Button; ContactEditController contactEditController = import javafx.scene.control.ButtonBar.ButtonData; (ContactEditController)contactEditLoader.getController(); import javafx.scene.control.ButtonType; MainWindowController primaryController = import javafx.scene.control.ContextMenu; (MainWindowController)primaryLoader.getController(); import javafx.scene.control.Dialog; primaryController.initialize(configRoot, contactsRoot, prefsStage,import javafx.scene.control.Label; contactEditController, this.mainLogger, this.protocolLogger);import javafx.scene.control.ListCell; prefsController.initialize(configRoot, primaryController); import javafx.scene.control.ListView; contactEditController.initialize(contactEditStage, import javafx.scene.control.MenuItem; primaryController); import javafx.scene.control.ProgressBar; import javafx.scene.control.TextArea; // set close handler import javafx.scene.image.Image; primaryStage.setOnCloseRequest((e) -> { import javafx.scene.image.ImageView; mainLogger.trace("Main Window closed"); import javafx.scene.layout.HBox; primaryController.shutdown(); import javafx.scene.layout.VBox; }); import javafx.scene.text.Text; prefsStage.setOnCloseRequest((e) -> { import javafx.stage.FileChooser; prefsController.handleOnCloseRequest(e); import javafx.stage.Stage; }); import javafx.stage.StageStyle; contactEditStage.setOnCloseRequest((e) -> { contactEditController.handleOnCloseRequest(e); import javafx.beans.binding.Bindings; }); import javafx.beans.binding.BooleanBinding; import javafx.beans.property.SimpleBooleanProperty; // set static sizes import javafx.beans.property.SimpleDoubleProperty; primaryStage.setMinWidth(300); import javafx.beans.property.SimpleStringProperty; primaryStage.setMinHeight(200); import javafx.collections.FXCollections; prefsStage.setMinWidth(300); import javafx.collections.ObservableList; prefsStage.setMinHeight(200); import java.io.File; // set stage UI import java.io.FilenameFilter; prefsStage.setTitle("Preferences"); import java.nio.file.Files;
90 import java.nio.file.Paths; if(sType == StatusType.STATE_CHANGED) { import java.util.Arrays; if(args == null || args.length < 1 || import java.util.HashMap; !(args[0] instanceof SessionState)) { import java.util.List; return; // exit ASAP import java.util.Map; } import java.util.concurrent.CountDownLatch; SessionState newState = (SessionState)(args[0]); import java.util.concurrent.Executors; if(sState.equals(newState)) { import java.util.concurrent.ExecutorService; return; // exit on nothing changed condition import java.util.function.Consumer; } switch(newState) { import com.transmisms.core.protocol.SmsServiceException; case CONNECTED: { import java.io.IOException; Platform.runLater(() -> { this.mwc.connectionProgress.set(-1); import java.util.concurrent.ExecutionException; this.mwc.connectionStatus.set( "Connected to peer"); }); break; public class MainWindowController { } protected static class ContactListCell case INIT_RECEIVED: extends ListCell
91 final byte[] hostPrivEnc = ContactManager.convertToContactMap( session.localEncKeypair.getPrivate().getEncoded(); this.mwc.contactOList)); final byte[] hostPubAuth = this.mwc.refreshContactList = true; session.localAuthKeypair.getPublic().getEncoded(); }); final byte[] hostPrivAuth = } session.localAuthKeypair.getPrivate().getEncoded(); } final byte[] peerPubEnc = session.peerEnckey; } final byte[] peerPubAuth = session.peerAuthkey; }
@Override // compute fingerprint public void onStatusUpdate(StatusType sType, ConnectionState oldState, final byte[] localPubKeys = Object ... args) { new byte[hostPubEnc.length+hostPubAuth.length]; if(args == null || args.length < 1) { System.arraycopy(hostPubEnc, 0, localPubKeys, return; // exit ASAP 0, hostPubEnc.length); } System.arraycopy(hostPubAuth, 0, localPubKeys, if(sType == StatusType.STATE_CHANGED && hostPubEnc.length, hostPubAuth.length); args[0] instanceof ConnectionState) { final byte[] peerPubKeys = ConnectionState newState = (ConnectionState)(args[0]); new byte[peerPubEnc.length+peerPubAuth.length]; if(oldState == null || newState == null || System.arraycopy(peerPubEnc, 0, peerPubKeys, oldState.equals(newState)) { 0, peerPubEnc.length); return; // exit ASAP System.arraycopy(peerPubAuth, 0, peerPubKeys, } peerPubEnc.length, peerPubAuth.length); switch(newState) { final byte[] part1 = case CLOSED: { session.getRole().equals(Role.INITIATOR) ? Platform.runLater(() -> { localPubKeys : peerPubKeys; this.mwc.connectionStatus.set("Disconnected"); final byte[] part2 = }); session.getRole().equals(Role.INITIATOR) ? // then clean up afterwards peerPubKeys : localPubKeys; this.mwc.cleanupTransmission(); final byte[] combined = // force refresh by "sorting contact list" new byte[part1.length+part2.length]; Platform.runLater(() -> { System.arraycopy(part1, 0, combined, if(this.mwc.refreshContactList) { 0, part1.length); ContactManager.sortContactListByName( System.arraycopy(part2, 0, combined, this.mwc.contactOList); part1.length, part2.length); this.mwc.refreshContactList = false; final String bfp = } BiometricFingerprint.getBiometricFingerprint( }); combined); break; } // prompt user for fingerprint verification/if keys case ESTABLISHED: { // should be saved Platform.runLater(() -> Platform.runLater(() -> { this.mwc.connectionStatus.set( String aContact = this.mwc.activeContact.get(); "Connection established")); SimpleBooleanProperty saveKeys = break; new SimpleBooleanProperty(false); } this.mwc.fpConfirmation.setEmphasisText(bfp); case LISTENING: { this.mwc.fpConfirmation.showAndWait() Platform.runLater(() -> { .filter(response -> response == this.mwc.connectionProgress.set(-1); MainWindowController.dialogSavekeysButton) this.mwc.connectionStatus.set( .ifPresent(response -> { "Waiting for connection requests"); saveKeys.set(true); }); }); break; // return asap on cancel } if(!saveKeys.get()) { case REQUEST_RECEIVED: { return; Platform.runLater(() -> } this.mwc.connectionStatus.set( "Received connection request")); // get the corresponding keymap for activeContact break; Map
92 case CLOSING_2: { .getRole().equals(Role.RESPONDER)) { Platform.runLater(() -> { this.mwc.currentFacade this.mwc.connectionProgress.set(-1); .continueOperationAfterPrompt(); this.mwc.connectionStatus.set( return; "Requesting disconnection"); } }); // then prompt user afterwards break; this.mwc.connectionErrorNotification.showAndWait() } .ifPresent(response -> { case TIME_WAIT: { if(response == Platform.runLater(() -> { MainWindowController.dialogRetryButton) { this.mwc.connectionProgress.set(-1); this.mwc.currentFacade this.mwc.connectionStatus.set( .continueOperationAfterPrompt(); "Finalizing disconnection"); } }); else if(response == break; MainWindowController.dialogCancelButton) { } this.mwc.currentFacade default: { .cancelOperationAfterPrompt(); return; // exit ASAP } } else { // NOTE: code should never reach here } assert false; } } else if(sType == StatusType.NOTICE) { }); if(args.length >= 2 && args[0] instanceof String && }); args[1] instanceof String) { break; switch(oldState) { } case CLOSE_WAIT: { default: { // populate status code and message return; // just do nothing and return ASAP if(this.statusCode == null) { } this.statusCode = (String)args[0]; } this.statusMessage = (String)args[1]; } } break; // we won't override onLogMessage since we already delegated the task } // to the LoggerPresenters default: { } return; // exit ASAP } private static class ReceiverPresenter extends PresenterBase { } final private MainWindowController mwc; } } public ReceiverPresenter(SmsftpFacade smsftpFacade, } MainWindowController mwc) { super(smsftpFacade, mwc); @Override this.mwc = mwc; protected void onUserPrompt(PromptType pType, String message) { } switch(pType) { case CONNECTION_REQUEST: { @Override break; protected void onUserPrompt(PromptType pType, String message) { } switch(pType) { case SMS_SERVICE_ERROR: { case CONNECTION_REQUEST: { Platform.runLater(() -> { // DATA_TRANSFER-specific variables this.mwc.errorNotification boolean isEncrypted = false; .setPrimaryText(message); byte[] encKey = null; this.mwc.errorNotification byte[] authKey = null; .setSecondaryText(message); // PAIR-specific variables // set as not ready (error state) this.mwc.smsServiceIsReady.set(false); // try to get sender info from contacts map }); // temporary container for keys, if needed break; Map
93 ContactManager.PEER_PUB_ENC_KEY) : "Unknown", names)); null; n.put(ContactManager.KEYS_KEY, null); authKey = (isEncrypted && paired) ? // add to map itemKeys.get( this.mwc.contactOList.add(n); ContactManager.PEER_PUB_AUTH_KEY) : // then save changes to file null; ContactManager.writeConfig(ContactManager } .convertToContactMap( else if(this.smsftpFacade.smsftpSession this.mwc.contactOList)); .getSessionIntent().equals( } SessionIntent.PAIR)) { this.mwc.isReceiving.set(true); // do nothing; not applicable this.mwc.activeContact.set(peer); } else { // show connection controls later after assert false; // should never happen // initialization of other components throw new IllegalStateException( Platform.runLater(() -> "Invalid sessionIntent on request: " + this.mwc.showActiveConnectionControls() this.smsftpFacade.smsftpSession ); .getSessionIntent()); } // finally, accept the request if(this.smsftpFacade.smsftpSession // final variables to comply with lambda restrictions .getSessionIntent().equals( final boolean fIsEncrypted = isEncrypted; SessionIntent.DATA_TRANSFER)) { final byte[] fEncKey = encKey; this.smsftpFacade.acceptRequestAfterPrompt( final byte[] fAuthKey = authKey; fEncKey, fAuthKey); } Platform.runLater(() -> { // run on EDT else { // customize acceptConfirmation before displaying this.smsftpFacade.acceptRequestAfterPrompt( // customize with contact null, null); this.mwc.acceptConfirmation.setSecondaryText( } (contactExists ? } (String)(tempItem.get( else if(response == ContactManager.NAME_KEY)) : MainWindowController.dialogRejectButton) { peer)); // stop facade asap // customize according to intent this.mwc.currentFacade.disconnect("1003"); if(this.smsftpFacade.smsftpSession } .getSessionIntent().equals( else { // NOTE: code should never reach here SessionIntent.DATA_TRANSFER)) { assert false; this.mwc.acceptConfirmation.setPrimaryText( return; "Accept file transfer request?"); } this.mwc.acceptConfirmation.appendSecondaryText( }); " is trying to send you a file named \"" + }); break; this.smsftpFacade.smsftpSession.getFilename() + } "\". " + case TIMEOUT: (this.smsftpFacade case SMS_SERVICE_ERROR: .smsftpSession.getEncryptionFlag() ? case PEER_ERROR_EXCEEDED_LIMIT: "This will be transmitted over a " + default: { "secure connection. " : super.onUserPrompt(pType, message); "This will be transmitted over an" + } " UNSECURE connection. ") + } "Do you want to accept the request?"); } } else { // SessionIntent.PAIR @Override this.mwc.acceptConfirmation.setPrimaryText( public void onStatusUpdate(StatusType sType, SessionState sState, "Accept pairing request?"); Object ... args) { this.mwc.acceptConfirmation.appendSecondaryText( // check if event is still valid (paired ? // also prevents 'dead' connections from sending status updates " is trying to renew their pairing " + if(this.mwc.currentFacade == null || "with you. Please make sure to verify " + !this.mwc.currentFacade.smsftpSession.connection "their identity and their intent to " + .getConnectionState().equals( "redo the pairing procedure. " : ConnectionState.ESTABLISHED)) { " is trying to pair with you. " + return; "This method is not as secure as " + } "manually exchanging keys off-air, " + "however. ") + // apply supermethod first "Do you want to accept the request?"); super.onStatusUpdate(sType, sState, args); } // apply our own changes, overriding previous changes, if any if(sType == StatusType.NOTICE) { // finally show acceptConfirmation dialog // for receiving DATA this.mwc.acceptConfirmation.showAndWait() if(sState.equals(SessionState.COMPLETED) && .ifPresent(response -> { this.mwc.currentFacade.smsftpSession.getSessionIntent() // if user accepted the request .equals(SessionIntent.DATA_TRANSFER)) { if(response == if(args != null && args.length >= 1) { MainWindowController.dialogAcceptButton) { if(args[0] == null) { // failure // auto add contact if user accepts request Platform.runLater(() -> if(!contactExists && !fIsEncrypted) { this.mwc.ftpFailureNotification.show()); // add 'unknown' contact first } Map
94 (byte[])args[0]); switch(newState) { } case TERMINATING: { catch(IOException e) { if(this.mwc.currentFacade.smsftpSession } .getSessionIntent().equals( // finally, alert user SessionIntent.DATA_TRANSFER)) { Platform.runLater(() -> Platform.runLater(() -> this.mwc.ftpRecvSuccessNotification.show()); this.mwc.ftpSentSuccessNotification.show()); } break; } } } } else if(sState.equals(SessionState.RECEIVING_DATA)) { default: { // no need to check args, we'll compute progress here // do nothing } // get skipped segnums, max, and current } double segCount = this.mwc.currentFacade.smsftpSession } .getDataSegCount(); } double lastSeg = this.mwc.currentFacade.smsftpSession .getLastDataSegNumReceived(); @Override double missingNos = this.mwc.currentFacade.smsftpSession public void onStatusUpdate(StatusType sType, ConnectionState oldState, .getSkippedSegNumsLength(); Object ... args) { // lastSeg+2 to accomodate 0-based array and possibly super.onStatusUpdate(sType, oldState, args); // non-executed last call if(sType == StatusType.NOTICE) { double progress = (lastSeg+2 - missingNos)/segCount; SessionState sState = this.mwc.currentFacade.smsftpSession.getSessionState(); // compare old value to new value to prevent if(sState.equals(SessionState.SENDING_DATA) || // unnecessary updates sState.equals(SessionState.COMPLETE_WAIT)) { double curProgress = this.mwc.connectionProgress.get(); // extract segment for retransmission and if(progress != curProgress) { // update lastSegmentReq when necessary Platform.runLater(() -> { PDUType t; this.mwc.connectionProgress.set(progress); int n; // override cases where > 100 percent misses double missingNos = 0; // we'll leave this as zero for now int progressPercent = (int)(progress*100); if(args.length == 2 && args[0] instanceof PDUType && progressPercent = (progressPercent > 100) ? args[1] instanceof Integer && 100 : progressPercent; args[0] != null && args[1] != null) { // set status with percentage t = (PDUType)(args[0]); this.mwc.connectionStatus.set( n = (Integer)(args[1]); "Receiving data - " + n = (n > this.lastSegmentReq) ? progressPercent + "%"); n : this.lastSegmentReq; }); this.lastSegmentReq = n; } } } else { } return; // exit ASAP on failure else if(sType == StatusType.STATE_CHANGED) { } } if(t.equals(SmsftpPDUType.SMSFTP_DATA)) { } // compute estimated progress, etc. double segCount = this.mwc.currentFacade @Override .smsftpSession.getDataSize(); public void onStatusUpdate(StatusType sType, ConnectionState oldState, // this is just an estimate anyway Object ... args) { final double progress = (n - missingNos)/segCount; super.onStatusUpdate(sType, oldState, args); // then modify related ui components } Platform.runLater(() -> { } this.mwc.connectionProgress.set(progress); // override cases where > 100 percent misses private static class SenderPresenter extends PresenterBase { int progressPercent = (int)(progress*100); final private MainWindowController mwc; progressPercent = (progressPercent > 100) ? private int lastSegmentReq = -1; 100 : progressPercent; // set status with percentage public SenderPresenter(SmsftpFacade smsftpFacade, this.mwc.connectionStatus.set( MainWindowController mwc) { "Sending data - " + super(smsftpFacade, mwc); progressPercent + "%"); this.mwc = mwc; }); } } } @Override } protected void onUserPrompt(PromptType pType, String message) { } switch(pType) { } case CONNECTION_REQUEST: { return; // do nothing; this should never happen } case TIMEOUT: // toolbar buttons case SMS_SERVICE_ERROR: @FXML case PEER_ERROR_EXCEEDED_LIMIT: private Button addContactButton; default: { @FXML super.onUserPrompt(pType, message); private Button editContactButton; } @FXML } private Button removeContactButton; } @FXML private Button sendFileButton; @Override @FXML public void onStatusUpdate(StatusType sType, SessionState sState, private Button sendFileInsecurelyButton; Object ... args) { @FXML super.onStatusUpdate(sType, sState, args); private Button appMenuButton; if(sType == StatusType.STATE_CHANGED) { // infobars if(args == null || args.length < 1 || @FXML !(args[0] instanceof SessionState)) { private Parent acceptConnInfobar; return; // exit ASAP @FXML } private Button toggleAcceptButton; SessionState newState = (SessionState)(args[0]); @FXML if(sState.equals(newState)) { private Parent errorInfobar; return; // exit on nothing changed condition // panes } @FXML
95 private HBox welcomePane; this.prefsStage = prefsStage; @FXML this.contactEditController = contactEditController; private ListView contactList; this.mainLogger = mainLogger; // misc defined properties this.protocolLogger = protocolLogger; @FXML private String startRcvLblStr; // set properties for contactList @FXML this.contactOList.addAll( private String stopRcvLblStr; ContactManager.convertToContactList(contactsRoot)); @FXML this.contactList.setItems(this.contactOList); private String startRcvBtnStr; this.contactList.setCellFactory((lview) -> { @FXML ContactListCell lcell = private String stopRcvBtnStr; new ContactListCell(this); // log-related controls return lcell; @FXML }); private Button showHideButton; @FXML // set bindings private TextArea logTarget; this.acceptConnInfobar.visibleProperty().bind( this.smsServiceIsReady.and(this.activeContact.isNull())); final private ContextMenu appMenuCM = new ContextMenu(); this.acceptConnInfobar.managedProperty().bind( private Stage prefsStage; this.smsServiceIsReady.and(this.activeContact.isNull())); private ContactEditController contactEditController; this.errorInfobar.visibleProperty().bind( private StandardDialog removeContactConfirmation = null; this.smsServiceIsReady.not()); private StandardDialog peerDisconnectNotification = null; this.errorInfobar.managedProperty().bind( private StandardDialog connectionErrorNotification = null; this.smsServiceIsReady.not()); private StandardDialog errorNotification = null; private StandardDialog acceptConfirmation = null; this.welcomePane.visibleProperty().bind( private StandardDialog disconnectConfirmation = null; Bindings.isEmpty(contactOList)); private StandardDialog ftpRecvSuccessNotification = null; this.contactList.visibleProperty().bind( private StandardDialog ftpSentSuccessNotification = null; Bindings.isNotEmpty(contactOList)); private StandardDialog ftpFailureNotification = null; private FingerprintDialog fpConfirmation = null; ObservableList
//// initialize Dialogs this.peerDisconnectNotification = /** new StandardDialog( * Initializes the controller for objects that are needed from "Peer Disconnected", * external sources as well as the properties of its members "", // populated with custom status code and messages from * // CLOSE_WAIT NOTICE */ new Image( public void initialize(Map
96 // NOTE: this is populated with custom messages from errors this.removeContactConfirmation.getDialogPane().getButtonTypes() // thrown by SmsService .add(ButtonType.CANCEL); "", this.acceptConfirmation.getDialogPane().getButtonTypes() "", .add(MainWindowController.dialogAcceptButton); new Image( this.acceptConfirmation.getDialogPane().getButtonTypes() this.getClass().getClassLoader().getResourceAsStream( .add(MainWindowController.dialogRejectButton); "icons/dialog-error.png")) this.disconnectConfirmation.getDialogPane().getButtonTypes() ); .add(MainWindowController.dialogDisconnectButton); this.connectionErrorNotification = this.disconnectConfirmation.getDialogPane().getButtonTypes() new StandardDialog( .add(ButtonType.CANCEL); // NOTE: this is populated with custom messages from errors this.ftpRecvSuccessNotification.getDialogPane().getButtonTypes() // thrown by SmsftpFacades .add(ButtonType.OK); "", this.ftpSentSuccessNotification.getDialogPane().getButtonTypes() "", .add(ButtonType.OK); new Image( this.ftpFailureNotification.getDialogPane().getButtonTypes() this.getClass().getClassLoader().getResourceAsStream( .add(ButtonType.OK); "icons/dialog-error.png")) this.fpConfirmation.getDialogPane().getButtonTypes() ); .add(MainWindowController.dialogSavekeysButton); this.removeContactConfirmation = new StandardDialog( this.fpConfirmation.getDialogPane().getButtonTypes() "Delete this contact?", .add(ButtonType.CANCEL); "Deleting this contact will also delete the associated " + "pairing keys, if any. This action cannot be undone.", // initialize FileChoosers new Image( this.sendFileChooser.setTitle("Select file to be sent"); this.getClass().getClassLoader().getResourceAsStream( "icons/dialog-warning.png")) ); // task for tailing TextArea logTarget this.acceptConfirmation = new StandardDialog( this.pool.submit(new Task
97 } this.pool.submit(new Task
this.handleSendAction(event, (sendFile) -> { private void handleSendAction(ActionEvent event, Consumer
98 .ifPresent(response -> { @FXML }); protected void handleAppMenuButtonAction(ActionEvent event) { } this.appMenuCM.show(appMenuButton, Side.LEFT, appMenuButton.getWidth(), 0); @FXML } protected void handleAddContactButtonAction(ActionEvent event) { this.contactEditController.prepareAndShowStage(null); @FXML } protected void handleShowHideButtonAction(ActionEvent event) { if(logTarget.isVisible()) { @FXML logTarget.getMinHeight(); protected void handleEditContactButtonAction(ActionEvent event) { logTarget.setMinHeight(-10000); // we assume that selectedItems is always not empty logTarget.setMaxHeight(-10000); List
99 try { } this.smsServiceIsReady.set(this.smsService.start()); } // set localMin via service if not yet configured on // this app's' preferences protected synchronized void hideActiveConnectionControls() { if(this.localMin == null) { if(this.activeContact.get() != null) { this.localMin = this.smsService.getLocalMin(); String prefix = "#" + this.activeContact.get(); } this.contactList.lookup(prefix+"StatusLabel").setVisible(false); } this.contactList.lookup(prefix+"ProgressBar").setVisible(false); catch(SmsServiceException e) { this.contactList.lookup(prefix+"CancelButton").setVisible(false); // er must always be type of ExtendedReason } ExtendedReason er = (ExtendedReason)e.getReason(); } // update error dialog box with details from the exception's // reason's properties protected void cleanupTransmission() { this.errorNotification.setPrimaryText(er.getMessage()); if(this.currentFacade != null) { this.errorNotification.setSecondaryText(er.getDetails()); this.currentFacade = null; // "destroy" currentFacade this.smsServiceIsReady.set(false); // set as not ready this.hideActiveConnectionControls(); // finally, some UI cleanups } this.activeContact.set(null); // reset activeContact to null } if(this.smsService != null) { // reset peer to null, if applicable this.smsService.setPeer(null); protected boolean startAcceptingConnections() { } if(!(this.smsServiceIsReady.get()) || currentFacade != null) { return false; if(this.acCDL != null) { } this.acCDL.countDown(); } // initialize smsftp connection and session objects Connection recvConn = // listen again if set to accept connections automatically Connection.createResponderConnection(this.localMin); if(this.isPostponingAccepting.get()) { SmsftpSession recvSession = // reset flag for use next time SmsftpSession.generateReceiverSession(recvConn); this.isPostponingAccepting.set(false); this.currentFacade = new SmsftpFacade(recvSession, } this.smsService); else { // if not set to postpone accepting // add presenters if(this.isAccepting.get()) { this.currentFacade.addObserver( this.mainLogger.info("Listening for connections again"); new ReceiverPresenter(this.currentFacade, this)); this.startAcceptingConnections(); this.currentFacade.addObserver( } new SmsftpLoggerPresenter(this.protocolLogger)); } } // run the receive method in the background } this.pool.submit(new Task
100 } if(str.length() == 0) { // needed to accomodate blank strings return ""; } // check for seemingly valid cases, but still need to fail // check for sole "+" cases ------if((str.length() == 1 && str.charAt(0) == '+') || Filename: src/main/java/com/transmisms/ui/javafx/MappedStringConverter.java // check for "++..." cases ------(str.length() > 1 && str.charAt(0) == str.charAt(1) && package com.transmisms.ui.javafx; str.charAt(0) == '+')) { throw new IllegalArgumentException(); import javafx.beans.property.SimpleObjectProperty; } import javafx.util.StringConverter; // check if it is a parseable number Long l = this.lsc.fromString(str); import java.util.Map; if(l == null || l.longValue() < 0) { // check for negative cases as well throw new IllegalArgumentException(); } /** return str; * StringConverter implementation for custom mapped String values. } */ } public class MappedStringConverter extends StringConverter
------Filename: src/main/java/com/transmisms/ui/javafx/NonemptyStringConverter.java @Override ------public String fromString(String value) { return value; package com.transmisms.ui.javafx; } import javafx.util.converter.DefaultStringConverter; @Override public String toString(String object) { Map
/** * StringConverter implementation for MSISDN values. /** */ * StringConverter implementation for port number values. public class MsisdnStringConverter extends DefaultStringConverter { */ private final LongStringConverter lsc = new LongStringConverter(); public class PortStringConverter extends PositiveIntegerStringConverter { private int prevValue = 0; @Override public String fromString(String str) { if(str == null) { return ""; @Override } public Integer fromString(String value) { return this.normalize(str); return this.normalize(super.fromString(value)); } }
@Override @Override public String toString(String value) { public String toString(Integer value) { if(value == null) { if(value == null) { return ""; return super.toString(this.normalize(this.prevValue)); } } return value; return super.toString(this.normalize(value)); } }
private String normalize(String str) { private int normalize(int val) {
101 if(val >= 0 && val < 65536) { @FXML this.prevValue = val; private ComboBox backendDummyRole; return val; @FXML } private TextField backendAndroidHost; else { @FXML return this.prevValue; private TextField backendAndroidPort; } @FXML } private TextField backendGammuHost; } @FXML private TextField backendGammuPort; @FXML private TextField backendGammuName; @FXML private TextField backendGammuUser; ------@FXML Filename: private TextField backendGammuPassword; src/main/java/com/transmisms/ui/javafx/PositiveIntegerStringConverter.java @FXML ------private TextField backendGammuLocalmin; package com.transmisms.ui.javafx; private final DirectoryChooser downloadLocationDirectoryChooser = new DirectoryChooser(); import javafx.util.converter.IntegerStringConverter; private MainWindowController primaryController; private Map
/** * StringConverter implementation for positive Integer (and int primitive) * values. public void initialize(Map
@Override // set listeners for controls public String toString(Integer value) { return super.toString(Math.abs(value)); // initialize misc properties } this.downloadLocationDirectoryChooser.setTitle( } "Save received files to"); this.downloadLocation.end(); // move caret to end // set directory chooser's initialDirectory this.downloadLocationDirectoryChooser.setInitialDirectory( new File((String)(configRoot.get( ConfigManager.DOWNLOADLOC_KEY)))); ------this.refreshBackendSettingsLabel(null); Filename: src/main/java/com/transmisms/ui/javafx/PrefsController.java } ------public void loadConfig(Map
102 this.backendGammuHost.setText( sanitizeAndGetText(this.backendDummyLocalmin)); (String)(backendGammuPrefsRoot.get( backendDummyPrefsRoot.put(ConfigManager.BACKEND_DUMMY_ROLE_KEY, ConfigManager.BACKEND_GAMMUPSQL_HOST_KEY))); this.backendDummyRole.getValue()); this.backendGammuPort.setText( ((Integer)(backendGammuPrefsRoot.get( // gammu backend settings ConfigManager.BACKEND_GAMMUPSQL_PORT_KEY))).toString()); backendGammuPrefsRoot.put(ConfigManager.BACKEND_GAMMUPSQL_HOST_KEY, this.backendGammuName.setText( sanitizeAndGetText(this.backendGammuHost)); (String)(backendGammuPrefsRoot.get( backendGammuPrefsRoot.put(ConfigManager.BACKEND_GAMMUPSQL_PORT_KEY, ConfigManager.BACKEND_GAMMUPSQL_NAME_KEY))); Integer.decode(sanitizeAndGetText(this.backendGammuPort))); this.backendGammuUser.setText( backendGammuPrefsRoot.put(ConfigManager.BACKEND_GAMMUPSQL_NAME_KEY, (String)(backendGammuPrefsRoot.get( sanitizeAndGetText(this.backendGammuName)); ConfigManager.BACKEND_GAMMUPSQL_USER_KEY))); backendGammuPrefsRoot.put(ConfigManager.BACKEND_GAMMUPSQL_USER_KEY, this.backendGammuPassword.setText( sanitizeAndGetText(this.backendGammuUser)); (String)(backendGammuPrefsRoot.get( backendGammuPrefsRoot.put(ConfigManager.BACKEND_GAMMUPSQL_PASS_KEY, ConfigManager.BACKEND_GAMMUPSQL_PASS_KEY))); sanitizeAndGetText(this.backendGammuPassword)); this.backendGammuLocalmin.setText( backendGammuPrefsRoot.put( (String)(backendGammuPrefsRoot.get( ConfigManager.BACKEND_GAMMUPSQL_LOCALMIN_KEY, ConfigManager.BACKEND_GAMMUPSQL_LOCALMIN_KEY))); sanitizeAndGetText(this.backendGammuLocalmin));
// android backend settings // android backend settings this.backendAndroidHost.setText( backendAndroidPrefsRoot.put(ConfigManager.BACKEND_ANDROID_HOST_KEY, (String)(backendAndroidPrefsRoot.get( sanitizeAndGetText(this.backendAndroidHost)); ConfigManager.BACKEND_ANDROID_HOST_KEY))); backendAndroidPrefsRoot.put(ConfigManager.BACKEND_ANDROID_PORT_KEY, this.backendAndroidPort.setText( Integer.decode( ((Integer)(backendAndroidPrefsRoot.get( sanitizeAndGetText(this.backendAndroidPort))); ConfigManager.BACKEND_ANDROID_PORT_KEY))).toString()); } // finally write to file and save curConfig public void handleDownloadLocationSelectorButton(ActionEvent event) { ConfigManager.writeConfig(configRoot); // write to file File newDownloadLocDir = // compare changes and apply changes ASAP if needed this.downloadLocationDirectoryChooser.showDialog( boolean smsServiceChanged = !this.prevConfig.get( ((Node)event.getTarget()).getScene().getWindow()); ConfigManager.BACKENDTYPE_KEY).equals( /* configRoot.get(ConfigManager.BACKENDTYPE_KEY)); this.downloadLocationDirectoryChooser.showDialog( if(!smsServiceChanged) { this.prefsStage); // check for changes in the currently selected */ // SmsService's settings Map if(newDownloadLocDir != null) { String backendKey = "backend-" + // change downloadLocation.text afterwards configRoot.get(ConfigManager.BACKENDTYPE_KEY); try { Map
Map
103 import java.io.Serializable; import com.google.gson.Gson; import java.util.Queue; import org.zeromq.ZMQ; import java.util.concurrent.BlockingQueue; import org.zeromq.ZMQ.Context; import java.util.concurrent.LinkedBlockingQueue; import org.zeromq.ZMQ.Poller; import java.util.concurrent.TimeUnit; import org.zeromq.ZMQ.Socket;
import java.util.Date; import java.util.Queue; @Plugin(name="QueueAppender", category="Core", elementType="appender", import java.util.concurrent.BlockingQueue; printObject=true) import java.util.concurrent.ConcurrentLinkedQueue; public class QueueAppender extends AbstractAppender { import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; private static BlockingQueue
public static String pop() throws InterruptedException { import com.transmisms.core.protocol.SmsServiceException; return QueueAppender.queue.take(); import zmq.ZError; }
public static String poll() throws InterruptedException { return QueueAppender.queue.poll(0l, TimeUnit.MILLISECONDS); public class AndroidSmsService extends ExtendedSmsService { } public enum Reason implements SmsServiceException.Reason
@Override public void append(LogEvent event) { private final String message; try { private final String details; QueueAppender.queue.offer(new String(this.getLayout().toByteArray( event)), 1000l, TimeUnit.MILLISECONDS); } // default access control since enums are restricted catch(InterruptedException e) { Reason(String message, String details) { return; // do nothing and exit ASAP this.message = message; } this.details = details; catch(Exception ex) { } if (!ignoreExceptions()) { throw new AppenderLoggingException(ex); } @Override } public Reason valueOf() { } return valueOf(this.name()); } @PluginFactory public static QueueAppender createAppender( @Override @PluginAttribute("name") String name, public final String getMessage() { @PluginElement("Filter") final Filter filter, return this.message; @PluginElement("Layout") Layout layout, } @PluginAttribute("ignoreExceptions") boolean ignoreExceptions, @Override @PluginAttribute("otherAttribute") String otherAttribute) { public final String getDetails() { // we basically do what other implementations of return this.details; // @PluginFactory createAppender() do: some checks and behave as } } // a normal factory method if (name == null) { LOGGER.error("No name provided for QueueAppender"); return null; /** } * Copied from com.transimsms.androidcompanion.ClientAction
. if (layout == null) { * This must be always up to date to the Transmisms Android Companion's layout = PatternLayout.createDefaultLayout(); * ClientAction class. } */ return new QueueAppender(name, filter, layout, ignoreExceptions); public static class ClientAction { } final public String action;
@PluginFactory // send public static QueueAppender createAppender( final public String recipient; @PluginAttribute("name") String name, final public String message; @PluginElement("Filter") final Filter filter, @PluginElement("Layout") Layout layout, // ping @PluginAttribute("otherAttribute") String otherAttribute) { // msisdn return QueueAppender.createAppender(name, filter, layout, true, // NOTHING FOLLOWS otherAttribute); } public ClientAction() { this.action = null; } this.recipient = null; this.message = null; }
// send ------public ClientAction(String recipient, String message) { Filename: this.action = "send"; src/main/java/com/transmisms/ui/javafx/smsservice/AndroidSmsService.java this.recipient = recipient; ------this.message = message; } package com.transmisms.ui.javafx.smsservice; // msisdn import com.transmisms.core.protocol.SmsEntry; public ClientAction(String s) { import com.transmisms.core.protocol.SmsService; this.action = "msisdn"; this.recipient = null;
104 this.message = null; private String peerMin = null; } private String localMin = null;
// ping private final Queuecom.transimsms.androidcompanion.ServerAction
. private CountDownLatch cdl = new CountDownLatch(1); * This must be always up to date to the Transmisms Android Companion's * ServerAction class. */ public static class ServerAction { public AndroidSmsService(String host, int port) { final public String action; this.zmqAddress = "tcp://" + host + ":" + port; } // recv final public String sender; final public String message; @Override final public Long timestamp; public String getLocalMin() { this.sendMsisdnRequest(); // error if(this.cdl != null) { final public String code; try { final public String[] alternativecodes; // optional this.cdl.await(); } // pong catch(InterruptedException e) {} // do nothing on interruption // NOTHING FOLLOWS this.cdl = null; } public ServerAction() { return this.localMin; this.action = null; } this.code = null; this.alternativecodes = null; @Override this.sender = null; public void setPeer(String peerMin) { this.message = null; super.setPeer(peerMin); this.timestamp = null; this.peerMin = peerMin; } }
// recv @Override public ServerAction(String sender, public String getPeer() { String message, long timestamp) { return this.peerMin; this.action = "recv"; } this.sender = sender; this.message = message; @Override this.timestamp = timestamp; protected void sendMessageToBackend(String message) { this.code = null; if(this.isStarted && this.peerMin != null) { this.alternativecodes = null; this.sendQueue.offer(message); } } } // error public ServerAction(String code, String[] alternativecodes) { @Override this.action = "error"; public SmsEntry pollMessageFromBackend(long timeout, TimeUnit unit) this.code = code; throws SmsServiceException, InterruptedException { this.alternativecodes = alternativecodes; // throw exceptions generated from our polling loop, if any this.sender = null; SmsServiceException e = this.currentException.get(); this.message = null; if(e != null) { this.timestamp = null; this.currentException.set(null); // clear exception } throw e; } // msisdn // for normal cases public ServerAction(String msisdn) { if(this.isStarted) { this.action = "msisdn"; return this.recvQueue.poll(timeout, unit); this.message = msisdn; } // no fields return null; this.code = null; } this.alternativecodes = null; this.sender = null; @Override this.timestamp = null; public boolean start() throws SmsServiceException { } super.start(); if(!this.isStarted) { // pong final CountDownLatch cdl = new CountDownLatch(1); public ServerAction(char c) { AtomicReference
105 String jsonStr = AndroidSmsService.this.gson.toJson( sAction.message, new ClientAction('p')); // PING ClientAction new Date(sAction.timestamp))); zmqSocket.send(jsonStr); } else if(sAction.action.equals("msisdn")) { // attempt getting a PONG response for LIMIT times AndroidSmsService.this.localMin = int LIMIT = 3; sAction.message; for(int i = 0; i < LIMIT; i++) { if(AndroidSmsService.this.cdl != null) { Poller plr = zmqContext.poller(); AndroidSmsService.this.cdl.countDown(); plr.register(zmqSocket, Poller.POLLIN); } try { } plr.poll(PING_TIMEOUT); // wait for PING_TIMEOUT ms else if(sAction.action.equals("error")) { } } catch(ZError.IOException e) { } System.out.println("got ZError.IOException " + plr.close(); "on connectivity test: " + e); } ses.set(new SmsServiceException( Reason.IOERROR)); // finally, close everything afterwards } try { if(plr.pollin(0)) { if(zmqSocket != null) { String s = zmqSocket.recvStr(0); zmqSocket.close(); ServerAction sAction = } AndroidSmsService.this.gson.fromJson(s, } ServerAction.class); catch(ZError.IOException e) { if(sAction != null && sAction.action != null & & System.out.println("got ZError.IOException on " + sAction.action.equals("pong")) { "closing zmqSocket: " + e); plr.close(); // preemptive close before exiting } AndroidSmsService.this.isStarted = true; try { break; // exit ASAP if(!zmqContext.isTerminated()) { } zmqContext.term(); // otherwise, discard received data and try again } else { } // 'infinite' retries/exhaustion of entries catch(ZError.IOException e) { i--; System.out.println("got ZError.IOException on " + } "closing zmqContext: " + e); } } plr.close(); // because we need more close() // set other variables as well } AndroidSmsService.this.isStarted = false; if(!AndroidSmsService.this.isStarted && } ses.get() == null) { }).start(); ses.set(new SmsServiceException(Reason.NOPONG)); try { } cdl.await(); cdl.countDown(); // ready for examining our isStarted flag } catch(InterruptedException e) { // loop until isStarted flag is revoked return false; while(AndroidSmsService.this.isStarted) { } // send all queued commands if(!this.isStarted) { for(String message = // do cleanup and throw exception AndroidSmsService.this.commandQueue.poll(); this.sendQueue.clear(); message != null; this.recvQueue.clear(); message = throw ses.get(); AndroidSmsService.this.commandQueue.poll()) { } zmqSocket.send(message); return this.isStarted; } } // send all pending messages return false; for(String message = } AndroidSmsService.this.sendQueue.poll(); message != null; message = @Override AndroidSmsService.this.sendQueue.poll()) { public boolean stop() { ClientAction cAction = new ClientAction( super.stop(); AndroidSmsService.this.peerMin, message); if(this.isStarted) { String str = AndroidSmsService.this.gson.toJson( this.isStarted = false; cAction, ClientAction.class); // clear sendQueue and recvQueue zmqSocket.send(str); this.sendQueue.clear(); } this.recvQueue.clear(); // poll for the time being } Poller plr = zmqContext.poller(); return false; plr.register(zmqSocket, Poller.POLLIN); } try { plr.poll(100); // poll for 100ms @Override } public boolean isRunning() { catch(ZError.IOException e) { return this.isStarted; System.out.println("got ZError.IOException: " + e); } AndroidSmsService.this.stop(); // stop ASAP // then set currentException to be thrown on private void sendMsisdnRequest() { // pollMessage()/pollMessageFromBackend() later if(this.localMin == null) { AndroidSmsService.this.currentException.set( ClientAction cAction = new ClientAction("msisdn"); new SmsServiceException(Reason.IOERROR)); String str = this.gson.toJson(cAction, ClientAction.class); break; this.commandQueue.offer(str); } } if(plr.pollin(0)) { } String str = zmqSocket.recvStr(0); } ServerAction sAction = AndroidSmsService.this.gson.fromJson(str, ServerAction.class); // check for errors first if(sAction.action == null) { ------continue; // do nothing and loop back Filename: } src/main/java/com/transmisms/ui/javafx/smsservice/ControlPrioritySmsService.java if(sAction.action.equals("recv")) { ------AndroidSmsService.this.recvQueue.offer( new SmsEntry(sAction.sender, package com.transmisms.ui.javafx.smsservice;
106 } import com.transmisms.core.protocol.SmsEntry; import com.transmisms.core.protocol.SmsService; public ControlPrioritySmsService() { import com.transmisms.core.protocol.SmsServiceException; this.loopTimeout = 5000; import com.transmisms.core.util.commons.FIFOEntry; } import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.PriorityBlockingQueue; @Override import java.util.concurrent.TimeUnit; public final void sendMessage(String message) { if(this.isStarted) { if(StringWrapper.getWeight(message) > 10) { this.pbq.offer(new FIFOEntry(new StringWrapper(message))); public abstract class ControlPrioritySmsService implements SmsService { } private static class StringWrapper implements Comparable
@Override public abstract boolean isRunning(); protected abstract void sendMessageToBackend(String message); }
public abstract String getLocalMin();
public abstract void setPeer(String peerMin); ------public abstract String getPeer(); Filename: src/main/java/com/transmisms/ui/javafx/smsservice/DummySmsService.java ------
package com.transmisms.ui.javafx.smsservice; public ControlPrioritySmsService(int loopTimeout) { this.loopTimeout = loopTimeout; import com.transmisms.core.protocol.SmsEntry;
107 import com.transmisms.core.protocol.SmsService; @Override public void setPeer(String peerMin) { import org.zeromq.ZMQ; super.setPeer(peerMin); import org.zeromq.ZMQ.Context; this.peerMin = peerMin; import org.zeromq.ZMQ.Poller; } import org.zeromq.ZMQ.Socket; @Override import java.util.Date; public String getPeer() { import java.util.Queue; return this.peerMin; import java.util.concurrent.BlockingQueue; } import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; @Override import java.util.concurrent.LinkedBlockingQueue; protected void sendMessageToBackend(String message) { import java.util.concurrent.TimeUnit; if(this.isStarted && this.peerMin != null) { import java.util.concurrent.atomic.AtomicReference; this.sendQueue.offer(this.getLocalMin() + ":" + message); } import com.transmisms.core.protocol.SmsServiceException; } import zmq.ZError; @Override public SmsEntry pollMessageFromBackend(long timeout, TimeUnit unit) throws SmsServiceException, InterruptedException { public class DummySmsService extends ExtendedSmsService { // throw exceptions generated from our polling loop, if any public enum Reason implements SmsServiceException.Reason
@Override // default access control since enums are restricted public boolean start() throws SmsServiceException { Reason(String message, String details) { super.start(); this.message = message; if(!this.isStarted) { this.details = details; final CountDownLatch cdl = new CountDownLatch(1); } AtomicReference
public final static int PING_TIMEOUT = 1000; DummySmsService.this.isStarted = false; // NOTE: all messages prior to 'starting' this service private final String zmqAddress; // are DISCARDED private final boolean isSender; if(DummySmsService.this.isSender) { private volatile boolean isStarted = false; // PING receiver and wait for PONG private String peerMin = null; zmqSocket.send("PING"); private String localMin = null; // attempt getting a PONG response for LIMIT times private final Queue
108 } @Override plr.close(); // because we need more close() public boolean stop() { } super.stop(); } if(this.isStarted) { cdl.countDown(); // ready for examining our isStarted flag this.isStarted = false; // clear sendQueue and recvQueue // loop until isStarted flag is revoked this.sendQueue.clear(); while(DummySmsService.this.isStarted) { this.recvQueue.clear(); // send all pending messages } for(String message = return false; DummySmsService.this.sendQueue.poll(); } message != null; message = DummySmsService.this.sendQueue.poll()) { @Override zmqSocket.send(message); public boolean isRunning() { } return this.isStarted; // poll for the time being } Poller plr = zmqContext.poller(); } plr.register(zmqSocket, Poller.POLLIN); try { plr.poll(100); // poll for 100ms } catch(ZError.IOException e) { ------System.out.println("got ZError.IOException: " Filename:+ e); DummySmsService.this.stop(); // stop ASAP src/main/java/com/transmisms/ui/javafx/smsservice/ExtendedSmsService.java // then set currentException to be thrown on ------// pollMessage()/pollMessageFromBackend() later DummySmsService.this.currentException.set( package com.transmisms.ui.javafx.smsservice; new SmsServiceException(Reason.IOERROR)); break; import com.transmisms.core.protocol.SmsEntry; } import com.transmisms.ui.javafx.Utils; if(plr.pollin(0)) { // process PING/PONG requests first import java.util.concurrent.TimeUnit; String raw = zmqSocket.recvStr(0); if(raw.equals("PING")) { // reply with PONG import com.transmisms.core.protocol.SmsServiceException; zmqSocket.send("PONG"); continue; } else if(raw.equals("PONG")) { public abstract class ExtendedSmsService extends ControlPrioritySmsService { // just ignore and try again public interface ExtendedReason { continue; public String getMessage(); } public String getDetails(); String[] parts = raw.split(":", 2); } String sender = parts[0]; String msg = parts[1]; private String countryCode = null; plr.close(); private String normalizedPeer = null; DummySmsService.this.recvQueue.offer( new SmsEntry(sender, msg, new Date())); } plr.close(); public ExtendedSmsService() {} } public ExtendedSmsService(int loopTimeout) { // finally, close everything afterwards super(loopTimeout); try { } if(zmqSocket != null) { zmqSocket.close(); } } public final SmsEntry pollMessage(long timeout, TimeUnit unit) catch(ZError.IOException e) { throws SmsServiceException, InterruptedException { System.out.println("got ZError.IOException on " + SmsEntry entry = this.pollMessageFromBackend(timeout, unit); "closing zmqSocket: " + e); // set as the peer if there's still no peer set previously } if(entry != null && this.getPeer() == null) { try { if(this.countryCode == null) { if(!zmqContext.isTerminated()) { this.setPeer(entry.getSender()); zmqContext.term(); } } else { } this.setPeer(Utils.getIntFormat(entry.getSender(), catch(ZError.IOException e) { this.countryCode)); System.out.println("got ZError.IOException on " + } "closing zmqContext: " + e); } } if(entry != null && this.getPeer() != null) { // set other variables as well // return null on non-peer senders DummySmsService.this.isStarted = false; if(!entry.getSender().endsWith(this.normalizedPeer)) { } return null; }).start(); } try { } cdl.await(); return entry; } } catch(InterruptedException e) { return false; public abstract SmsEntry pollMessageFromBackend(long timeout, } TimeUnit unit) if(!this.isStarted) { throws SmsServiceException, InterruptedException; // do cleanup and throw exception this.sendQueue.clear(); public void setCountryCode(String countryCode) { this.recvQueue.clear(); this.countryCode = countryCode; throw ses.get(); } } return this.isStarted; public void setPeer(String peer) { } if(peer != null) { return false; this.normalizedPeer = Utils.normalizeMsisdn(peer, } this.countryCode); }
109 else { "SELECT * FROM phones WHERE \"TimeOut\" > " + this.normalizedPeer = null; "(CURRENT_TIMESTAMP - INTERVAL '2 MINUTE')"; } private static final String insertQueryStr = } "INSERT INTO outbox (\"TextDecoded\", \"DestinationNumber\", " + } "\"CreatorID\", \"Priority\") VALUES (?, ?, ?, ?)"; private static final String getCurtimestampQueryStr = "SELECT CURRENT_TIMESTAMP AT TIME ZONE current_setting('TimeZone')"; public final static int PING_TIMEOUT = 3000;
------private boolean isStarted = false; Filename: src/main/java/com/transmisms/ui/javafx/smsservice/GammuSmsService.java private Timestamp startTime; ------private String peerMin = null; private int lastMessageId = -1; package com.transmisms.ui.javafx.smsservice; private final String dbHost; private final int dbPort; import com.transmisms.core.protocol.SmsEntry; private final String dbName; import com.transmisms.core.protocol.SmsService; private final String dbUser; private final String dbPassword; import java.sql.Connection; private final String localMin; import java.sql.Timestamp; import java.sql.DriverManager; private Connection dbConnection = null; import java.sql.PreparedStatement; private PreparedStatement messageQuery = null; import java.sql.ResultSet; private PreparedStatement statusQuery = null; import java.sql.Statement; private PreparedStatement insertQuery = null; import java.util.Queue; private PreparedStatement getCurtimestampQuery = null; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executors; private final Queue
110 e.printStackTrace(); if(e != null && e.getCause() != null) { return; e.getCause().printStackTrace(); } } } } } catch(CancellationException e) { if(e != null && e.getCause() != null) { @Override e.getCause().printStackTrace(); public SmsEntry pollMessageFromBackend(long timeout, TimeUnit unit) } throws SmsServiceException, InterruptedException { } if(this.isStarted) { finally { SmsEntry entry = null; this.queryFuture = null; // try to get a pending entry first } entry = this.entryQueue.poll(); return entry; if(entry != null) { } return entry; return null; // just return null otherwise (or on timeout/error) } }
// create a separate task for querying @Override this.queryFuture = this.executor.submit(() -> { public synchronized boolean start() throws SmsServiceException { while(!Thread.interrupted()) { super.start(); ResultSet rs = null; if(!this.isStarted) { boolean hasResults = false; // start the connection String sender = null; String jdbcUrl = "jdbc:postgresql://" + this.dbHost + ":" String message = null; + this.dbPort + "/" + this.dbName; Timestamp timestamp = null; ResultSet rs = null; int messageId = -1; try { try { // prepare connection this.messageQuery.setInt(1, this.lastMessageId); this.dbConnection = DriverManager.getConnection( this.messageQuery.setTimestamp(2, this.startTime); jdbcUrl, this.dbUser, this.dbPassword); if(this.getPeer() != null) { // prepare PreparedStatements this.messageQuery.setString(3, "%"+this.getPeer()); this.messageQuery = this.dbConnection.prepareStatement( } GammuSmsService.messageQueryUnfilteredStr); this.statusQuery = this.dbConnection.prepareStatement( rs = this.messageQuery.executeQuery(); GammuSmsService.statusQueryStr); hasResults = rs.next(); this.insertQuery = this.dbConnection.prepareStatement( if(hasResults) { GammuSmsService.insertQueryStr); sender = rs.getString("SenderNumber"); this.getCurtimestampQuery = this.dbConnection.prepareStatement( message = rs.getString("TextDecoded"); GammuSmsService.getCurtimestampQueryStr); timestamp = rs.getTimestamp("UpdatedInDB"); messageId = rs.getInt("ID"); // get CURRENT_TIMESTAMP and use it for querying messages // update lastMessageId for next queries rs = this.getCurtimestampQuery.executeQuery(); this.lastMessageId = messageId; if(rs.next()) { } this.startTime = rs.getTimestamp(1); } } catch(SQLException e) { e.printStackTrace(); // attempt checking for connected 'phones' within the break; // exit on errors // last 2 minutes for LIMIT times } int LIMIT = 3; finally { for(int i = 0; i < LIMIT; i++) { // clean up first rs = this.statusQuery.executeQuery(); try { if(rs.next()) { // got a 'phone' if(rs != null) { this.isStarted = true; rs.close(); break; } } } // sleep for a while before retrying catch(SQLException e) { try { e.printStackTrace(); Thread.sleep(PING_TIMEOUT); break; // exit on errors } } catch(InterruptedException e) { break; // return if there are results, loop again otherwise } if(hasResults) { } // then return a single entry if(!this.isStarted) { this.entryQueue.offer(new SmsEntry( this.stop(); // stop the service ASAP sender, message, timestamp)); throw new SmsServiceException(Reason.NODEVICE); return null; } } } else { catch(SQLException e) { try { // just sleep for a while and try again this.stop(); // stop the service ASAP Thread.sleep(1000); // sleep for a second throw new SmsServiceException(Reason.NOCONN); } } catch(InterruptedException e) { finally { // try to close everything break; // exit on break try { } if(rs != null) { } rs.close(); } } } } return null; // just return null on timeout/failure catch(SQLException e) { }); e.printStackTrace(); } try { // execute and wait for results until timeout // finally, set the isStarted flag to true this.queryFuture.get(timeout, unit); } entry = this.entryQueue.poll(); return this.isStarted; } } catch(ExecutionException e) { else { if(e != null && e.getCause() != null) { return false; e.getCause().printStackTrace(); } } } } catch(TimeoutException e) { @Override
111 public boolean stop() { e.printStackTrace(); super.stop(); } if(this.isStarted) { // set css for the parent // stop the execution of current queries header.getStylesheets().add("dialog.css"); if(this.queryFuture != null) { // set icon for dialog this.queryFuture.cancel(true); ImageView iv = (ImageView)header.lookup(".dialogIcon"); } iv.setImage(img); try { // close the connection and other related objects // set necessary text properties if(this.dbConnection != null) { this.primaryLabel = (Label)header.lookup(".dialogPrimaryLabel"); this.dbConnection.close(); this.secondaryLabel = (Text)header.lookup(".dialogSecondaryLabel"); } primaryLabel.setText(primaryText); if(this.messageQuery != null) { secondaryLabel.setText(secondaryText); this.messageQuery.close(); // add the header to dialog pane } this.getDialogPane().setHeader(header); if(this.statusQuery != null) { this.statusQuery.close(); // misc modifications for the dialog } this.initStyle(StageStyle.UTILITY); if(this.insertQuery != null) { } this.insertQuery.close(); } } public void setPrimaryText(String text) { catch(SQLException e) { this.primaryLabel.setText(text); e.printStackTrace(); } } finally { public void setSecondaryText(String text) { // then let JVM GC the remnants this.secondaryLabel.setText(text); this.dbConnection = null; } this.messageQuery = null; this.statusQuery = null; public void appendSecondaryText(String text) { this.insertQuery = null; this.secondaryLabel.setText(this.secondaryLabel.getText() + text); this.getCurtimestampQuery = null; } // finally, set the isStarted flag to false } this.isStarted = true; } return true; } else { ------return false; Filename: src/main/java/com/transmisms/ui/javafx/Utils.java } ------} package com.transmisms.ui.javafx; @Override public boolean isRunning() { return this.isStarted; } public class Utils { } public static final int MIN_MIN_LENGTH = 10;
/** * Normalizes MSISDN and other numbers into 10-digit MINs * ------* @param msisdn number to be normalized into 10-digit MINs Filename: src/main/java/com/transmisms/ui/javafx/StandardDialog.java */ ------public static String normalizeMsisdn(String msisdn, String countryCode) { // if carrier-provided access code, return ASAP package com.transmisms.ui.javafx; if(msisdn == null || msisdn.length() < MIN_MIN_LENGTH) { return msisdn; import javafx.scene.Parent; } import javafx.scene.control.ButtonType; import javafx.scene.control.Dialog; String min = msisdn; import javafx.scene.control.Label; // remove leading '+', if any import javafx.scene.image.Image; if(min.startsWith("+")) { import javafx.scene.image.ImageView; min = min.substring(1); import javafx.scene.text.Text; } import javafx.stage.StageStyle; // remove leading zeroes min = min.replaceFirst("^0+(?!$)", ""); import javafx.fxml.FXMLLoader; if(min.startsWith(countryCode)) { min = min.substring(countryCode.length()); import java.io.IOException; } return min; } public class StandardDialog extends Dialog
112 GridPane.rowIndex="6" GridPane.columnIndex="0" />
------Filename: src/main/resources/contact-listcell-template.fxml ------Filename: src/main/resources/contact-edit-window.fxml ------
113
------
114
------.contactNum { Filename: src/main/resources/main.css -fx-alignment: center-left; ------fx-padding: 0 0 0 6; /* set left padding to 6 */ } .root { /* custom colors defined here */ .contactStatus { warning-yellow: #ffe16b; /* elementary OS Banana 300 */ -fx-alignment: center-right; info-blue: #64baff; /* elementary OS Blueberry 300 */ -fx-padding: 0 12 0 0; /* set right padding to 12 */ error-red: #ed5353; /* elementary OS Strawberry 300 */ }
text-black: #0c0c0d; /* Firefox Photon Grey 90 */ .contactNum, .contactStatus { text-grey: #737373; /* Firefox Photon Grey 50 */ -fx-font-size: 11; text-black-90: #0c0c0de6; /* 90% Firefox Photon Grey 90 */ -fx-opacity: 0.5; text-grey-90: #737373e6; /* 90% Firefox Photon Grey 50 */ }
button-black: text-black; .cell:selected .contactNum, .cell:selected .contactStatus { button-border-black: #999999; -fx-opacity: 0.9; button-pressed-bg: #eeeeee; } } .cancelOperationButton { .label { -fx-background-color: transparent; -fx-font-smoothing-type: gray; } -fx-text-fill: text-black-90; } .cancelOperationButton .ikonli-font-icon { -fx-icon-code: "gmi-cancel"; .list-view:focused .cell:selected .label, .menu-item:focused .label, -fx-icon-size: 16px; .cell:focused:selected .choice-box .label { -fx-icon-color: black; -fx-text-fill: white; -fx-opacity: 0.5; } }
.tool-bar .button { .cancelOperationButton .ikonli-font-icon:hover {
115 -fx-opacity: 0.6; -fx-text-fill: text-black; } }
.cancelOperationButton .ikonli-font-icon:pressed { .welcomeActionDesc { -fx-opacity: 0.8; -fx-text-fill: text-grey; } }
.progress-bar { -fx-padding: 0 0 0 6; /* set left padding to 6 */ } ------.progress-bar > .bar { Filename: src/main/resources/prefs-window.css -fx-background-insets: 1 1 2 1; ------fx-padding: 0.20em; } #prefsGrid .label { /*fx-alignment: right;*/ #logTarget { } -fx-font-family: "Courier", monospace; } #backendSettingsLabel { /*-fx-font-size: 22px;*/ .infobar { -fx-font-weight: bold; -fx-padding: 6 12 6 12; } } #prefsErrorText { .infoInfobar { -fx-text-fill: red; -fx-background-color: info-blue; } }
.infobarLabel { -fx-alignment: center-left; -fx-padding: 0 0 0 6; /* set left padding to 6 */ ------} Filename: src/main/resources/prefs-window.fxml ------.infobuttonContainer { -fx-alignment: center-right; } .infobuttonContainer .button { -fx-alignment: center-right; } #errorInfobar { -fx-background-color: error-red; } #welcomePaneRoot { -fx-spacing: 20px; }
#welcomeActions { -fx-alignment: center-left; }
#welcomeTitle, #welcomeSubtitle { -fx-alignment: center;
#welcomeAddContactButton .ikonli-font-icon {
116
117 GridPane.halignment="RIGHT" ------GridPane.rowIndex="10" GridPane.columnIndex="0" />
# keys: # just null/empty for insecure contacts
------Filename: src/main/resources/transmisms-javafx.yaml ------
118 Filename: src/test/integration/java/AdversarialConnectionTest.java // initialize Core objects ------Connection sendConn = Connection.createInitiatorConnection(sendMin, recvMin); /* Connection recvConn = Connection.createResponderConnection(recvMin); // NOTE: just to be sure we have BouncyCastle as a provider Session sendSession = NullSession.generateSenderSession(sendConn); //Security.addProvider(new BouncyCastleProvider()); Session recvSession = NullSession.generateReceiverSession(recvConn); */ NullFacade sendFacade = package com.transmisms.test.integration; new NullFacade(sendSession, sendService); NullFacade recvFacade = import com.transmisms.core.protocol.Connection; new NullFacade(recvSession, recvService); import com.transmisms.core.protocol.Session; sendFacade.addObserver( import com.transmisms.core.protocol.SmsEntry; new SmsftpLoggerPresenter(LogManager.getLogger("PPT-send"))); import com.transmisms.smsftp.temputil.SmsftpLoggerPresenter; recvFacade.addObserver( new SmsftpLoggerPresenter(LogManager.getLogger("PPT-recv"))); import org.apache.logging.log4j.LogManager; sendFacade.addObserver(new AllYesPresenter(sendFacade)); recvFacade.addObserver(new AllYesPresenter(recvFacade)); import static org.testng.Assert.assertNotEquals; // initialize, run, and join Runnables import static org.testng.AssertJUnit.assertEquals; Runnable sendRunnable = () -> { import static org.testng.AssertJUnit.assertTrue; sendFacade.connect(); import static org.testng.AssertJUnit.assertFalse; }; import static org.testng.AssertJUnit.fail; Runnable recvRunnable = () -> { recvFacade.listen(); import org.testng.annotations.Test; }; Utils.runAndJoin(sendRunnable, recvRunnable, 20000, "previousSessionPdusReceivedTest"); public class AdversarialConnectionTest { assertEquals(Connection.ConnectionState.CLOSED, sendConn.getConnectionState()); //// final Object for repeated use across tests assertEquals(Connection.ConnectionState.CLOSED, private static final String sendMin = "09121234567"; recvConn.getConnectionState()); private static final String recvMin = "09129876543"; // sleep for a while to "synchronize" stdout Utils.sleepSilently(10); LogManager.getLogger("PPT").info( @Test "END of previousSessionPdusReceivedTest"); public void previousSessionPdusReceivedTest() { } class ThrowbackSmsService extends DummySmsService { final static String oldMessage = "?transmismsv0.7.0\n" + } "9>#N£W!>W(N'L+GR4Yo)\n" + "last/nullprotocol/0001/OK; Completed without error\n" + "\n" + "To stop receiving messages, reply with NO"; ------final int limit = 1; Filename: src/test/integration/java/AllYesPresenter.java int count = 0; ------
public ThrowbackSmsService(String localMin) { package com.transmisms.test.integration; super(localMin); } import com.transmisms.core.protocol.CoreProtocolFacade;
public ThrowbackSmsService(String localMin, DummySmsService peer) { super(localMin, peer); } public class AllYesPresenter extends NullPresenter { private CoreProtocolFacade coreProtocolFacade; @Override public void sendMessage(String message) { public AllYesPresenter(CoreProtocolFacade coreProtocolFacade) { // don't send any retransmission messages for this test super(coreProtocolFacade); if(message.charAt(0) == 'r') { this.coreProtocolFacade = coreProtocolFacade; return; } } @Override boolean shouldResendOldMessage = this.count == 0; protected void onTimeout() { // default: cancel immediately after timeout if(shouldResendOldMessage) { this.coreProtocolFacade.continueOperationAfterPrompt(); System.out.println("Resending old message..."); } this.peer.insertSmsEntry( } new SmsEntry(this.localMin, this.oldMessage)); } this.peer.insertSmsEntry( new SmsEntry(this.localMin, message)); ------// comment out this block for filtering against DATA PDUs:Filename: src/test/integration/java/AllYesSmsftpPresenter.java if(message.charAt(0) != 'd') { ------System.out.println("Sendto " + this.localMin + ": " + message); package com.transmisms.test.integration; } import com.transmisms.core.protocol.Presenter.PromptType; this.count++; import com.transmisms.smsftp.protocol.SmsftpFacade; if(this.count >= this.limit) { this.count = 0; } } public class AllYesSmsftpPresenter extends NullSmsftpPresenter { } public AllYesSmsftpPresenter(SmsftpFacade smsftpFacade, byte[] enckey, byte[] authkey) { // initialize sms services super(smsftpFacade, enckey, authkey); DummySmsService sendService = new ThrowbackSmsService(sendMin); } DummySmsService recvService = new ThrowbackSmsService( recvMin, sendService); public AllYesSmsftpPresenter(SmsftpFacade smsftpFacade) { sendService.setPeer(recvService); super(smsftpFacade); }
119 recvMin, sendService); sendService.setPeer(recvService);
@Override // initialize Core objects protected void onUserPrompt(PromptType pType, String message) { Connection sendConn = if(pType == PromptType.TIMEOUT) { Connection.createInitiatorConnection(sendMin, recvMin); this.smsftpFacade.continueOperationAfterPrompt(); Connection recvConn = Connection.createResponderConnection(recvMin); } Session sendSession = NullSession.generateSenderSession(sendConn); else { Session recvSession = NullSession.generateReceiverSession(recvConn); super.onUserPrompt(pType, message); NullFacade sendFacade = } new NullFacade(sendSession, sendService); } NullFacade recvFacade = } new NullFacade(recvSession, recvService); sendFacade.addObserver( new SmsftpLoggerPresenter(LogManager.getLogger("CDT-send"))); recvFacade.addObserver( new SmsftpLoggerPresenter(LogManager.getLogger("CDT-recv"))); ------Filename: src/test/integration/java/BlackholeOutgoingSmsService.java // initialize, run, and join Runnables ------Runnable sendRunnable = () -> { sendFacade.connect(); package com.transmisms.test.integration; }; Runnable recvRunnable = () -> { import com.transmisms.core.protocol.SmsServiceException; recvFacade.listen(); }; Utils.runAndJoin(sendRunnable, recvRunnable, 10000, "connectDisconnectTest"); public class BlackholeOutgoingSmsService extends DummySmsService { assertEquals(Connection.ConnectionState.CLOSED, public BlackholeOutgoingSmsService(String localMin) { sendConn.getConnectionState()); super(localMin); assertEquals(Connection.ConnectionState.CLOSED, } recvConn.getConnectionState());
public BlackholeOutgoingSmsService(String localMin, DummySmsService peer) { // sleep for a while to "synchronize" stdout super(localMin, peer); Utils.sleepSilently(10); } LogManager.getLogger("CDT").info("END of connectDisconnectTest"); } @Override public void sendMessage(String message) { @Test // do nothing; discard all messages to be sent public void timeoutShouldTriggerReactionTest() { // initialize sms services // NOTE: you can comment this out for debugging: DummySmsService sendService = new DummySmsService(sendMin); //System.out.println("Nosend: " + this.localMin + ": " + message); DummySmsService recvService = // or this code block for filtering against DATA PDUs: new BlackholeOutgoingSmsService(recvMin, sendService); } sendService.setPeer(recvService); } // initialize Core objects Connection sendConn = Connection.createInitiatorConnection(sendMin, recvMin); Connection recvConn = ------Connection.createResponderConnection(recvMin); Filename: src/test/integration/java/ConnectionTest.java Session sendSession = NullSession.generateSenderSession(sendConn); ------Session recvSession = NullSession.generateReceiverSession(recvConn); NullFacade sendFacade = /* new NullFacade(sendSession, sendService); // NOTE: just to be sure we have BouncyCastle as a provider NullFacade recvFacade = //Security.addProvider(new BouncyCastleProvider()); new NullFacade(recvSession, recvService); */ sendFacade.addObserver( package com.transmisms.test.integration; new SmsftpLoggerPresenter(LogManager.getLogger("TOT-send"))); recvFacade.addObserver( import com.transmisms.core.protocol.Connection; new SmsftpLoggerPresenter(LogManager.getLogger("TOT-recv"))); import com.transmisms.core.protocol.Session; sendFacade.addObserver(new NullPresenter(sendFacade)); import com.transmisms.core.protocol.SmsEntry; recvFacade.addObserver(new NullPresenter(recvFacade)); import com.transmisms.smsftp.temputil.SmsftpLoggerPresenter; // initialize, run, and join Runnables import org.apache.logging.log4j.LogManager; Runnable sendRunnable = () -> { sendFacade.connect(); }; import static org.testng.Assert.assertNotEquals; Runnable recvRunnable = () -> { import static org.testng.AssertJUnit.assertEquals; recvFacade.listen(); import static org.testng.AssertJUnit.assertTrue; }; import static org.testng.AssertJUnit.assertFalse; Utils.runAndJoin(sendRunnable, recvRunnable, 8000, import static org.testng.AssertJUnit.fail; "timeoutShouldTriggerReactionTest"); import org.testng.annotations.Test; assertEquals(Connection.ConnectionState.CLOSED, recvConn.getConnectionState()); assertEquals(Connection.ConnectionState.CLOSED, sendConn.getConnectionState()); public class ConnectionTest { // sleep for a while to "synchronize" stdout //// final Object for repeated use across tests Utils.sleepSilently(10); private static final String sendMin = "09121234567"; LogManager.getLogger("TOT") private static final String recvMin = "09129876543"; .info("END of timeoutShouldTriggerReactionTest"); }
@Test @Test public void userContinueTimeoutShouldContinue() { public void connectDisconnectTest() { class UnreliableSmsService extends DummySmsService { // initialize sms services final int limit = 3; DummySmsService sendService = new DummySmsService(sendMin); int count = 0; DummySmsService recvService = new DummySmsService(
120 public UnreliableSmsService(String localMin) { // NOTE: just to be sure we have BouncyCastle as a provider super(localMin); //Security.addProvider(new BouncyCastleProvider()); } */ package com.transmisms.test.integration; public UnreliableSmsService(String localMin, DummySmsService peer) { super(localMin, peer); import com.transmisms.core.protocol.Connection; } import com.transmisms.core.protocol.Session; import com.transmisms.core.protocol.SmsEntry; @Override import com.transmisms.smsftp.temputil.SmsftpLoggerPresenter; public void sendMessage(String message) { // don't send any retransmission messages for this test import org.apache.logging.log4j.LogManager; if(message.charAt(0) == 'r') { return; } import static org.testng.Assert.assertNotEquals; import static org.testng.AssertJUnit.assertEquals; boolean isReliable = this.count != 0; import static org.testng.AssertJUnit.assertTrue; import static org.testng.AssertJUnit.assertFalse; if(isReliable) { import static org.testng.AssertJUnit.fail; this.peer.insertSmsEntry( new SmsEntry(this.localMin, message)); import org.testng.annotations.Test; }
// comment out this block for filtering against DATA PDUs: /* public class DelayedConnectionTest { if(message.charAt(0) != 'd') { System.out.println((isReliable ? "Sendto: " : //// final Object for repeated use across tests "Nosend: ") + this.localMin + ": " + message); private static final String sendMin = "09121234567"; } private static final String recvMin = "09129876543"; */
this.count++; if(this.count >= this.limit) { @Test this.count = 0; public void delayedDeliveryTest() { } class SlowSmsService extends DummySmsService { } } public SlowSmsService(String localMin) { super(localMin); // initialize sms services } DummySmsService sendService = new UnreliableSmsService(sendMin); DummySmsService recvService = public SlowSmsService(String localMin, DummySmsService peer) { new UnreliableSmsService(recvMin, sendService); super(localMin, peer); sendService.setPeer(recvService); }
// initialize Core objects @Override Connection sendConn = public void sendMessage(String message) { Connection.createInitiatorConnection(sendMin, recvMin); Thread t = new Thread(() -> { Connection recvConn = try { Connection.createResponderConnection(recvMin); Thread.sleep(2000); Session sendSession = NullSession.generateSenderSession(sendConn); } Session recvSession = NullSession.generateReceiverSession(recvConn); catch(InterruptedException e) { NullFacade sendFacade = return; // exit ASAP new NullFacade(sendSession, sendService); } NullFacade recvFacade = this.peer.insertSmsEntry( new NullFacade(recvSession, recvService); new SmsEntry(this.localMin, message)); sendFacade.addObserver( }); new SmsftpLoggerPresenter(LogManager.getLogger("TCT-send"))); t.start(); recvFacade.addObserver( } new SmsftpLoggerPresenter(LogManager.getLogger("TCT-recv"))); } sendFacade.addObserver(new AllYesPresenter(sendFacade)); recvFacade.addObserver(new AllYesPresenter(recvFacade)); // initialize sms services DummySmsService sendService = new SlowSmsService(sendMin); // initialize, run, and join Runnables DummySmsService recvService = new SlowSmsService( Runnable sendRunnable = () -> { recvMin, sendService); sendFacade.connect(); sendService.setPeer(recvService); }; Runnable recvRunnable = () -> { // initialize Core objects recvFacade.listen(); Connection sendConn = }; Connection.createInitiatorConnection(sendMin, recvMin); Utils.runAndJoin(sendRunnable, recvRunnable, 30000, Connection recvConn = Connection.createResponderConnection(recvMin); "userContinueTimeoutShouldContinue"); Session sendSession = NullSession.generateSenderSession(sendConn); Session recvSession = NullSession.generateReceiverSession(recvConn); assertEquals(Connection.ConnectionState.CLOSED, NullFacade sendFacade = recvConn.getConnectionState()); new NullFacade(sendSession, sendService); assertEquals(Connection.ConnectionState.CLOSED, NullFacade recvFacade = sendConn.getConnectionState()); new NullFacade(recvSession, recvService); sendFacade.addObserver( // sleep for a while to "synchronize" stdout new SmsftpLoggerPresenter(LogManager.getLogger("DDT-send"))); Utils.sleepSilently(10); recvFacade.addObserver( LogManager.getLogger("TCT") new SmsftpLoggerPresenter(LogManager.getLogger("DDT-recv"))); .info("END of userContinueTimeoutShouldContinue"); sendFacade.addObserver(new AllYesPresenter(sendFacade)); } recvFacade.addObserver(new AllYesPresenter(recvFacade)); } // initialize, run, and join Runnables Runnable sendRunnable = () -> { sendFacade.connect(); }; ------Runnable recvRunnable = () -> { Filename: src/test/integration/java/DelayedConnectionTest.java recvFacade.listen(); ------}; Utils.runAndJoin(sendRunnable, recvRunnable, 20000, /* "delayedDeliveryTest");
121 public SmsEntry pollMessage(long timeout, TimeUnit unit) assertEquals(Connection.ConnectionState.CLOSED, throws SmsServiceException, InterruptedException { sendConn.getConnectionState()); return this.bq.poll(timeout, unit); assertEquals(Connection.ConnectionState.CLOSED, } recvConn.getConnectionState()); }
// sleep for a while to "synchronize" stdout Utils.sleepSilently(10); LogManager.getLogger("CDT").info("END of delayedDeliveryTest"); } ------Filename: src/test/integration/java/DuplicateConnectionTest.java } ------
/* // NOTE: just to be sure we have BouncyCastle as a provider //Security.addProvider(new BouncyCastleProvider()); ------*/ Filename: src/test/integration/java/DummySmsService.java package com.transmisms.test.integration; ------import com.transmisms.core.protocol.Connection; package com.transmisms.test.integration; import com.transmisms.core.protocol.Session; import com.transmisms.core.protocol.SmsEntry; import com.transmisms.core.protocol.SmsEntry; import com.transmisms.smsftp.temputil.SmsftpLoggerPresenter; import com.transmisms.core.protocol.SmsService; import com.transmisms.core.protocol.SmsServiceException; import org.apache.logging.log4j.LogManager; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import static org.testng.Assert.assertNotEquals; import java.util.concurrent.TimeUnit; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.fail; public class DummySmsService implements SmsService { protected DummySmsService peer = null; import org.testng.annotations.Test; protected String localMin; private boolean fakeStatus = false;
protected BlockingQueue
//// final Object for repeated use across tests private static final String sendMin = "09121234567"; public DummySmsService(String localMin) { private static final String recvMin = "09129876543"; this(localMin, null); }
public DummySmsService(String localMin, DummySmsService peer) { @Test this.localMin = localMin; public void duplicateDeliveryTest() { this.setPeer(peer); class DoubleSmsService extends DummySmsService { this.bq = new LinkedBlockingQueue<>(); } public DoubleSmsService(String localMin) { super(localMin); }
public void setPeer(DummySmsService peer) { public DoubleSmsService(String localMin, DummySmsService peer) { if(this.peer == null) { super(localMin, peer); this.peer = peer; } } } @Override public void sendMessage(String message) { protected void insertSmsEntry(SmsEntry entry) { this.peer.insertSmsEntry( // NOTE: no checking for overflow since it is assumed that it should new SmsEntry(this.localMin, message)); // never happen on our use case // then send the message a while later this.bq.offer(entry); Thread t = new Thread(() -> { } try { Thread.sleep(500); @Override } public boolean isRunning() { catch(InterruptedException e) { return this.fakeStatus; return; // exit ASAP } } this.peer.insertSmsEntry( @Override new SmsEntry(this.localMin, message)); public boolean start() { }); this.fakeStatus = true; t.start(); return true; } } }
@Override // initialize sms services public boolean stop() { DummySmsService sendService = new DoubleSmsService(sendMin); this.fakeStatus = false; DummySmsService recvService = new DoubleSmsService( return true; recvMin, sendService); } sendService.setPeer(recvService);
@Override // initialize Core objects public void sendMessage(String message) { Connection sendConn = // NOTE: you can comment this out for debugging: Connection.createInitiatorConnection(sendMin, recvMin); //System.out.println("Sendto: " + this.localMin + ": " + message); Connection recvConn = Connection.createResponderConnection(recvMin); // or this code block for filtering against DATA PDUs: Session sendSession = NullSession.generateSenderSession(sendConn); this.peer.insertSmsEntry(new SmsEntry(this.localMin, message)); Session recvSession = NullSession.generateReceiverSession(recvConn); } NullFacade sendFacade = new NullFacade(sendSession, sendService); @Override NullFacade recvFacade =
122 new NullFacade(recvSession, recvService); if(!isReliable) { sendFacade.addObserver( message = "f" + msg; new SmsftpLoggerPresenter(LogManager.getLogger("2XT-send"))); } recvFacade.addObserver( this.peer.insertSmsEntry( new SmsftpLoggerPresenter(LogManager.getLogger("2XT-recv"))); new SmsEntry(this.localMin, message)); sendFacade.addObserver(new AllYesPresenter(sendFacade)); recvFacade.addObserver(new AllYesPresenter(recvFacade)); // comment out this block for filtering against DATA PDUs: /* // initialize, run, and join Runnables if(message.charAt(0) != 'd') { Runnable sendRunnable = () -> { System.out.println("Sendto " sendFacade.connect(); + this.localMin + ": " + message); }; } Runnable recvRunnable = () -> { */ recvFacade.listen(); }; this.count++; Utils.runAndJoin(sendRunnable, recvRunnable, 20000, if(this.count >= this.limit) { "duplicateDeliveryTest"); this.count = 0; } assertEquals(Connection.ConnectionState.CLOSED, } sendConn.getConnectionState()); } assertEquals(Connection.ConnectionState.CLOSED, recvConn.getConnectionState()); // initialize sms services DummySmsService sendService = new ErraticSmsService(sendMin); Utils.sleepSilently(10); // sleep for a while to "synchronize" stdout DummySmsService recvService = new ErraticSmsService( LogManager.getLogger("CDT").info("END of duplicateDeliveryTest"); recvMin, sendService); } sendService.setPeer(recvService); } // initialize Core objects Connection sendConn = Connection.createInitiatorConnection(sendMin, recvMin); Connection recvConn = Connection.createResponderConnection(recvMin); ------Session sendSession = NullSession.generateSenderSession(sendConn); Filename: src/test/integration/java/ErraticConnectionTest.java Session recvSession = NullSession.generateReceiverSession(recvConn); ------NullFacade sendFacade = new NullFacade(sendSession, sendService); /* NullFacade recvFacade = // NOTE: just to be sure we have BouncyCastle as a provider new NullFacade(recvSession, recvService); //Security.addProvider(new BouncyCastleProvider()); sendFacade.addObserver( */ new SmsftpLoggerPresenter(LogManager.getLogger("CDT-send"))); package com.transmisms.test.integration; recvFacade.addObserver( new SmsftpLoggerPresenter(LogManager.getLogger("CDT-recv"))); import com.transmisms.core.protocol.Connection; sendFacade.addObserver(new AllYesPresenter(sendFacade)); import com.transmisms.core.protocol.Session; recvFacade.addObserver(new AllYesPresenter(recvFacade)); import com.transmisms.core.protocol.SmsEntry; import com.transmisms.smsftp.temputil.SmsftpLoggerPresenter; // initialize, run, and join Runnables Runnable sendRunnable = () -> { import org.apache.logging.log4j.LogManager; sendFacade.connect(); }; Runnable recvRunnable = () -> { import static org.testng.Assert.assertNotEquals; recvFacade.listen(); import static org.testng.AssertJUnit.assertEquals; }; import static org.testng.AssertJUnit.assertTrue; Utils.runAndJoin(sendRunnable, recvRunnable, 20000, import static org.testng.AssertJUnit.assertFalse; "corruptedDeliveryTest"); import static org.testng.AssertJUnit.fail; assertEquals(Connection.ConnectionState.CLOSED, import org.testng.annotations.Test; sendConn.getConnectionState()); assertEquals(Connection.ConnectionState.CLOSED, recvConn.getConnectionState()); public class ErraticConnectionTest { // sleep for a while to "synchronize" stdout Utils.sleepSilently(10); //// final Object for repeated use across tests LogManager.getLogger("CDT").info("END of corruptedDeliveryTest"); private static final String sendMin = "09121234567"; } private static final String recvMin = "09129876543"; }
@Test public void corruptedDeliveryTest() { class ErraticSmsService extends DummySmsService { ------final int limit = 3; Filename: src/test/integration/java/NullFacade.java int count = 0; ------
public ErraticSmsService(String localMin) { package com.transmisms.test.integration; super(localMin); } import com.transmisms.core.protocol.BinaryPDUDecoder; import com.transmisms.core.protocol.Connection; public ErraticSmsService(String localMin, DummySmsService peer)import { com.transmisms.core.protocol.CoreProtocolFacade; super(localMin, peer); import com.transmisms.core.protocol.Session; } import com.transmisms.core.protocol.SmsService; import com.transmisms.core.protocol.TextBasedPDUDecoder; @Override public void sendMessage(String msg) { // don't send any retransmission messages for this test if(msg.charAt(0) == 'r') { public class NullFacade extends CoreProtocolFacade { return; private final String SUBPROTOCOL_NAME = "nullprotocol"; } private static final String USER_ACCEPT_STATUS_CODE = "0002"; boolean isReliable = this.count != 0; private static final String COMPLETED_STATUS_CODE = "0001";
String message = msg; private static final String[] knownProtocols = { "nullprotocol" };
123 this.processMessage(o, args); public NullFacade(Session session, SmsService smsService) { } this(session, smsService, 1); } private void processMessage(Observable o, Object[] args) { // exit asap if message type is not applicable to this public NullFacade(Session session, SmsService smsService, if(!(args.length == 2 && args[1] instanceof PromptType)) { int maxPduLifetime) { return; super(session, BinaryPDUDecoder.getInstance(), } new TextBasedPDUDecoder(NullFacade.knownProtocols){}, smsService, maxPduLifetime); // args[0] is guaranteed instanceof MessageType } MessageType messageType = (MessageType)args[0]; PromptType promptType = (PromptType)args[1]; @Override public String getSubprotocolName() { switch(messageType) { return this.SUBPROTOCOL_NAME; // NOTE: copied from SmsftpPresenter.processMessage() } // check for unwanted cases case PROMPT: { @Override this.onUserPrompt(promptType, null); protected String[] processReqAndGenRepPayload(Object[] requestData) { break; // just accept all incoming connections regardless of the data's } // contents default: { String[] retArr = { NullFacade.USER_ACCEPT_STATUS_CODE }; // do nothing return retArr; break; } } } @Override } protected String generateHeadPayload() { return ""; // nothing is returned (results in "head/nullprotocol/") protected void onUserPrompt(PromptType promptType, String message) { } switch(promptType) { case TIMEOUT: { @Override // act using a background task to simulate user input protected void processHeadPayload(Object[] headData) { Thread t = new Thread() { // do nothing; we don't process anything here @Override } public void run() { try { @Override Thread.sleep(500); // sleep for a while protected void onEstablished() { } // if initiator, just disconnect catch(InterruptedException e) { if(this.coreSession.connection.role == // just maintain interrupt status and continue Connection.Role.INITIATOR) { Thread.currentThread().interrupt(); this.disconnect(COMPLETED_STATUS_CODE); } } // otherwise, do nothing and wait NullPresenter.this.onTimeout(); } } }; public void listen() { t.start(); super.listen(); break; } } default: { public void connect() { // do nothing super.connect(""); break; } } } } }
protected void onTimeout() { // default: cancel immediately after timeout this.coreProtocolFacade.cancelOperationAfterPrompt(); ------} Filename: src/test/integration/java/NullPresenter.java } ------package com.transmisms.test.integration; import com.transmisms.core.protocol.CoreProtocolFacade; import com.transmisms.core.protocol.Presenter; ------import com.transmisms.core.protocol.Presenter.MessageType; Filename: src/test/integration/java/NullSession.java import com.transmisms.core.protocol.Presenter.PromptType; ------import java.util.Observable; package com.transmisms.test.integration;
import com.transmisms.core.protocol.Connection; import com.transmisms.core.protocol.Session; public class NullPresenter extends Presenter { private CoreProtocolFacade coreProtocolFacade; import java.util.UUID;
public NullPresenter(CoreProtocolFacade coreProtocolFacade) { //super(coreProtocolFacade); this.coreProtocolFacade = coreProtocolFacade; public class NullSession extends Session { } public NullSession(Connection connection, UUID sessionId) { @Override super(connection, sessionId); public final void update(Observable o, Object arg) { } // NOTE: copied from SmsftpPresenter.update() // check for unwanted cases if(arg == null || !(arg instanceof Object[])) { return; // do nothing public static NullSession generateSenderSession(Connection connection) { } return new NullSession(connection, UUID.randomUUID()); Object[] args = (Object[])arg; } if(args.length <= 1 || !(args[0] instanceof MessageType)) { return; // do nothing public static NullSession generateReceiverSession(Connection connection) { } return new NullSession(connection, null);
124 } } } default: { return; // just do nothing and return ASAP } } } } ------Filename: src/test/integration/java/NullSmsftpPresenter.java ------package com.transmisms.test.integration; ------Filename: src/test/integration/java/OutOfOrderConnectionTest.java import com.transmisms.core.protocol.Presenter.PromptType; ------import com.transmisms.smsftp.protocol.SmsftpFacade; import com.transmisms.smsftp.protocol.SmsftpPresenter; /* import com.transmisms.smsftp.protocol.SmsftpSession; // NOTE: just to be sure we have BouncyCastle as a provider //Security.addProvider(new BouncyCastleProvider()); import java.util.Observable; */ package com.transmisms.test.integration;
import com.transmisms.core.protocol.Connection; public class NullSmsftpPresenter extends SmsftpPresenter { import com.transmisms.core.protocol.Session; protected final SmsftpFacade smsftpFacade; import com.transmisms.core.protocol.SmsEntry; private final SmsftpSession session; import com.transmisms.smsftp.temputil.SmsftpLoggerPresenter; private final byte[] enckey; private final byte[] authkey; import java.util.Deque; import java.util.concurrent.LinkedBlockingDeque;
import org.apache.logging.log4j.LogManager; public NullSmsftpPresenter(SmsftpFacade smsftpFacade, byte[] enckey, byte[] authkey) { super(smsftpFacade); import static org.testng.Assert.assertNotEquals; this.smsftpFacade = smsftpFacade; import static org.testng.AssertJUnit.assertEquals; this.session = this.smsftpFacade.getSmsftpSession(); import static org.testng.AssertJUnit.assertTrue; this.enckey = enckey; import static org.testng.AssertJUnit.assertFalse; this.authkey = authkey; import static org.testng.AssertJUnit.fail; } import org.testng.annotations.Test; public NullSmsftpPresenter(SmsftpFacade smsftpFacade) { this(smsftpFacade, null, null); } public class OutOfOrderConnectionTest {
//// final Object for repeated use across tests protected void onLogMessage(MessageType mType, String message) { private static final String sendMin = "09121234567"; // do nothing; we delegate logging to other Presenters private static final String recvMin = "09129876543"; }
protected void onStatusUpdate() { } @Test public void outOfOrderDeliveryTest() { protected void onUserPrompt(PromptType pType, String message) { class OutOfOrderSmsService extends DummySmsService { switch(pType) { final int limit = 3; case CONNECTION_REQUEST: { boolean getHead = false; // if responder if(this.session.getRole() == SmsftpSession.Role.RESPONDER) { final Deque
125 } private final String filename = "test.txt"; */ this.peer.insertSmsEntry( private final String testStr = "test string"; new SmsEntry(this.localMin, s)); private final byte[] dataBytes = testStr.getBytes(); } this.getHead = !this.getHead; // flip getHead state private static final int FTC_TIMEOUT = 8000; } private static final int PRSC_TIMEOUT = 24000; } }
// initialize sms services @Test DummySmsService sendService = new OutOfOrderSmsService(sendMin); public void xFileTransferShouldComplete() { DummySmsService recvService = new OutOfOrderSmsService( // initialize sms services recvMin, sendService); DummySmsService sendService = new DummySmsService(sendMin); sendService.setPeer(recvService); DummySmsService recvService = new DummySmsService( recvMin, sendService); // initialize Core objects sendService.setPeer(recvService); Connection sendConn = Connection.createInitiatorConnection(sendMin, recvMin); // initialize Core objects Connection recvConn = Connection.createResponderConnection(recvMin); Connection sendConn = Session sendSession = NullSession.generateSenderSession(sendConn); Connection.createInitiatorConnection(sendMin, recvMin); Session recvSession = NullSession.generateReceiverSession(recvConn); Connection recvConn = NullFacade sendFacade = Connection.createResponderConnection(recvMin); new NullFacade(sendSession, sendService); SmsftpSession sendSession = SmsftpSession.generateSenderDataSession( NullFacade recvFacade = sendConn, filename, dataBytes, null, null); new NullFacade(recvSession, recvService); SmsftpSession recvSession = SmsftpSession.generateReceiverSession( sendFacade.addObserver( recvConn); new SmsftpLoggerPresenter(LogManager.getLogger("O3T-send"))); recvFacade.addObserver( SmsftpFacade sendFacade = new SmsftpFacade(sendSession, sendService, new SmsftpLoggerPresenter(LogManager.getLogger("O3T-recv"))); 1); sendFacade.addObserver(new AllYesPresenter(sendFacade)); SmsftpFacade recvFacade = new SmsftpFacade(recvSession, recvService, recvFacade.addObserver(new AllYesPresenter(recvFacade)); 1); sendFacade.addObserver( // initialize, run, and join Runnables new SmsftpLoggerPresenter(LogManager.getLogger("XFTC-send"))); Runnable sendRunnable = () -> { recvFacade.addObserver( sendFacade.connect(); new SmsftpLoggerPresenter(LogManager.getLogger("XFTC-recv"))); }; sendFacade.addObserver(new NullSmsftpPresenter(sendFacade)); Runnable recvRunnable = () -> { recvFacade.addObserver(new NullSmsftpPresenter(recvFacade)); recvFacade.listen(); }; // initialize, run, and join Runnables Utils.runAndJoin(sendRunnable, recvRunnable, 30000, Runnable sendRunnable = () -> { "outOfOrderDeliveryTest"); sendFacade.insecureSendFile(); }; assertEquals(Connection.ConnectionState.CLOSED, Runnable recvRunnable = () -> { sendConn.getConnectionState()); recvFacade.receive(); assertEquals(Connection.ConnectionState.CLOSED, }; recvConn.getConnectionState()); Utils.runAndJoin(sendRunnable, recvRunnable, FTC_TIMEOUT, "xFileTransferShouldCompleteTest"); Utils.sleepSilently(10); // sleep for a while to "synchronize" stdout LogManager.getLogger("CDT").info("END of outOfOrderDeliveryTest"); assertEquals(Connection.ConnectionState.CLOSED, } sendConn.getConnectionState()); } assertEquals(Connection.ConnectionState.CLOSED, recvConn.getConnectionState());
assertEquals(testStr, new String(recvFacade.getDataBytes()));
------// sleep for a while to "synchronize" stdout Filename: src/test/integration/java/SmsftpConnectionTest.java Utils.sleepSilently(10); ------LogManager.getLogger("XFTC") .info("END of xFileTransferShouldCompleteTest"); package com.transmisms.test.integration; } import com.transmisms.core.protocol.Connection; @Test import com.transmisms.smsftp.protocol.SmsftpFacade; public void eFileTransferShouldComplete() { import com.transmisms.smsftp.protocol.SmsftpSession; // populate with proper encryption keys import com.transmisms.smsftp.temputil.SmsftpLoggerPresenter; ByteArrayOutputStream ePrivOs = new ByteArrayOutputStream(); ByteArrayOutputStream ePubOs = new ByteArrayOutputStream(); import com.transmisms.core.util.crypto.AES; ByteArrayOutputStream aPrivOs = new ByteArrayOutputStream(); import com.transmisms.core.util.crypto.RSA; ByteArrayOutputStream aPubOs = new ByteArrayOutputStream(); try { import java.io.ByteArrayOutputStream; RSA.generateKeys(ePrivOs, ePubOs); import java.io.IOException; RSA.generateKeys(aPrivOs, aPubOs); import java.util.Arrays; } import org.apache.logging.log4j.LogManager; catch(IOException e) { fail("got IOException: " + e); } import static org.testng.Assert.assertNotEquals; byte[] pubEnckey = ePubOs.toByteArray(); import static org.testng.Assert.assertNotNull; byte[] privEnckey = ePrivOs.toByteArray(); import static org.testng.AssertJUnit.assertEquals; byte[] pubAuthkey = aPubOs.toByteArray(); import static org.testng.AssertJUnit.assertTrue; byte[] privAuthkey = aPrivOs.toByteArray(); import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.fail; // initialize sms services DummySmsService sendService = new DummySmsService(sendMin); import org.testng.annotations.Test; DummySmsService recvService = new DummySmsService( recvMin, sendService); sendService.setPeer(recvService); public class SmsftpConnectionTest { // initialize Core objects //// final Objects for repeated use across tests Connection sendConn = private final String sendMin = "09121234567"; Connection.createInitiatorConnection(sendMin, recvMin); private final String recvMin = "09129876543"; Connection recvConn =
126 Connection.createResponderConnection(recvMin); assertNotEquals(0, sendSession.peerEnckey.length); SmsftpSession sendSession = SmsftpSession.generateSenderDataSession( assertNotEquals(0, sendSession.peerAuthkey.length); sendConn, filename, dataBytes, privEnckey, privAuthkey); assertNotEquals(0, recvSession.peerEnckey.length); SmsftpSession recvSession = SmsftpSession.generateReceiverSession( assertNotEquals(0, recvSession.peerAuthkey.length); recvConn); assertTrue(Arrays.equals(sendSession.peerEnckey, recvSession.localEncKeypair.getPublic().getEncoded())); SmsftpFacade sendFacade = new SmsftpFacade(sendSession, sendService, assertTrue(Arrays.equals(sendSession.peerAuthkey, 1); recvSession.localAuthKeypair.getPublic().getEncoded())); SmsftpFacade recvFacade = new SmsftpFacade(recvSession, recvService, assertTrue(Arrays.equals(recvSession.peerEnckey, 1); sendSession.localEncKeypair.getPublic().getEncoded())); sendFacade.addObserver( assertTrue(Arrays.equals(recvSession.peerAuthkey, new SmsftpLoggerPresenter(LogManager.getLogger("EFTC-send"))); sendSession.localAuthKeypair.getPublic().getEncoded())); recvFacade.addObserver( new SmsftpLoggerPresenter(LogManager.getLogger("EFTC-recv"))); // sleep for a while to "synchronize" stdout sendFacade.addObserver(new NullSmsftpPresenter(sendFacade)); Utils.sleepSilently(10); recvFacade.addObserver(new NullSmsftpPresenter(recvFacade, pubEnckey, LogManager.getLogger("PRSC") pubAuthkey)); .info("END of pairingShouldCompleteTest"); } // initialize, run, and join Runnables } Runnable sendRunnable = () -> { sendFacade.secureSendFile(); }; Runnable recvRunnable = () -> { recvFacade.receive(); ------}; Filename: src/test/integration/java/SmsftpLargeFileTest.java Utils.runAndJoin(sendRunnable, recvRunnable, FTC_TIMEOUT, ------"eFileTransferShouldCompleteTest"); package com.transmisms.test.integration; assertEquals(Connection.ConnectionState.CLOSED, sendConn.getConnectionState()); import com.transmisms.core.protocol.Connection; assertEquals(Connection.ConnectionState.CLOSED, import com.transmisms.smsftp.protocol.SmsftpFacade; recvConn.getConnectionState()); import com.transmisms.smsftp.protocol.SmsftpSession; import com.transmisms.smsftp.temputil.SmsftpLoggerPresenter; assertEquals(testStr, new String(recvFacade.getDataBytes())); import com.transmisms.core.util.crypto.AES; // sleep for a while to "synchronize" stdout import com.transmisms.core.util.crypto.RSA; Utils.sleepSilently(10); LogManager.getLogger("EFTC") import java.io.ByteArrayOutputStream; .info("END of eFileTransferShouldCompleteTest"); import java.io.IOException; } import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @Test import java.util.Arrays; public void pairingShouldComplete() { import org.apache.logging.log4j.LogManager; // initialize sms services import org.apache.commons.io.IOUtils; DummySmsService sendService = new DummySmsService(sendMin); DummySmsService recvService = new DummySmsService( recvMin, sendService); import static org.testng.Assert.assertNotEquals; sendService.setPeer(recvService); import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; // initialize Core objects import static org.testng.AssertJUnit.assertFalse; Connection sendConn = import static org.testng.AssertJUnit.fail; Connection.createInitiatorConnection(sendMin, recvMin); Connection recvConn = import org.testng.annotations.Test; Connection.createResponderConnection(recvMin); SmsftpSession sendSession = SmsftpSession.generateInitiatorPairSession(sendConn); SmsftpSession recvSession = SmsftpSession.generateReceiverSession(public class SmsftpLargeFileTest { recvConn); //// final Object for repeated use across tests private final String sendMin = "09121234567"; SmsftpFacade sendFacade = new SmsftpFacade(sendSession, sendService, private final String recvMin = "09129876543"; 1); private final String filename = "test.txt"; SmsftpFacade recvFacade = new SmsftpFacade(recvSession, recvService, 1); private static byte[] dataBytes = null; sendFacade.addObserver( new SmsftpLoggerPresenter(LogManager.getLogger("PRSC-send"))); private static final int LTC_TIMEOUT = 40000; recvFacade.addObserver( new SmsftpLoggerPresenter(LogManager.getLogger("PRSC-recv"))); static { sendFacade.addObserver(new AllYesSmsftpPresenter(sendFacade)); try { recvFacade.addObserver(new AllYesSmsftpPresenter(recvFacade, null, dataBytes = IOUtils.toByteArray( null)); ClassLoader.getSystemResourceAsStream("5MB.zip")); } // initialize, run, and join Runnables catch(IOException e) { Runnable sendRunnable = () -> { assert false; sendFacade.pair(); } }; } Runnable recvRunnable = () -> { recvFacade.receive(); }; Utils.runAndJoin(sendRunnable, recvRunnable, PRSC_TIMEOUT, @Test "pairingShouldCompleteTest"); public void xLargeFileTransferShouldComplete() { // initialize sms services assertEquals(Connection.ConnectionState.CLOSED, DummySmsService sendService = new DummySmsService(sendMin); sendConn.getConnectionState()); DummySmsService recvService = new DummySmsService( assertEquals(Connection.ConnectionState.CLOSED, recvMin, sendService); recvConn.getConnectionState()); sendService.setPeer(recvService);
// verify keys // initialize Core objects assertNotNull(sendSession.peerEnckey); Connection sendConn = assertNotNull(sendSession.peerAuthkey); Connection.createInitiatorConnection(sendMin, recvMin); assertNotNull(recvSession.peerEnckey); Connection recvConn = assertNotNull(recvSession.peerAuthkey); Connection.createResponderConnection(recvMin);
127 SmsftpSession sendSession = SmsftpSession.generateSenderDataSession( new SmsftpLoggerPresenter(LogManager.getLogger("ELTC-send"))); sendConn, filename, dataBytes, null, null); recvFacade.addObserver( SmsftpSession recvSession = SmsftpSession.generateReceiverSession( new SmsftpLoggerPresenter(LogManager.getLogger("ELTC-recv"))); recvConn); sendFacade.addObserver(new NullSmsftpPresenter(sendFacade)); recvFacade.addObserver(new NullSmsftpPresenter(recvFacade, pubEnckey, SmsftpFacade sendFacade = new SmsftpFacade(sendSession, sendService, pubAuthkey)); 4); SmsftpFacade recvFacade = new SmsftpFacade(recvSession, recvService, // initialize, run, and join Runnables 4); Runnable sendRunnable = () -> { sendFacade.addObserver( sendFacade.secureSendFile(); new SmsftpLoggerPresenter(LogManager.getLogger("XLTC-send"))); }; recvFacade.addObserver( Runnable recvRunnable = () -> { new SmsftpLoggerPresenter(LogManager.getLogger("XLTC-recv"))); recvFacade.receive(); sendFacade.addObserver(new NullSmsftpPresenter(sendFacade)); }; recvFacade.addObserver(new NullSmsftpPresenter(recvFacade)); Utils.runAndJoin(sendRunnable, recvRunnable, LTC_TIMEOUT, "eLargeFileTransferShouldCompleteTest"); // initialize, run, and join Runnables Runnable sendRunnable = () -> { // check if data received bytes' and original bytes' hashes match sendFacade.insecureSendFile(); MessageDigest md = null; }; try { Runnable recvRunnable = () -> { md = MessageDigest.getInstance("SHA-1"); recvFacade.receive(); } }; catch(NoSuchAlgorithmException e) { Utils.runAndJoin(sendRunnable, recvRunnable, LTC_TIMEOUT, fail("Problems initializing native SHA-1 digest"); "xLargeFileTransferShouldCompleteTest"); } md.update(dataBytes); // check if data received bytes' and original bytes' hashes match byte[] srcHash = md.digest(); MessageDigest md = null; md.update(recvFacade.getDataBytes()); try { byte[] rcvHash = md.digest(); md = MessageDigest.getInstance("SHA-1"); assertTrue(Arrays.equals(srcHash, rcvHash)); } catch(NoSuchAlgorithmException e) { assertEquals(Connection.ConnectionState.CLOSED, fail("Problems initializing native SHA-1 digest"); sendConn.getConnectionState()); } assertEquals(Connection.ConnectionState.CLOSED, md.update(dataBytes); recvConn.getConnectionState()); byte[] srcHash = md.digest(); md.update(recvFacade.getDataBytes()); // sleep for a while to "synchronize" stdout byte[] rcvHash = md.digest(); Utils.sleepSilently(10); assertTrue(Arrays.equals(srcHash, rcvHash)); LogManager.getLogger("ELTC") .info("END of eLargeFileTransferShouldCompleteTest"); assertEquals(Connection.ConnectionState.CLOSED, } sendConn.getConnectionState()); } assertEquals(Connection.ConnectionState.CLOSED, recvConn.getConnectionState());
// sleep for a while to "synchronize" stdout Utils.sleepSilently(10); ------LogManager.getLogger("XLTC") Filename: src/test/integration/java/Utils.java .info("END of xLargeFileTransferShouldCompleteTest"); ------} package com.transmisms.test.integration; @Test public void eLargeFileTransferShouldComplete() { import java.util.concurrent.Executors; // populate with proper encryption keys import java.util.concurrent.ExecutorService; ByteArrayOutputStream ePrivOs = new ByteArrayOutputStream(); import java.util.concurrent.TimeUnit; ByteArrayOutputStream ePubOs = new ByteArrayOutputStream(); ByteArrayOutputStream aPrivOs = new ByteArrayOutputStream(); import static org.testng.AssertJUnit.fail; ByteArrayOutputStream aPubOs = new ByteArrayOutputStream(); try { RSA.generateKeys(ePrivOs, ePubOs); RSA.generateKeys(aPrivOs, aPubOs); /** } * Utility class to contain helper methods for use by integration tests catch(IOException e) { */ fail("got IOException: " + e); public class Utils { } byte[] pubEnckey = ePubOs.toByteArray(); /** byte[] privEnckey = ePrivOs.toByteArray(); * Runs a pair of Runnables in background and waits for both at most byte[] pubAuthkey = aPubOs.toByteArray(); * timeoutMillis
milliseconds for the Runnables to complete. byte[] privAuthkey = aPrivOs.toByteArray(); * A timeout of
0
means to wait forever. * // initialize sms services * @param r1 one of the Runnables to be ran in the background DummySmsService sendService = new DummySmsService(sendMin); * @param r2 one of the Runnables to be ran in the background DummySmsService recvService = new DummySmsService( * @param timeoutMillis time to wait in milliseconds recvMin, sendService); * @param testName name of the test currently being run sendService.setPeer(recvService); */ public static void runAndJoin(Runnable r1, Runnable r2, long timeoutMillis, // initialize Core objects String testName) { Connection sendConn = ExecutorService es = Executors.newFixedThreadPool(2); Connection.createInitiatorConnection(sendMin, recvMin); es.submit(r1); Connection recvConn = es.submit(r2); Connection.createResponderConnection(recvMin); es.shutdown(); SmsftpSession sendSession = SmsftpSession.generateSenderDataSession( try { sendConn, filename, dataBytes, privEnckey, privAuthkey); if(timeoutMillis == 0) { SmsftpSession recvSession = SmsftpSession.generateReceiverSession( // for "infinite" wait recvConn); es.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS); } SmsftpFacade sendFacade = new SmsftpFacade(sendSession, sendService, else { 4); es.awaitTermination(timeoutMillis, TimeUnit.MILLISECONDS); SmsftpFacade recvFacade = new SmsftpFacade(recvSession, recvService, } 4); } sendFacade.addObserver( catch(InterruptedException e) {
128 System.out.println("INTERRUPTION on " + testName + "!"); fail("Test interrupted"); } // ERROR TEST CASES } @Test(expectedExceptions = SmsServiceException.class, timeOut = 7000, priority = 1) /** public void nonExistentServerShouldThrowSmsServiceErrorTest() * Generates encryption and authentication key pairs for use with throws SmsServiceException { * transmisms smsftp integration tests System.out.println("Starting * nonExistentServerShouldThrowSmsServiceErrorTest"); * @return a four-member byte[][] containing keys in byte[] format in the AndroidSmsService service = * order: public encryption key, private encryption key, new AndroidSmsService("localhost", 65535); * public authentication key, and private authentication key service.start(); */ // we don't need to cleanup for non-existent connections public static byte[][] generateEncAuthKeyPairs() { System.out.println("End of return null; nonExistentServerShouldThrowSmsServiceErrorTest"); } }
/** * Causes the currently executing thread to sleep for a specified number // NORMAL TEST CASES * of milliseconds, silently failing if interrupted. @Test(timeOut = 7000, priority = 2) * public void startServiceTest() * @param millis the length of time to sleep in milliseconds throws SmsServiceException, InterruptedException { * System.out.println("Starting startServiceTest"); * @see Thread Thread.sleep(1000); // sleep to avoid flooding the server with tests */ AndroidSmsService service = public static void sleepSilently(int millis) { new AndroidSmsService(TEST_HOST, TEST_PORT); try { service.start(); Thread.sleep(10); // cleanup } service.stop(); catch(InterruptedException e) { System.out.println("End of startServiceTest"); // do nothing } } } @Test(timeOut = 4000, priority = 3) } public void msisdnTest() throws SmsServiceException, InterruptedException { System.out.println("Starting msisdnTest"); Thread.sleep(1000); // sleep to avoid flooding the server with tests AndroidSmsService service = ------new AndroidSmsService(TEST_HOST, TEST_PORT); Filename: src/test/resources/log4j2-test.yaml service.start(); ------String msisdn = service.getLocalMin(); assertEquals(OWN_MSISDN, msisdn); Configuration: // cleanup status: warn service.stop(); System.out.println("End of msisdnTest"); Appenders: } Console: name: Console @Test(timeOut = 34000, priority = 4) target: SYSTEM_OUT public void sendRecvMessageTest() PatternLayout: throws SmsServiceException, InterruptedException { Pattern: "%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n" System.out.println("Starting sendMessageTest"); Thread.sleep(1000); // sleep to avoid flooding the server with tests Loggers: AndroidSmsService service = Root: new AndroidSmsService(TEST_HOST, TEST_PORT); level: trace service.start(); AppenderRef: - ref: Console // send message service.setPeer(TEST_RECIPIENT); service.sendMessage(TEST_MSG);
Thread.sleep(1000); // sleep for a while to let the message be sent ------// try to receive any message Filename: src/test/staging/java/AndroidCompanionTest.java service.setPeer(null); ------SmsEntry msg = null; while(msg == null) { package com.transmisms.test.staging; msg = service.pollMessage(10, TimeUnit.SECONDS); } import com.transmisms.core.protocol.SmsEntry; assertNotNull(msg); import com.transmisms.core.protocol.SmsServiceException; import com.transmisms.ui.javafx.smsservice.AndroidSmsService; // cleanup service.stop(); import static org.testng.Assert.assertNotEquals; System.out.println("End of sendMessageTest"); import static org.testng.Assert.assertNotNull; } import static org.testng.AssertJUnit.assertEquals; } import static org.testng.AssertJUnit.assertTrue; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.fail; import org.testng.annotations.Test; ------import java.util.concurrent.TimeUnit; Filename: src/test/staging/java/GammuTest.java ------
package com.transmisms.test.staging; public class AndroidCompanionTest { public final static String TEST_HOST = "192.168.254.181"; import com.transmisms.core.protocol.SmsEntry; public final static int TEST_PORT = 8767; import com.transmisms.core.protocol.SmsServiceException; import com.transmisms.ui.javafx.smsservice.GammuSmsService; public final static String TEST_MSG = "help"; public final static String TEST_RECIPIENT = "8080"; import static org.testng.Assert.assertNotEquals; public final static String OWN_MSISDN = "+639754314753"; import static org.testng.Assert.assertNotNull; import static org.testng.AssertJUnit.assertEquals;
129 import static org.testng.AssertJUnit.assertTrue; import org.junit.experimental.categories.Categories; import static org.testng.AssertJUnit.assertFalse; import org.junit.experimental.categories.Categories.IncludeCategory; import static org.testng.AssertJUnit.fail; import org.junit.experimental.categories.Categories.ExcludeCategory; import org.testng.annotations.Test; @IncludeCategory({ RegularTests.class, ExceptionTests.class }) import java.util.concurrent.TimeUnit; @RunWith(Categories.class) @SuiteClasses({ CoreProtocolTest.class, SmsftpTest.class, DecoderTest.class, public class GammuTest { BinaryDecoderTest.class, public final static String TEST_HOST = "localhost"; TextBasedDecoderTest.class, public final static int TEST_PORT = 5432; VersionTest.class, public final static String TEST_DB = "smsd"; SessionTest.class, public final static String TEST_USER = "smsd"; SegmentManagerTest.class, public final static String TEST_PASS = ""; Base85Test.class, public final static String TEST_MSISDN = "9127654321"; RSAPEMImportExportTest.class, BiometricFingerprintTest.class}) public final static String TEST_MSG = "help"; public class AllTestsSuite {} public final static String TEST_RECIPIENT = "8080";
// ERROR TEST CASES ------@Test(expectedExceptions = SmsServiceException.class, timeOut = 10000,Filename: src/test/unit/java/Base85Test.java priority = 1) ------public void nonExistentServerShouldThrowSmsServiceError() throws SmsServiceException { import com.transmisms.core.util.codec.Base85; System.out.println("Starting nonExistentServerShouldThrowSmsServiceErrorTest"); import java.util.Arrays; GammuSmsService service = import java.io.IOException; new GammuSmsService("localhost", 65535, TEST_DB, TEST_USER, TEST_PASS, TEST_MSISDN); import static org.junit.Assert.assertEquals; service.start(); import static org.junit.Assert.assertNotEquals; // we don't need to cleanup for non-existent connections import static org.junit.Assert.assertTrue; System.out.println("End of import static org.junit.Assert.assertFalse; nonExistentServerShouldThrowSmsServiceErrorTest"); import static org.junit.Assert.fail; } import org.junit.experimental.categories.Category;
// NORMAL TEST CASES import org.junit.Test; @Test(timeOut = 10000, priority = 2) public void startServiceTest() throws SmsServiceException { System.out.println("Starting startServiceTest"); GammuSmsService service = public class Base85Test { new GammuSmsService(TEST_HOST, TEST_PORT, TEST_DB, TEST_USER, @Category(RegularTests.class) TEST_PASS, TEST_MSISDN); @Test service.start(); public void whiteSpaceShouldBeIrrelevant() throws IOException { // cleanup // encoded string: &Ct>3]-''~ service.stop(); byte[] magicNumbersA = { 16, -42, 99, 29, -69, 32, 56 }; System.out.println("End of startServiceTest"); } assertTrue(Arrays.equals(Base85.decode("&Ct>3]-''~"), Base85.decode("&Ct>3] -''~"))); @Test(timeOut = 34000, priority = 3) assertTrue(Arrays.equals(Base85.decode("&Ct>3]-''~"), public void sendRecvMessageTest() Base85.decode("&Ct >3]\n-''~"))); throws SmsServiceException, InterruptedException { assertTrue(Arrays.equals(Base85.decode("&Ct>3]-''~"), System.out.println("Starting sendMessageTest"); Base85.decode(" & Ct >3]\n -\n ''~"))); GammuSmsService service = } new GammuSmsService(TEST_HOST, TEST_PORT, TEST_DB, TEST_USER, TEST_PASS, TEST_MSISDN); @Category(RegularTests.class) service.start(); @Test public void suffixesShouldBeIrrelevant() throws IOException { // send message // checks for various inputs with combinations of suffixes service.setPeer(TEST_RECIPIENT); // a. ~ service.sendMessage(TEST_MSG); // b. ~> // c. none Thread.sleep(1000); // sleep for a while to let the message be sent // try to receive any message /* service.setPeer(null); * The following are the magic numbers behind our strings SmsEntry msg = null; * poT]46!?V86oONZ~ : { -8, -83, -110, 47, 65, 90, 66, -29, 68, 54, while(msg == null) { -16, 35 }; msg = service.pollMessage(10, TimeUnit.SECONDS); * 1h$7?@fPD#J$]~ : { 52, 97, -6, -30, 98, -1, -31, 104, 127, -76 }; } * DX14]rT.k~ : { 110, -21, 83, 57, -3, -27, 109 }; assertNotNull(msg); */ assertTrue(Arrays.equals(Base85.decode("poT]46!?V86oONZ~"), // cleanup Base85.decode("poT]46!?V86oONZ~>"))); service.stop(); assertTrue(Arrays.equals(Base85.decode("poT]46!?V86oONZ~"), System.out.println("End of sendMessageTest"); Base85.decode("poT]46!?V86oONZ"))); } } assertTrue(Arrays.equals(Base85.decode("1h$7?@fPD#J$]~"), Base85.decode("1h$7?@fPD#J$]~>"))); assertTrue(Arrays.equals(Base85.decode("1h$7?@fPD#J$]~"), Base85.decode("1h$7?@fPD#J$]")));
------assertTrue(Arrays.equals(Base85.decode("DX14]rT.k~"), Filename: src/test/unit/java/AllTestsSuite.java Base85.decode("DX14]rT.k~>"))); ------assertTrue(Arrays.equals(Base85.decode("DX14]rT.k~"), Base85.decode("DX14]rT.k"))); import org.junit.runner.RunWith; } import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @Category(RegularTests.class) @Test
130 public void nonGSM7CharactersShouldBeReplaced() throws IOException { // PCr assertTrue(Arrays.equals(Base85.decode("[pxu%"), } Base85.decode("vpxu%"))); // [ -> v assertTrue(Arrays.equals(Base85.decode("\\NIHB"), @Category(ExceptionTests.class) Base85.decode("wNIHB"))); // \ -> w @Test(expected = PDUMalformedException.class) assertTrue(Arrays.equals(Base85.decode("P]L+<"), public void invalidFirstCharShouldThrowPDUMalformedException() Base85.decode("PxL+<"))); // ] -> x throws PDUMalformedException, InvalidCRCException { assertTrue(Arrays.equals(Base85.decode("&^0ui"), SmsftpBinaryPduDecoder.getInstance().decodeBinary("X8f"); Base85.decode("&y0ui"))); // ^ -> y // PCr assertTrue(Arrays.equals(Base85.decode("_k`!l"), } Base85.decode("_k£!l"))); // ` -> £ } @Category(ExceptionTests.class) @Test(expected = InvalidCRCException.class) @Category(RegularTests.class) public void invalidCRCShouldThrowInvalidCRCException() @Test throws PDUMalformedException, InvalidCRCException { public void byteInputShouldReturnExpectedEncodedString() // CRC of "r" should be 0x59 throws IOException { SmsftpBinaryPduDecoder.getInstance().decodeBinary("r58"); // Magic numbers and their corresponding encoded forms // PCr // WVIdN3D91@"[r~ if encoded } byte[] magicNumbersA = { -87, -7, 35, -69, 57, 75, -7, -96, 5, 69 }; // KbX_\:-"WM[7iKR~ if encoded byte[] magicNumbersB = { -123, 21, 4, 71, 78, 57, -106, -72, -75, 76, // CORE_FIN 54, -93 }; @Category(ExceptionTests.class) // %o;>OX#'>~ if encoded @Test(expected = PDUMalformedException.class) byte[] magicNumbersC = { 15, 79, -26, -109, -85, 51, -13 }; public void coreFinShortSessionIdShouldThrowPDUMalformedException() throws PDUMalformedException, InvalidCRCException { assertEquals("WVIdN3D91@\"vr", Base85.encode(magicNumbersA)); SmsftpBinaryPduDecoder.getInstance().decodeBinary( assertEquals("KbX_w:-\"WMv7iKR", Base85.encode(magicNumbersB)); "f1234567890123456789db"); assertEquals("%o;>OX#'>", Base85.encode(magicNumbersC)); // P-Sessionid------StatCr } } } @Category(ExceptionTests.class) @Test public void coreFinTruncatedSessionIdShouldThrowPDUMalformedException() throws PDUMalformedException, InvalidCRCException { ------try { Filename: src/test/unit/java/BinaryDecoderTest.java SmsftpBinaryPduDecoder.getInstance().decodeBinary( ------"fFX7%V4B>2:M~12>%W*h=050076"); // P-Sessionid------StatCr // NOTE: For code readability, assertEquals() tests' arguments here are on fail("expected PDUMalformedException on truncated Session Id " + // reverse order (expected <-> was) "on SMSFTP_FIN: FX7%V4B>2:M~12>%W*h="); import com.transmisms.core.protocol.PDU; } import com.transmisms.core.protocol.PDUType; catch(PDUMalformedException e) { } // do nothing import com.transmisms.core.protocol.CorePDUType; try { // with Additional Message field import com.transmisms.core.protocol.PDUMalformedException; SmsftpBinaryPduDecoder.getInstance().decodeBinary( import com.transmisms.core.protocol.InvalidCRCException; "fFX7%V4B>2:M~12>%W*h=0500This is a test messagef9"); import com.transmisms.smsftp.protocol.SmsftpBinaryPduDecoder; // P-Sessionid------StatAdditional*Message****Cr import com.transmisms.smsftp.protocol.SmsftpPDUType; fail("expected PDUMalformedException on truncated Session Id " + "on SMSFTP_FIN: FX7%V4B>2:M~12>%W*h="); import java.util.Arrays; } import java.util.HashSet; catch(PDUMalformedException e) { } // do nothing import java.util.Set; } import java.util.UUID; import java.nio.ByteBuffer; @Category(ExceptionTests.class) @Test public void coreFinMalformedSessionIdShouldThrowPDUMalformedException() import static org.junit.Assert.assertEquals; throws PDUMalformedException, InvalidCRCException { import static org.junit.Assert.assertNotEquals; try { import static org.junit.Assert.assertTrue; SmsftpBinaryPduDecoder.getInstance().decodeBinary( import static org.junit.Assert.assertFalse; "fFX7%V4B>2¿Mf12>%W*h=050068"); import static org.junit.Assert.fail; // P-Sessionid------StatCr fail("expected PDUMalformedException on malformed Session Id " + import org.junit.experimental.categories.Category; "on SMSFTP_FIN: FX7%V4B>2¿Mf12>%W*h="); import org.junit.Test; } catch(PDUMalformedException e) { } // do nothing try { // with Additional Message field SmsftpBinaryPduDecoder.getInstance().decodeBinary( public class BinaryDecoderTest { "fFX7%V4B>2¿Mf12>%W*h=0500This is a test messageb7"); // ERROR TEST CASES // P-Sessionid------StatAdditional*Message****Cr fail("expected PDUMalformedException on malformed Session Id " + // General "on SMSFTP_FIN: FX7%V4B>2¿Mf12>%W*h="); @Category(ExceptionTests.class) } @Test(expected = NullPointerException.class) catch(PDUMalformedException e) { } // do nothing public void nullShouldThrowNullPointerException() } throws PDUMalformedException, InvalidCRCException { SmsftpBinaryPduDecoder.getInstance().decodeBinary(null); @Category(ExceptionTests.class) } @Test(expected = PDUMalformedException.class) public void coreFinShortStatusCodeShouldThrowPDUMalformedException() @Category(ExceptionTests.class) throws PDUMalformedException, InvalidCRCException { @Test(expected = PDUMalformedException.class) SmsftpBinaryPduDecoder.getInstance().decodeBinary( public void emptyShouldThrowPDUMalformedException() "fFX7%V4B>2:Mf12>%W*h=ERRc5"); throws PDUMalformedException, InvalidCRCException { // P-Sessionid------StatCr SmsftpBinaryPduDecoder.getInstance().decodeBinary(""); } // PCr } @Category(ExceptionTests.class) @Test(expected = PDUMalformedException.class) @Category(ExceptionTests.class) public void coreFinMalformedStatusCodeShouldThrowPDUMalformedException() @Test(expected = PDUMalformedException.class) throws PDUMalformedException, InvalidCRCException { public void tooShortShouldThrowPDUMalformedException() SmsftpBinaryPduDecoder.getInstance().decodeBinary( throws PDUMalformedException, InvalidCRCException { "fFX7%V4B>2:Mf12>%W*h=ERRRec"); SmsftpBinaryPduDecoder.getInstance().decodeBinary("1"); // P-Sessionid------StatCr
131 } PSi-Sessionid------Encrypted-aes-key-segment-part-1------Cr } // CORE_FIN+ACK @Category(ExceptionTests.class) @Category(ExceptionTests.class) @Test(expected = PDUMalformedException.class) @Test(expected = PDUMalformedException.class) public void coreFinackShortSessionIdShouldThrowPDUMalformedException() public void throws PDUMalformedException, InvalidCRCException { smsftpInitEnc1MalformedSessionIdShouldThrowPDUMalformedException() SmsftpBinaryPduDecoder.getInstance().decodeBinary( throws PDUMalformedException, InvalidCRCException { "F123456789012345678940"); SmsftpBinaryPduDecoder.getInstance().decodeBinary( // P-Sessionid------Cr } "a01FX7%V4B>2¿Mf12>%W*h=12345678901234567890123456789012345678901234567890123456 7890123456789012345678901234567890123456789012345678901234567890123456789012345c @Category(ExceptionTests.class) 8"); @Test(expected = PDUMalformedException.class) // public void coreFinackTruncatedSessionIdShouldThrowPDUMalformedException()PSi-Sessionid------Encrypted-aes-key-segment-part-1------throws PDUMalformedException, InvalidCRCException { ------Cr SmsftpBinaryPduDecoder.getInstance().decodeBinary( } "FFX7%V4B>2:M~12>%W*h=41"); // P-Sessionid------Cr } // SMSFTP_META //// SMSFTP_META - General @Category(ExceptionTests.class) @Category(ExceptionTests.class) @Test(expected = PDUMalformedException.class) @Test(expected = PDUMalformedException.class) public void coreFinackMalformedSessionIdShouldThrowPDUMalformedException() public void smsftpMetaGeneralEmptyShouldThrowPDUMalformedException() throws PDUMalformedException, InvalidCRCException { throws PDUMalformedException, InvalidCRCException { SmsftpBinaryPduDecoder.getInstance().decodeBinary( SmsftpBinaryPduDecoder.getInstance().decodeBinary("b29"); "FFX7%V4B>2¿Mf12>%W*h=8d"); // PRCr
132 // PSi-Sessionid------CecSgctSha1------Cr0123456789012345678901234567890123456789012345678901234567890123456789012345678a fail("expected PDUMalformedException on invalid CEC: XZX"); f"); } // catch(PDUMalformedException e) { } // do nothing PSi-Sessionid------CecSgctRsa-signature-segment------try { // CXX ------Cr SmsftpBinaryPduDecoder.getInstance().decodeBinary( }
"b0012345678901234567890CXXs8W*1234567890123456789012345c7"); @Category(ExceptionTests.class) // PSi-Sessionid------CecSgctSha1------Cr @Test fail("expected PDUMalformedException on invalid CEC: CXX"); public void smsftpMetaEnc1InvalidCecFlagsShouldThrowPDUMalformedException() } throws PDUMalformedException, InvalidCRCException { catch(PDUMalformedException e) { } // do nothing try { // XEZ try { // XEX SmsftpBinaryPduDecoder.getInstance().decodeBinary( SmsftpBinaryPduDecoder.getInstance().decodeBinary( "b0112345678901234567890XEZs8W*1234567890123456789012345678901234567890123456789 "b0012345678901234567890XEXs8W*1234567890123456789012345d6"); 01234567890123456789012345678901234567890123456789012345678901234567890123456781 // PSi-Sessionid------CecSgctSha1------Crf"); fail("expected PDUMalformedException on invalid CEC: XEX"); // } PSi-Sessionid------CecSgctRsa-signature-segment------catch(PDUMalformedException e) { } // do nothing ------Cr try { // XEC fail("expected PDUMalformedException on invalid CEC: XEZ"); SmsftpBinaryPduDecoder.getInstance().decodeBinary( } catch(PDUMalformedException e) { } // do nothing "b0012345678901234567890XECs8W*123456789012345678901234508"); try { // XZX // PSi-Sessionid------CecSgctSha1------Cr SmsftpBinaryPduDecoder.getInstance().decodeBinary( fail("expected PDUMalformedException on invalid CEC: XEC"); } "b0112345678901234567890XZXs8W*1234567890123456789012345678901234567890123456789 catch(PDUMalformedException e) { } // do nothing 01234567890123456789012345678901234567890123456789012345678901234567890123456786 } 7"); // @Category(ExceptionTests.class) PSi-Sessionid------CecSgctRsa-signature-segment------@Test(expected = PDUMalformedException.class) ------Cr public void smsftpMetaNoencShortSha1ShouldThrowPDUMalformedException() fail("expected PDUMalformedException on invalid CEC: XZX"); throws PDUMalformedException, InvalidCRCException { } SmsftpBinaryPduDecoder.getInstance().decodeBinary( catch(PDUMalformedException e) { } // do nothing "b0012345678901234567890XXCs8W*12345675a"); try { // ZEX // PSi-Sessionid------CecSgctSha1------Cr SmsftpBinaryPduDecoder.getInstance().decodeBinary( } "b0112345678901234567890ZEXs8W*1234567890123456789012345678901234567890123456789 @Category(ExceptionTests.class) 01234567890123456789012345678901234567890123456789012345678901234567890123456786 @Test(expected = PDUMalformedException.class) b"); public void smsftpMetaNoencLongSha1ShouldThrowPDUMalformedException() // throws PDUMalformedException, InvalidCRCException { PSi-Sessionid------CecSgctRsa-signature-segment------SmsftpBinaryPduDecoder.getInstance().decodeBinary( ------Cr fail("expected PDUMalformedException on invalid CEC: ZEX"); "b0012345678901234567890XXCs8W*123456789012345678901234567890c1"); } // PSi-Sessionid------CecSgctSha1------Cr catch(PDUMalformedException e) { } // do nothing } try { // CXC SmsftpBinaryPduDecoder.getInstance().decodeBinary( @Category(ExceptionTests.class) @Test(expected = PDUMalformedException.class) "b0112345678901234567890CXCs8W*1234567890123456789012345678901234567890123456789 public void smsftpMetaNoencTruncatedSha1ShouldThrowPDUMalformedException()0123456789012345678901234567890123456789012345678901234567890123456789012345678e throws PDUMalformedException, InvalidCRCException { e"); SmsftpBinaryPduDecoder.getInstance().decodeBinary( // "b0012345678901234567890XXCs8W*12345678901234567~9012345c3");PSi-Sessionid------CecSgctRsa-signature-segment------// PSi-Sessionid------CecSgctSha1------Cr------Cr } fail("expected PDUMalformedException on invalid CEC: CXC"); } @Category(ExceptionTests.class) catch(PDUMalformedException e) { } // do nothing @Test(expected = PDUMalformedException.class) try { // XXX public void smsftpMetaNoencMalformedSha1ShouldThrowPDUMalformedException() SmsftpBinaryPduDecoder.getInstance().decodeBinary( throws PDUMalformedException, InvalidCRCException { SmsftpBinaryPduDecoder.getInstance().decodeBinary( "b0112345678901234567890XXXs8W*1234567890123456789012345678901234567890123456789 "b0012345678901234567890XXCs8W*123456¿890123456789012345e8");0123456789012345678901234567890123456789012345678901234567890123456789012345678d // PSi-Sessionid------CecSgctSha1------Cre"); } // PSi-Sessionid------CecSgctRsa-signature-segment------//// SMSFTP_META - Encrypted connections ------Cr @Category(ExceptionTests.class) fail("expected PDUMalformedException on invalid CEC: XXX"); @Test(expected = PDUMalformedException.class) } public void catch(PDUMalformedException e) { } // do nothing smsftpMetaEnc1TruncatedSessionIdShouldThrowPDUMalformedException() } throws PDUMalformedException, InvalidCRCException { SmsftpBinaryPduDecoder.getInstance().decodeBinary( // commented out because this might not make sense on padded "small" "b01123456789~1234567890CECs8W*1234567890123456789012345678901234567890123456789 // segment counts 01234567890123456789012345678901234567890123456789012345678901234567890123456782 /* e"); @Category(ExceptionTests.class) // @Test(expected = PDUMalformedException.class) PSi-Sessionid------CecSgctRsa-signature-segment------public void ------Cr smsftpMetaEnc1TruncatedSegmentCountShouldThrowPDUMalformedException() } throws PDUMalformedException, InvalidCRCException { SmsftpBinaryPduDecoder.getInstance().decodeBinary( @Category(ExceptionTests.class) @Test(expected = PDUMalformedException.class) "b0112345678901234567890XXCs~W*1234567890123456789012345678901234567890123456789 public void 0123456789012345678901234567890123456789012345678901234567890123456789012345678? smsftpMetaEnc1MalformedSessionIdShouldThrowPDUMalformedException() ?"); throws PDUMalformedException, InvalidCRCException { // SmsftpBinaryPduDecoder.getInstance().decodeBinary( PSi-Sessionid------CecSgctRsa-signature-segment------Cr "b01123456789012345¿7890CECs8W*1234567890123456789012345678901234567890123456789 }
133 */ SmsftpBinaryPduDecoder.getInstance().decodeBinary( "E12345678901234~678905e"); @Category(ExceptionTests.class) // P-Sessionid------Cr @Test(expected = PDUMalformedException.class) } public void smsftpMetaEnc1MalformedSegmentCountShouldThrowPDUMalformedException() @Category(ExceptionTests.class) throws PDUMalformedException, InvalidCRCException { @Test(expected = PDUMalformedException.class) SmsftpBinaryPduDecoder.getInstance().decodeBinary( public void smsftpEndwaitMalformedSessionIdShouldThrowPDUMalformedException() "b0112345678901234567890XXC¿8W*1234567890123456789012345678901234567890123456789 throws PDUMalformedException, InvalidCRCException { 01234567890123456789012345678901234567890123456789012345678901234567890123456780 SmsftpBinaryPduDecoder.getInstance().decodeBinary( 7"); "E1234567890123456¿890a5"); // // P-Sessionid------Cr PSi-Sessionid------CecSgctRsa-signature-segment------} ------Cr } // SMSFTP_DATA @Category(ExceptionTests.class) // SMSFTP_READY @Test(expected = PDUMalformedException.class) @Category(ExceptionTests.class) public void smsftpDataShortSegmentIdShouldThrowPDUMalformedException() @Test(expected = PDUMalformedException.class) throws PDUMalformedException, InvalidCRCException { public void smsftpReadyShortSessionIdShouldThrowPDUMalformedException() SmsftpBinaryPduDecoder.getInstance().decodeBinary("ds8*50"); throws PDUMalformedException, InvalidCRCException { // PSgidCr SmsftpBinaryPduDecoder.getInstance().decodeBinary( } "c123456789012345678947"); // P-Sessionid------Cr @Category(ExceptionTests.class) } @Test(expected = PDUMalformedException.class) public void smsftpDataMalformedSegmentIdShouldThrowPDUMalformedException() @Category(ExceptionTests.class) throws PDUMalformedException, InvalidCRCException { @Test(expected = PDUMalformedException.class) SmsftpBinaryPduDecoder.getInstance().decodeBinary("ds8¿*c9"); public void smsftpReadyTruncatedSessionIdShouldThrowPDUMalformedException() // PSgidCr throws PDUMalformedException, InvalidCRCException { } SmsftpBinaryPduDecoder.getInstance().decodeBinary( "c12345678901234~6789000"); // Reason for commenting out: should be impossible to happen + requires // P-Sessionid------Cr // more processing } // - SMSFTP_DATA, SMSFTP_INIT, and SMSFTP_META, SMSFTP_INITIATOR_IDENTITY, // and SMSFTP_RESPONDER_IDENTITY suffers from the same dilemma @Category(ExceptionTests.class) // - for SMSFTP_META.noenc, SHA1 can be converted and checked for @Test(expected = PDUMalformedException.class) // byte[].length public void smsftpReadyMalformedSessionIdShouldThrowPDUMalformedException() /* throws PDUMalformedException, InvalidCRCException { @Category(ExceptionTests.class) SmsftpBinaryPduDecoder.getInstance().decodeBinary( @Test(expected = PDUMalformedException.class) "c1234567890123456¿89038"); public void smsftpDataMalformedBodyShouldThrowPDUMalformedException() // P-Sessionid------Cr throws PDUMalformedException, InvalidCRCException { } SmsftpBinaryPduDecoder.getInstance().decodeBinary("ds8W*¿df"); // PSgidBCr } // SMSFTP_END */ @Category(ExceptionTests.class) @Test(expected = PDUMalformedException.class) public void smsftpEndShortSessionIdShouldThrowPDUMalformedException() throws PDUMalformedException, InvalidCRCException { // NORMAL TEST CASES SmsftpBinaryPduDecoder.getInstance().decodeBinary( // CORE_FIN "e1234567890123456789af"); @Category(RegularTests.class) // P-Sessionid------Cr @Test } public void coreFinValuesShouldBeReturned() throws PDUMalformedException, InvalidCRCException { @Category(ExceptionTests.class) { // without Additional Message field @Test(expected = PDUMalformedException.class) // this is UUID 752500ef3c564de38b6f2d9f0e6d2e72 public void smsftpEndTruncatedSessionIdShouldThrowPDUMalformedException() PDU finPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( throws PDUMalformedException, InvalidCRCException { "fFX7%V4B>2:Mf12>%W*h=000172"); SmsftpBinaryPduDecoder.getInstance().decodeBinary( // P-Sessionid------StatCr "e12345678901234~6789096"); // P-Sessionid------Cr assertEquals(finPDU.getPduType(), CorePDUType.CORE_FIN); } Object[] data = finPDU.getData(); assertEquals(data.length, 2); @Category(ExceptionTests.class) // Session Id @Test(expected = PDUMalformedException.class) assertTrue(data[0] instanceof UUID); public void smsftpEndMalformedSessionIdShouldThrowPDUMalformedException() assertEquals((UUID)data[0], hexStringsToUUID( throws PDUMalformedException, InvalidCRCException { "752500ef3c564de3", "8b6f2d9f0e6d2e72")); SmsftpBinaryPduDecoder.getInstance().decodeBinary( // Status Code "e1234567890123456¿890d3"); assertTrue(data[1] instanceof String); // P-Sessionid------Cr assertTrue("0001".equals((String)(data[1]))); } } { // with Additional Message field PDU finPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( // SMSFTP_END_WAIT "fFX7%V4B>2:Mf12>%W*h=0001This is a test messageb6"); @Category(ExceptionTests.class) // P-Sessionid------StatAdditional*Message****Cr @Test(expected = PDUMalformedException.class) assertEquals(finPDU.getPduType(), CorePDUType.CORE_FIN); public void smsftpEndwaitShortSessionIdShouldThrowPDUMalformedException() Object[] data = finPDU.getData(); throws PDUMalformedException, InvalidCRCException { assertTrue(data.length == 3); SmsftpBinaryPduDecoder.getInstance().decodeBinary( // Session Id "E123456789012345678934"); assertTrue(data[0] instanceof UUID); // P-Sessionid------Cr assertEquals((UUID)data[0], hexStringsToUUID( } "752500ef3c564de3", "8b6f2d9f0e6d2e72")); // Status Code @Category(ExceptionTests.class) assertTrue(data[1] instanceof String); @Test(expected = PDUMalformedException.class) assertTrue("0001".equals((String)(data[1]))); public void // Additional Message smsftpEndwaitTruncatedSessionIdShouldThrowPDUMalformedException() assertTrue(data[2] instanceof String); throws PDUMalformedException, InvalidCRCException { assertTrue("This is a test message".equals((String)(data[2])));
134 } assertTrue(data[0] instanceof PDUType); } assertEquals(data[0], SmsftpPDUType.SMSFTP_DATA); assertTrue(data[1] instanceof Integer); assertEquals(((Integer)data[1]).intValue(), 3095042); // CORE_FIN+ACK @Category(RegularTests.class) assertTrue(data[2] instanceof PDUType); @Test assertEquals(data[2], SmsftpPDUType.SMSFTP_DATA); public void coreFinackValuesShouldBeReturned() assertTrue(data[3] instanceof Integer); throws PDUMalformedException, InvalidCRCException { assertEquals(((Integer)data[3]).intValue(), 3095043); // this is UUID 752500ef3c564de38b6f2d9f0e6d2e72 PDU finackPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( assertTrue(data[4] instanceof PDUType); "FFX7%V4B>2:Mf12>%W*h=05"); assertEquals(data[4], SmsftpPDUType.SMSFTP_DATA); // P-Sessionid------Cr assertTrue(data[5] instanceof Integer); assertEquals(finackPDU.getPduType(), CorePDUType.CORE_FINACK); assertEquals(((Integer)data[5]).intValue(), 310244); Object[] data = finackPDU.getData(); } assertTrue(data.length == 1); { // multi mixed // Session Id PDU retPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( assertTrue(data[0] instanceof UUID); "ra03d0001dtdrlb00f00dkana75"); assertEquals((UUID)data[0], hexStringsToUUID( // PPSiPSgidPSgidPSiPSiPSgidCr "752500ef3c564de3", "8b6f2d9f0e6d2e72")); assertEquals(retPDU.getPduType(), CorePDUType.CORE_RETRANSMISSION); } Object[] data = retPDU.getData(); assertEquals(data.length, 12);
// CORE_RETRANSMISSION assertTrue(data[0] instanceof PDUType); @Category(RegularTests.class) assertEquals(data[0], SmsftpPDUType.SMSFTP_INIT); @Test assertTrue(data[1] instanceof Integer); public void coreRetransmissionValuesShouldBeReturned() assertEquals(((Integer)data[1]).intValue(), 3); throws PDUMalformedException, InvalidCRCException { { // empty assertTrue(data[2] instanceof PDUType); PDU retPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( assertEquals(data[2], SmsftpPDUType.SMSFTP_DATA); "r59"); assertTrue(data[3] instanceof Integer); // PCr assertEquals(((Integer)data[3]).intValue(), 3095043); assertEquals(retPDU.getPduType(), CorePDUType.CORE_RETRANSMISSION); Object[] data = retPDU.getData(); assertTrue(data[4] instanceof PDUType); assertEquals(data.length, 0); assertEquals(data[4], SmsftpPDUType.SMSFTP_DATA); } assertTrue(data[5] instanceof Integer); { // single hex assertEquals(((Integer)data[5]).intValue(), 310244); PDU retPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( "ra01de"); assertTrue(data[6] instanceof PDUType); // PPSiCr assertEquals(data[6], SmsftpPDUType.SMSFTP_META); assertEquals(retPDU.getPduType(), CorePDUType.CORE_RETRANSMISSION); assertTrue(data[7] instanceof Integer); Object[] data = retPDU.getData(); assertEquals(((Integer)data[7]).intValue(), 0); assertEquals(data.length, 2); assertTrue(data[0] instanceof PDUType); assertTrue(data[8] instanceof PDUType); assertEquals(data[0], SmsftpPDUType.SMSFTP_INIT); assertEquals(data[8], CorePDUType.CORE_FIN); assertTrue(data[1] instanceof Integer); assertTrue(data[9] instanceof Integer); assertEquals(((Integer)data[1]).intValue(), 1); assertEquals(((Integer)data[9]).intValue(), 0); } { // single base85 assertTrue(data[10] instanceof PDUType); PDU retPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( assertEquals(data[10], SmsftpPDUType.SMSFTP_DATA); "rd000083"); assertTrue(data[11] instanceof Integer); // PPSgidCr assertEquals(((Integer)data[11]).intValue(), 15244969); assertEquals(retPDU.getPduType(), CorePDUType.CORE_RETRANSMISSION); } Object[] data = retPDU.getData(); } assertEquals(data.length, 2); assertTrue(data[0] instanceof PDUType); @Category(RegularTests.class) assertEquals(data[0], SmsftpPDUType.SMSFTP_DATA); @Test assertTrue(data[1] instanceof Integer); public void coreRetransmissionImperfectValuesShouldBeReturned() assertEquals(((Integer)data[1]).intValue(), 3095042); throws PDUMalformedException, InvalidCRCException { } { // last segment cut short { // multi hex PDU retPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( PDU retPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( "ra01f00dwat36"); "ra03b01c00e3"); // PPSiPSiPSgidCr // PPSiPSiPSiCr assertEquals(retPDU.getPduType(), CorePDUType.CORE_RETRANSMISSION); assertEquals(retPDU.getPduType(), CorePDUType.CORE_RETRANSMISSION); Object[] data = retPDU.getData(); Object[] data = retPDU.getData(); assertEquals(data.length, 4); assertEquals(data.length, 6); assertTrue(data[0] instanceof PDUType); assertTrue(data[0] instanceof PDUType); assertEquals(data[0], SmsftpPDUType.SMSFTP_INIT); assertEquals(data[0], SmsftpPDUType.SMSFTP_INIT); assertTrue(data[1] instanceof Integer); assertTrue(data[1] instanceof Integer); assertEquals(((Integer)data[1]).intValue(), 1); assertEquals(((Integer)data[1]).intValue(), 3); assertTrue(data[2] instanceof PDUType); assertTrue(data[2] instanceof PDUType); assertEquals(data[2], CorePDUType.CORE_FIN); assertEquals(data[2], SmsftpPDUType.SMSFTP_META); assertTrue(data[3] instanceof Integer); assertTrue(data[3] instanceof Integer); assertEquals(((Integer)data[3]).intValue(), 0); assertEquals(((Integer)data[3]).intValue(), 1); // remaining is unreadable } assertTrue(data[4] instanceof PDUType); { // invalid PDU prefix assertEquals(data[4], SmsftpPDUType.SMSFTP_READY); PDU retPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( assertTrue(data[5] instanceof Integer); "ra03x01c0060"); assertEquals(((Integer)data[5]).intValue(), 0); // PPSiPSiPSiCr } assertEquals(retPDU.getPduType(), CorePDUType.CORE_RETRANSMISSION); { // multi base85 Object[] data = retPDU.getData(); PDU retPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( assertEquals(data.length, 4); "rd0000d0001dtdrl75"); // PPSgidPSgidPSgidCr assertTrue(data[0] instanceof PDUType); assertEquals(retPDU.getPduType(), CorePDUType.CORE_RETRANSMISSION); assertEquals(data[0], SmsftpPDUType.SMSFTP_INIT); Object[] data = retPDU.getData(); assertTrue(data[1] instanceof Integer); assertEquals(data.length, 6); assertEquals(((Integer)data[1]).intValue(), 3);
135 // 'x' PDU is unknown -> continue "a011234567890123456789012345678901234567890123456789012345678901234567890123456 assertTrue(data[2] instanceof PDUType); 78901234567890123456789012345678901234567890123456789012345678901234567890123458 assertEquals(data[2], SmsftpPDUType.SMSFTP_READY); 2"); assertTrue(data[3] instanceof Integer); // assertEquals(((Integer)data[3]).intValue(), 0); PSi-Sessionid------Encrypted-aes-key-segment-part-1------} ------Cr { // invalid hex (format) assertEquals(initPDU.getPduType(), SmsftpPDUType.SMSFTP_INIT); PDU retPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( Object[] data = initPDU.getData(); "rf00ixxa00e3"); assertEquals(data.length, 3); // PPSiPSiPSiCr assertTrue(data[0] instanceof Integer); assertEquals(retPDU.getPduType(), CorePDUType.CORE_RETRANSMISSION); assertEquals(data[0], 1); Object[] data = retPDU.getData(); // Session Id assertEquals(data.length, 4); assertTrue(data[1] instanceof UUID); assertEquals((UUID)data[1], hexStringsToUUID( assertTrue(data[0] instanceof PDUType); "32699b3242279c09", "32699b3242279c09")); assertEquals(data[0], CorePDUType.CORE_FIN); // Encrypted AES Segment assertTrue(data[1] instanceof Integer); assertTrue(data[2] instanceof String); assertEquals(((Integer)data[1]).intValue(), 0); assertEquals((String)data[2], "1234567890123456789012345678901234567890123456789012345678901234567890123456789 // 'xx' hex is invalid 01234567890123456789012345678901234567890123456789012345"); } assertTrue(data[2] instanceof PDUType); assertEquals(data[2], SmsftpPDUType.SMSFTP_INIT); @Category(RegularTests.class) assertTrue(data[3] instanceof Integer); @Test assertEquals(((Integer)data[3]).intValue(), 0); public void smsftpInitEnc2and3ValuesShouldBeReturned() } throws PDUMalformedException, InvalidCRCException { { // invalid segment id (format) { // part 2 PDU retPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( PDU initPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( "ra01ds8W¿dtdrlb00f00dkanaca"); // PPSiPSgidPSgidPSiPSiPSgidCr "a021234567890123456789012345678901234567890123456789012345678901234567890123456 assertEquals(retPDU.getPduType(), CorePDUType.CORE_RETRANSMISSION);7890123456789012345678901234567890123456789012345678901234567890123456789012345f Object[] data = retPDU.getData(); 3"); assertEquals(data.length, 10); // PSi-Encrypted-aes-key-segment-part-2------assertTrue(data[0] instanceof PDUType); ------Cr assertEquals(data[0], SmsftpPDUType.SMSFTP_INIT); assertEquals(initPDU.getPduType(), SmsftpPDUType.SMSFTP_INIT); assertTrue(data[1] instanceof Integer); Object[] data = initPDU.getData(); assertEquals(((Integer)data[1]).intValue(), 1); assertEquals(data.length, 3); assertTrue(data[0] instanceof Integer); // 's8W¿' segment id is invalid assertEquals(data[0], 2); // Session Id assertTrue(data[2] instanceof PDUType); assertTrue(data[1] instanceof UUID); assertEquals(data[2], SmsftpPDUType.SMSFTP_DATA); assertEquals((UUID)data[1], hexStringsToUUID( assertTrue(data[3] instanceof Integer); "32699b3242279c09", "32699b3242279c09")); assertEquals(((Integer)data[3]).intValue(), 310244); // Encrypted AES Segment assertTrue(data[2] instanceof String); assertTrue(data[4] instanceof PDUType); assertEquals((String)data[2], assertEquals(data[4], SmsftpPDUType.SMSFTP_META); "1234567890123456789012345678901234567890123456789012345678901234567890123456789 assertTrue(data[5] instanceof Integer); 01234567890123456789012345678901234567890123456789012345"); assertEquals(((Integer)data[5]).intValue(), 0); } { // part 3 assertTrue(data[6] instanceof PDUType); PDU initPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( assertEquals(data[6], CorePDUType.CORE_FIN); assertTrue(data[7] instanceof Integer); "a031234567890123456789012345678901234567890123456789012345678901234567890123456 assertEquals(((Integer)data[7]).intValue(), 0); 7890123456789012345678901234567890123456789012345678901234567890123456789012345d c"); assertTrue(data[8] instanceof PDUType); // assertEquals(data[8], SmsftpPDUType.SMSFTP_DATA); PSi-Encrypted-aes-key-segment-part-3------assertTrue(data[9] instanceof Integer); ------Cr assertEquals(((Integer)data[9]).intValue(), 15244969); assertEquals(initPDU.getPduType(), SmsftpPDUType.SMSFTP_INIT); } Object[] data = initPDU.getData(); // NOTE: for invalid segment id for specific PDU (i.e. '4' for 'i' assertEquals(data.length, 3); // PDU, it should be simply ignored by the protocol assertTrue(data[0] instanceof Integer); } assertEquals(data[0], 3); // Session Id assertTrue(data[1] instanceof UUID); // SMSFTP_INIT assertEquals((UUID)data[1], hexStringsToUUID( @Category(RegularTests.class) "32699b3242279c09", "32699b3242279c09")); @Test // Encrypted AES Segment public void smsftpInitNoencValuesShouldBeReturned() assertTrue(data[2] instanceof String); throws PDUMalformedException, InvalidCRCException { assertEquals((String)data[2], PDU initPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( "1234567890123456789012345678901234567890123456789012345678901234567890123456789 "a0012345678901234567890c1"); 01234567890123456789012345678901234567890123456789012345"); // PSi-Sessionid------Cr } assertEquals(initPDU.getPduType(), SmsftpPDUType.SMSFTP_INIT); } Object[] data = initPDU.getData(); assertEquals(data.length, 2); assertTrue(data[0] instanceof Integer); // SMSFTP_META assertEquals(data[0], 0); @Category(RegularTests.class) // Session Id @Test assertTrue(data[1] instanceof UUID); public void smsftpMetaNoencValuesShouldBeReturned() assertEquals((UUID)data[1], hexStringsToUUID( throws PDUMalformedException, InvalidCRCException { "32699b3242279c09", "32699b3242279c09")); PDU metaPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( } "b0012345678901234567890XXCs8W*s8W-!s8W-!s8W-!s8W-!s8W-!67"); // PSi-Sessionid------CecSgctSha1------Cr @Category(RegularTests.class) assertEquals(metaPDU.getPduType(), SmsftpPDUType.SMSFTP_META); @Test Object[] data = metaPDU.getData(); public void smsftpInitEnc1ValuesShouldBeReturned() assertEquals(data.length, 5); throws PDUMalformedException, InvalidCRCException { assertTrue(data[0] instanceof Integer); PDU initPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( assertEquals(data[0], 0);
136 // Session Id assertEquals(data.length, 3); assertTrue(data[1] instanceof UUID); assertTrue(data[0] instanceof Integer); assertEquals((UUID)data[1], hexStringsToUUID( assertEquals(data[0], 3); "32699b3242279c09", "32699b3242279c09")); // Session id // CEC assertTrue(data[1] instanceof UUID); assertTrue(data[2] instanceof String); assertEquals((UUID)data[1], hexStringsToUUID( assertEquals("XXC", data[2]); "32699b3242279c09", "32699b3242279c09")); // Segment Count // RSA Signature segment assertTrue(data[3] instanceof Integer); assertTrue(data[2] instanceof String); assertEquals((Integer)data[3], new Integer(16777215)); assertEquals((String)data[2], // SHA1 Checksum "1234567890123456789012345678901234567890123456789012345678901234567890123456789 assertTrue(data[4] instanceof byte[]); 01234567890123456789012345678901234567890123456789012345"); assertTrue(Arrays.equals((byte[])data[4], } javax.xml.bind.DatatypeConverter.parseHexBinary( } "ffffffffffffffffffffffffffffffffffffffff"))); } // SMSFTP_READY @Category(RegularTests.class) @Category(RegularTests.class) @Test @Test public void smsftpMetaEnc1ValuesShouldBeReturned() public void smsftpReadyValuesShouldBeReturned() throws PDUMalformedException, InvalidCRCException { throws PDUMalformedException, InvalidCRCException { // Segment count is 16777215 (or 'ffffff') PDU readyPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( PDU metaPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( "c1234567890123456789042"); // P-Sessionid------Cr "b0112345678901234567890CECs8W*1234567890123456789012345678901234567890123456789 assertEquals(readyPDU.getPduType(), SmsftpPDUType.SMSFTP_READY); 01234567890123456789012345678901234567890123456789012345678901234567890123456787 Object[] data = readyPDU.getData(); d"); assertEquals(data.length, 1); // // Session id PSi-Sessionid------CecSgctRsa-signature-segment------assertTrue(data[0] instanceof UUID); ------Cr assertEquals((UUID)data[0], hexStringsToUUID( assertEquals(metaPDU.getPduType(), SmsftpPDUType.SMSFTP_META); "32699b3242279c09", "32699b3242279c09")); Object[] data = metaPDU.getData(); } assertEquals(data.length, 5); assertTrue(data[0] instanceof Integer); assertEquals(data[0], 1); // SMSFTP_DATA // Session id @Category(RegularTests.class) assertTrue(data[1] instanceof UUID); @Test assertEquals((UUID)data[1], hexStringsToUUID( public void smsftpDataValuesShouldBeReturned() "32699b3242279c09", "32699b3242279c09")); throws PDUMalformedException, InvalidCRCException { // CEC { // emtpy Data (this is still perfectly acceptable :) ) assertTrue(data[2] instanceof String); // Segment Id in hex: "ffffff" assertEquals(data[2], "CEC"); PDU dataPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( // Segment count "ds8W*2a"); assertTrue(data[3] instanceof Integer); // PSgidCr assertEquals((Integer)data[3], new Integer(16777215)); assertEquals(dataPDU.getPduType(), SmsftpPDUType.SMSFTP_DATA); // RSA Signature Segment Object[] data = dataPDU.getData(); assertTrue(data[4] instanceof String); assertEquals(data.length, 2); assertEquals((String)data[4], // Segment Id "1234567890123456789012345678901234567890123456789012345678901234567890123456789 assertTrue(data[0] instanceof Integer); 0123456789012345678901234567890123456789012345678"); assertEquals(((Integer)data[0]).intValue(), 16777215); } } { // with Data @Category(RegularTests.class) // Segment Id in hex: "ffffff" @Test PDU dataPDU = public void smsftpMetaEnc2and3RsaSigSegmentShouldBeReturned() SmsftpBinaryPduDecoder.getInstance().decodeBinary( throws PDUMalformedException, InvalidCRCException { "ds8W*Sample Bodycb"); { // part 2 // PSgid-Data------Cr PDU metaPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( assertEquals(dataPDU.getPduType(), SmsftpPDUType.SMSFTP_DATA); Object[] data = dataPDU.getData(); "b021234567890123456789012345678901234567890123456789012345678901234567890123456 assertEquals(data.length, 2); 78901234567890123456789012345678901234567890123456789012345678901234567890123454 // Segment Id 4"); assertTrue(data[0] instanceof Integer); // assertEquals(((Integer)data[0]).intValue(), 16777215); PSi-Rsa-signature-segment------// Data ------Cr assertTrue(data[1] instanceof String); assertEquals(metaPDU.getPduType(), SmsftpPDUType.SMSFTP_META); assertEquals(data[1], "Sample Body"); Object[] data = metaPDU.getData(); } assertEquals(data.length, 3); } assertTrue(data[0] instanceof Integer); assertEquals(data[0], 2); // Session id // SMSFTP_INITIATOR_IDENTITY assertTrue(data[1] instanceof UUID); @Category(RegularTests.class) assertEquals((UUID)data[1], hexStringsToUUID( @Test "32699b3242279c09", "32699b3242279c09")); public void smsftpInitiatorIdentityValuesShouldBeReturned() // RSA Signature segment throws PDUMalformedException, InvalidCRCException { assertTrue(data[2] instanceof String); { // part 1 assertEquals((String)data[2], PDU iidPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( "1234567890123456789012345678901234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789012345"); "i011234567890123456789012345678901234567890123456789012345678901234567890123456 } 7890123456789012345678901234567890123456789012345678901234567890123456789012345e { // part 3 3"); PDU metaPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( // PSi-Rsa-public-key------"b031234567890123456789012345678901234567890123456789012345678901234567890123456------Cr 78901234567890123456789012345678901234567890123456789012345678901234567890123456 assertEquals(iidPDU.getPduType(), b"); SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY); // Object[] data = iidPDU.getData(); PSi-Rsa-signature-segment------assertEquals(data.length, 2); ------Cr // Segment Id assertEquals(metaPDU.getPduType(), SmsftpPDUType.SMSFTP_META); assertTrue(data[0] instanceof Integer); Object[] data = metaPDU.getData(); assertEquals(((Integer)data[0]).intValue(), 1);
137 // RSA Public Key "1234567890123456789012345678901234567890123456789012345678901234567890123456789 assertTrue(data[1] instanceof String); 0123456789012345678901234567890123456789012345678901234567890123456789012345"); assertEquals((String)data[1], } "1234567890123456789012345678901234567890123456789012345678901234567890123456789 } 0123456789012345678901234567890123456789012345678901234567890123456789012345"); } // SMSFTP_RESPONDER_IDENTITY { // part 2 @Category(RegularTests.class) PDU iidPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( @Test public void smsftpResponderIdentityValuesShouldBeReturned() "i021234567890123456789012345678901234567890123456789012345678901234567890123456 throws PDUMalformedException, InvalidCRCException { 78901234567890123456789012345678901234567890123456789012345678901234567890123459 { // part 1 2"); PDU ridPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( // PSi-Rsa-public-key------"j011234567890123456789012345678901234567890123456789012345678901234567890123456 ------Cr 78901234567890123456789012345678901234567890123456789012345678901234567890123455 assertEquals(iidPDU.getPduType(), 4"); SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY); // Object[] data = iidPDU.getData(); PSi-Rsa-public-key------assertEquals(data.length, 2); ------Cr // Segment Id assertEquals(ridPDU.getPduType(), assertTrue(data[0] instanceof Integer); SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY); assertEquals(((Integer)data[0]).intValue(), 2); Object[] data = ridPDU.getData(); // RSA Public Key assertEquals(data.length, 2); assertTrue(data[1] instanceof String); // Segment Id assertEquals((String)data[1], assertTrue(data[0] instanceof Integer); "1234567890123456789012345678901234567890123456789012345678901234567890123456789 assertEquals(((Integer)data[0]).intValue(), 1); 0123456789012345678901234567890123456789012345678901234567890123456789012345"); // RSA Public Key } assertTrue(data[1] instanceof String); { // part 3 assertEquals((String)data[1], PDU iidPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary("1234567890123456789012345678901234567890123456789012345678901234567890123456789 0123456789012345678901234567890123456789012345678901234567890123456789012345"); "i031234567890123456789012345678901234567890123456789012345678901234567890123456 } 7890123456789012345678901234567890123456789012345678901234567890123456789012345b { // part 2 d"); PDU ridPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( // PSi-Rsa-public-key------"j021234567890123456789012345678901234567890123456789012345678901234567890123456 ------Cr 78901234567890123456789012345678901234567890123456789012345678901234567890123452 assertEquals(iidPDU.getPduType(), 5"); SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY); // Object[] data = iidPDU.getData(); PSi-Rsa-public-key------assertEquals(data.length, 2); ------Cr // Segment Id assertEquals(ridPDU.getPduType(), assertTrue(data[0] instanceof Integer); SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY); assertEquals(((Integer)data[0]).intValue(), 3); Object[] data = ridPDU.getData(); // RSA Public Key assertEquals(data.length, 2); assertTrue(data[1] instanceof String); // Segment Id assertEquals((String)data[1], assertTrue(data[0] instanceof Integer); "1234567890123456789012345678901234567890123456789012345678901234567890123456789 assertEquals(((Integer)data[0]).intValue(), 2); 0123456789012345678901234567890123456789012345678901234567890123456789012345"); // RSA Public Key } assertTrue(data[1] instanceof String); { // part 4 assertEquals((String)data[1], PDU iidPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary("1234567890123456789012345678901234567890123456789012345678901234567890123456789 0123456789012345678901234567890123456789012345678901234567890123456789012345"); "i041234567890123456789012345678901234567890123456789012345678901234567890123456 } 78901234567890123456789012345678901234567890123456789012345678901234567890123457 { // part 3 0"); PDU ridPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( // PSi-Rsa-public-key------"j031234567890123456789012345678901234567890123456789012345678901234567890123456 ------Cr 78901234567890123456789012345678901234567890123456789012345678901234567890123450 assertEquals(iidPDU.getPduType(), a"); SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY); // Object[] data = iidPDU.getData(); PSi-Rsa-public-key------assertEquals(data.length, 2); ------Cr // Segment Id assertEquals(ridPDU.getPduType(), assertTrue(data[0] instanceof Integer); SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY); assertEquals(((Integer)data[0]).intValue(), 4); Object[] data = ridPDU.getData(); // RSA Public Key assertEquals(data.length, 2); assertTrue(data[1] instanceof String); // Segment Id assertEquals((String)data[1], assertTrue(data[0] instanceof Integer); "1234567890123456789012345678901234567890123456789012345678901234567890123456789 assertEquals(((Integer)data[0]).intValue(), 3); 0123456789012345678901234567890123456789012345678901234567890123456789012345"); // RSA Public Key } assertTrue(data[1] instanceof String); { // part 5 assertEquals((String)data[1], PDU iidPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary("1234567890123456789012345678901234567890123456789012345678901234567890123456789 0123456789012345678901234567890123456789012345678901234567890123456789012345"); "i051234567890123456789012345678901234567890123456789012345678901234567890123456 } 78901234567890123456789012345678901234567890123456789012345678901234567890123455 { // part 4 f"); PDU ridPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( // PSi-Rsa-public-key------"j041234567890123456789012345678901234567890123456789012345678901234567890123456 ------Cr 7890123456789012345678901234567890123456789012345678901234567890123456789012345c assertEquals(iidPDU.getPduType(), 7"); SmsftpPDUType.SMSFTP_INITIATOR_IDENTITY); // Object[] data = iidPDU.getData(); PSi-Rsa-public-key------assertEquals(data.length, 2); ------Cr // Segment Id assertEquals(ridPDU.getPduType(), assertTrue(data[0] instanceof Integer); SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY); assertEquals(((Integer)data[0]).intValue(), 5); Object[] data = ridPDU.getData(); // RSA Public Key assertEquals(data.length, 2); assertTrue(data[1] instanceof String); // Segment Id assertEquals((String)data[1], assertTrue(data[0] instanceof Integer);
138 assertEquals(((Integer)data[0]).intValue(), 4); // // RSA Public Key PSi-Sessionid------CecSgctRsa-signature-segment------assertTrue(data[1] instanceof String); ------Cr assertEquals((String)data[1], fail("expected PDUMalformedException on invalid Segment Id " + "1234567890123456789012345678901234567890123456789012345678901234567890123456789 "on SMSFTP_META: 04"); 0123456789012345678901234567890123456789012345678901234567890123456789012345"); } } catch(PDUMalformedException e) { } // do nothing { // part 5 try { // 0X PDU ridPDU = SmsftpBinaryPduDecoder.getInstance().decodeBinary( SmsftpBinaryPduDecoder.getInstance().decodeBinary(
"j051234567890123456789012345678901234567890123456789012345678901234567890123456"b0X12345678901234567890CECs8W*1234567890123456789012345678901234567890123456789 7890123456789012345678901234567890123456789012345678901234567890123456789012345e01234567890123456789012345678901234567890123456789012345678901234567890123456782 8"); 7"); // // PSi-Rsa-public-key------PSi-Sessionid------CecSgctRsa-signature-segment------Cr ------Cr assertEquals(ridPDU.getPduType(), fail("expected PDUMalformedException on invalid Segment Id " + SmsftpPDUType.SMSFTP_RESPONDER_IDENTITY); "on SMSFTP_META: 0X"); Object[] data = ridPDU.getData(); } assertEquals(data.length, 2); catch(PDUMalformedException e) { } // do nothing // Segment Id try { // G0 assertTrue(data[0] instanceof Integer); SmsftpBinaryPduDecoder.getInstance().decodeBinary( assertEquals(((Integer)data[0]).intValue(), 5); // RSA Public Key "bG012345678901234567890CECs8W*1234567890123456789012345678901234567890123456789 assertTrue(data[1] instanceof String); 0123456789012345678901234567890123456789012345678901234567890123456789012345678f assertEquals((String)data[1], d"); "1234567890123456789012345678901234567890123456789012345678901234567890123456789 // 0123456789012345678901234567890123456789012345678901234567890123456789012345");PSi-Sessionid------CecSgctRsa-signature-segment------} ------Cr } fail("expected PDUMalformedException on invalid Segment Id " + "on SMSFTP_META: G0"); } // Multi-tests catch(PDUMalformedException e) { } // do nothing //// Includes the following: //// - SMSFTP_INIT //// SMSFTP_INITIATOR_IDENTITY //// - SMSFTP_META try { // 00 //// - SMSFTP_INITIATOR_IDENTITY SmsftpBinaryPduDecoder.getInstance().decodeBinary( //// - SMSFTP_RESPONDER_IDENTITY //// - SMSFTP_DATA "i001234567890123456789012345678901234567890123456789012345678901234567890123456 @Category(ExceptionTests.class) 7890123456789012345678901234567890123456789012345678901234567890123456789012345c @Test c"); public void smsftpMultiInvalidSegmentIdShouldThrowPDUMalformedException() // throws PDUMalformedException, InvalidCRCException { PSi-RSA-public-key------//// SMSFTP_INIT ------Cr try { // 04 fail("expected PDUMalformedException on invalid Segment Id " + SmsftpBinaryPduDecoder.getInstance().decodeBinary( "on SMSFTP_INITIATOR_IDENTITY: 00"); } "a041234567890123456789012345678901234567890123456789012345678901234567890123456 catch(PDUMalformedException e) { } // do nothing 78901234567890123456789012345678901234567890123456789012345678901234567890123451 try { // 06 1"); SmsftpBinaryPduDecoder.getInstance().decodeBinary( // PSi-Sessionid------Encrypted-aes-key-segment-part-1------"i061234567890123456789012345678901234567890123456789012345678901234567890123456 ------Cr 78901234567890123456789012345678901234567890123456789012345678901234567890123452 fail("expected PDUMalformedException on invalid Segment Id " +e"); "on SMSFTP_INIT: 04"); // } PSi-RSA-public-key------catch(PDUMalformedException e) { } // do nothing ------Cr try { // 0X fail("expected PDUMalformedException on invalid Segment Id " + SmsftpBinaryPduDecoder.getInstance().decodeBinary( "on SMSFTP_INITIATOR_IDENTITY: 06"); } "a0X1234567890123456789012345678901234567890123456789012345678901234567890123456 catch(PDUMalformedException e) { } // do nothing 7890123456789012345678901234567890123456789012345678901234567890123456789012345d try { // 0X 8"); SmsftpBinaryPduDecoder.getInstance().decodeBinary( // PSi-Sessionid------Encrypted-aes-key-segment-part-1------"i0X1234567890123456789012345678901234567890123456789012345678901234567890123456 ------Cr 7890123456789012345678901234567890123456789012345678901234567890123456789012345b fail("expected PDUMalformedException on invalid Segment Id " +9"); "on SMSFTP_INIT: 0X"); // } PSi-RSA-public-key------catch(PDUMalformedException e) { } // do nothing ------Cr try { // G0 fail("expected PDUMalformedException on invalid Segment Id " + SmsftpBinaryPduDecoder.getInstance().decodeBinary( "on SMSFTP_INITIATOR_IDENTITY: 0X"); } "aG01234567890123456789012345678901234567890123456789012345678901234567890123456 catch(PDUMalformedException e) { } // do nothing 78901234567890123456789012345678901234567890123456789012345678901234567890123450 try { // G0 2"); SmsftpBinaryPduDecoder.getInstance().decodeBinary( // PSi-Sessionid------Encrypted-aes-key-segment-part-1------"iG01234567890123456789012345678901234567890123456789012345678901234567890123456 ------Cr 78901234567890123456789012345678901234567890123456789012345678901234567890123456 fail("expected PDUMalformedException on invalid Segment Id " +3"); "on SMSFTP_INIT: G0"); // } PSi-RSA-public-key------catch(PDUMalformedException e) { } // do nothing ------Cr fail("expected PDUMalformedException on invalid Segment Id " + //// SMSFTP_META "on SMSFTP_INITIATOR_IDENTITY: G0"); try { // 04 } SmsftpBinaryPduDecoder.getInstance().decodeBinary( catch(PDUMalformedException e) { } // do nothing
"b0412345678901234567890CECs8W*1234567890123456789012345678901234567890123456789 //// SMSFTP_RESPONDER_IDENTITY 0123456789012345678901234567890123456789012345678901234567890123456789012345678e try { // 00 e"); SmsftpBinaryPduDecoder.getInstance().decodeBinary(
139
"j001234567890123456789012345678901234567890123456789012345678901234567890123456import org.junit.experimental.categories.Category; 78901234567890123456789012345678901234567890123456789012345678901234567890123457 b"); import org.junit.Test; // PSi-RSA-public-key------Cr fail("expected PDUMalformedException on invalid Segment Id " +public class BiometricFingerprintTest { "on SMSFTP_RESPONDER_IDENTITY: 00"); @Category(RegularTests.class) } @Test catch(PDUMalformedException e) { } // do nothing public void fingerprintShouldBeReturned() { try { // 06 byte[] testInput1 = {}; SmsftpBinaryPduDecoder.getInstance().decodeBinary( String testResult1 = "surrender classroom pandemic tycoon finicky " + "glitter disable ancient component edict " + "j061234567890123456789012345678901234567890123456789012345678901234567890123456 "rebellion uncut Montana facial borderline " + 78901234567890123456789012345678901234567890123456789012345678901234567890123459 "peachy pharmacy stormy amusement Algol"; 9"); assertEquals(testResult1, // BiometricFingerprint.getBiometricFingerprint(testInput1)); PSi-RSA-public-key------Cr byte[] testInput2 = {0x46, 0x75, 0x63, 0x6b, 0x0a}; fail("expected PDUMalformedException on invalid Segment Id " + String testResult2 = "speculate bluebird millionaire spearhead " + "on SMSFTP_RESPONDER_IDENTITY: 06"); "graduate offload hurricane music repellent " + } "backfield Hamilton tissue combustion " + catch(PDUMalformedException e) { } // do nothing "sweatband Atlantic merit pedigree Geiger " + try { // 0X "Apollo quiver"; SmsftpBinaryPduDecoder.getInstance().decodeBinary( assertEquals(testResult2, BiometricFingerprint.getBiometricFingerprint(testInput2)); "j0X1234567890123456789012345678901234567890123456789012345678901234567890123456 } 78901234567890123456789012345678901234567890123456789012345678901234567890123450 e"); @Category(ExceptionTests.class) // @Test(expected = NullPointerException.class) PSi-RSA-public-key------public void nullShouldThrowNullPointerException() { ------Cr BiometricFingerprint.getBiometricFingerprint(null); fail("expected PDUMalformedException on invalid Segment Id " + } "on SMSFTP_RESPONDER_IDENTITY: 0X"); } } catch(PDUMalformedException e) { } // do nothing try { // G0 SmsftpBinaryPduDecoder.getInstance().decodeBinary( ------"jG01234567890123456789012345678901234567890123456789012345678901234567890123456Filename: src/test/unit/java/CoreProtocolTest.java 7890123456789012345678901234567890123456789012345678901234567890123456789012345d------4"); // import com.transmisms.core.protocol.CoreLookupTable; PSi-RSA-public-key------Cr import static org.junit.Assert.assertEquals; fail("expected PDUMalformedException on invalid Segment Id " +import static org.junit.Assert.assertNotEquals; "on SMSFTP_RESPONDER_IDENTITY: G0"); import static org.junit.Assert.assertTrue; } import static org.junit.Assert.assertFalse; catch(PDUMalformedException e) { } // do nothing import org.junit.experimental.categories.Category; //// SMSFTP_DATA try { // malformed import org.junit.Test; SmsftpBinaryPduDecoder.getInstance().decodeBinary( "ds8W¿Sample Body03"); // PSgid-Data------Cr fail("expected PDUMalformedException on malformed Segment Id "public + class CoreProtocolTest { "on SMSFTP_RESPONDER_IDENTITY: s8W¿"); } @Category(RegularTests.class) catch(PDUMalformedException e) { } // do nothing @Test } public void coreLookupTableHealthCheck() { CoreLookupTable clt = new CoreLookupTable(); /** } * } * */ private UUID hexStringsToUUID(String msb, String lsb) { ByteBuffer msbBuffer = ByteBuffer.allocate(Long.BYTES).put( javax.xml.bind.DatatypeConverter.parseHexBinary(msb)); ------ByteBuffer lsbBuffer = ByteBuffer.allocate(Long.BYTES).put( Filename: src/test/unit/java/DecoderTest.java javax.xml.bind.DatatypeConverter.parseHexBinary(lsb)); ------msbBuffer.flip(); // required for reading lsbBuffer.flip(); // required for reading import com.transmisms.core.protocol.Connection; return new UUID(msbBuffer.getLong(), lsbBuffer.getLong()); import com.transmisms.core.protocol.CoreProtocolFacade; } import com.transmisms.core.protocol.CorePDUType; } import com.transmisms.core.protocol.PDUType; import com.transmisms.smsftp.protocol.SmsftpFacade; import com.transmisms.smsftp.protocol.SmsftpSession;
import com.transmisms.core.protocol.PDUMalformedException; ------import com.transmisms.core.protocol.InvalidCRCException; Filename: src/test/unit/java/BiometricFingerprintTest.java ------import java.util.HashSet; import java.util.Set; import com.transmisms.core.util.crypto.BiometricFingerprint; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import static org.junit.Assert.fail;
140 import org.junit.experimental.categories.Category; import org.junit.experimental.categories.Category; import org.junit.Test; import org.junit.Test; public class DecoderTest { private static CoreProtocolFacade generateTestFacade() { public class RSAPEMImportExportTest { return new SmsftpFacade(SmsftpSession.generateInitiatorPairSession( Connection.createResponderConnection(null)), null); @Category(RegularTests.class) } @Test public void x509ShouldBeReturned() throws RSADecoderException { // ERROR TEST CASES byte[] pemFileByteArray1 = ("-----BEGIN PUBLIC KEY-----\n" + @Category(ExceptionTests.class) "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1SgRBuihq+k9vWwuGBlE\n" + @Test(expected = NullPointerException.class) "rB4XntzDtTlE53wL48F/Re49BrX8X2cczneLwGLDSTj34w0AZ2zHK9Ue/NmpLtrv\n" + public void nullShouldThrowNullPointerException() "sxVaFzhFOLZ7o9kcI/ncbNktlyrNmyeMDL0tZI84lvTWfcxf7pFxaXotef+y5RIm\n" + throws PDUMalformedException, InvalidCRCException { "RYMOI5kv00dSgujHhM/ToUar2/bITKVVo7bhsA1hgat7YeuD7EJBPb6n061IPNtN\n" + CoreProtocolFacade f = DecoderTest.generateTestFacade(); "Ew0TonCp82kqg+bYKOssTXcxMsuOBGvp+ZwNkneY7b2IzF7ZtDs746FBNF9qLI0B\n" + f.decode(null); "nqBdUA0n9JdPJCltIYaMmUIgtcv9tXjbpY1Y3FAIh4/Mg4J7KoGWIRoXXGrcjiW/\n" + } "7QIDAQAB\n" + "-----END PUBLIC KEY-----").getBytes(); @Category(ExceptionTests.class) @Test(expected = PDUMalformedException.class) byte[] pemFileByteArray2 = ("-----BEGIN PUBLIC KEY-----\n" + public void emptyShouldThrowPDUMalformedException() "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3wgs9G9FWccRDQd9d++n\n" + throws PDUMalformedException, InvalidCRCException { "OT6HGsrHDzdyw63HoCQMtMm2mibDAWAV5m9bi7KmFY6wralYmPADpzp2s0O+CFb6\n" + CoreProtocolFacade f = DecoderTest.generateTestFacade(); "1X/H7Ftm9En79mJZEy0BPbNd+8+/AFd0v1q3Jzx8wOhm92+OyICGXheKMAXDx944\n" + f.decode(""); "lgAAFPxyYOVwnP+Xu2B/odjniQNr0NM0ZsJRGcsIrKfOzuVBiXAMtECR9eFGVIuB\n" + } "1U2xNzrptjaPXQef6ETWNuCejaN/3DmmZXOfZRB/cWIhrnFCspi1yYDI0NJpUmS5\n" + "THShYpF4hjbwrETbXW75CNXSa0KK38YvPaspprR6ja0a4I3+Gt20hwdw2afHW2Kg\n" + "QQIDAQAB\n" + // NORMAL TEST CASES "-----END PUBLIC KEY-----").getBytes(); // HUMAN_REJECT @Category(RegularTests.class) byte[] expectedX509Format1 = Base64.decodeBase64( @Test "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1SgRBuihq+k9vW" + public void noShouldReturnHumanRejectPdu() "wuGBlErB4XntzDtTlE53wL48F/Re49BrX8X2cczneLwGLDSTj34w0AZ2zH" + throws PDUMalformedException, InvalidCRCException { "K9Ue/NmpLtrvsxVaFzhFOLZ7o9kcI/ncbNktlyrNmyeMDL0tZI84lvTWfc" + CoreProtocolFacade f = DecoderTest.generateTestFacade(); "xf7pFxaXotef+y5RImRYMOI5kv00dSgujHhM/ToUar2/bITKVVo7bhsA1h" + assertEquals(CorePDUType.HUMAN_REJECT, f.decode("NO").getPduType()); "gat7YeuD7EJBPb6n061IPNtNEw0TonCp82kqg+bYKOssTXcxMsuOBGvp+Z" + assertEquals(CorePDUType.HUMAN_REJECT, f.decode("no").getPduType()); "wNkneY7b2IzF7ZtDs746FBNF9qLI0BnqBdUA0n9JdPJCltIYaMmUIgtcv9" + assertEquals(CorePDUType.HUMAN_REJECT, f.decode("No").getPduType()); "tXjbpY1Y3FAIh4/Mg4J7KoGWIRoXXGrcjiW/7QIDAQAB"); assertEquals(CorePDUType.HUMAN_REJECT, f.decode("nO").getPduType()); byte[] expectedX509Format2 = Base64.decodeBase64( assertEquals(CorePDUType.HUMAN_REJECT, f.decode(" NO ").getPduType()); "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3wgs9G9FWccRDQ" + assertEquals(CorePDUType.HUMAN_REJECT, f.decode("NO ").getPduType()); "d9d++nOT6HGsrHDzdyw63HoCQMtMm2mibDAWAV5m9bi7KmFY6wralYmPAD" + assertEquals(CorePDUType.HUMAN_REJECT, f.decode(" NO").getPduType()); "pzp2s0O+CFb61X/H7Ftm9En79mJZEy0BPbNd+8+/AFd0v1q3Jzx8wOhm92" + assertEquals(CorePDUType.HUMAN_REJECT, f.decode("NO\n").getPduType()); "+OyICGXheKMAXDx944lgAAFPxyYOVwnP+Xu2B/odjniQNr0NM0ZsJRGcsI" + assertEquals(CorePDUType.HUMAN_REJECT, "rKfOzuVBiXAMtECR9eFGVIuB1U2xNzrptjaPXQef6ETWNuCejaN/3DmmZX" + f.decode("NO\n\n").getPduType()); "OfZRB/cWIhrnFCspi1yYDI0NJpUmS5THShYpF4hjbwrETbXW75CNXSa0KK" + assertEquals(CorePDUType.HUMAN_REJECT, f.decode("NO \n").getPduType()); "38YvPaspprR6ja0a4I3+Gt20hwdw2afHW2KgQQIDAQAB"); } } assertTrue(Arrays.equals(expectedX509Format1, RSA.readPublicKeyFromPEM(new ByteArrayInputStream( pemFileByteArray1)))); assertTrue(Arrays.equals(expectedX509Format2, RSA.readPublicKeyFromPEM(new ByteArrayInputStream( ------pemFileByteArray2)))); Filename: src/test/unit/java/ExceptionTests.java } ------@Category(RegularTests.class) public interface ExceptionTests {} @Test public void pemShouldBeWritten() throws IOException { String expectedPEMString1 = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1SgRBuihq+k9vWwuGBlE\n" + "rB4XntzDtTlE53wL48F/Re49BrX8X2cczneLwGLDSTj34w0AZ2zHK9Ue/NmpLtrv\n" + ------"sxVaFzhFOLZ7o9kcI/ncbNktlyrNmyeMDL0tZI84lvTWfcxf7pFxaXotef+y5RIm\n" + Filename: src/test/unit/java/RegularTests.java "RYMOI5kv00dSgujHhM/ToUar2/bITKVVo7bhsA1hgat7YeuD7EJBPb6n061IPNtN\n" + ------"Ew0TonCp82kqg+bYKOssTXcxMsuOBGvp+ZwNkneY7b2IzF7ZtDs746FBNF9qLI0B\n" + "nqBdUA0n9JdPJCltIYaMmUIgtcv9tXjbpY1Y3FAIh4/Mg4J7KoGWIRoXXGrcjiW/\n" + public interface RegularTests {} "7QIDAQAB\n" + "-----END PUBLIC KEY-----";
String expectedPEMString2 = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3wgs9G9FWccRDQd9d++n\n" + ------"OT6HGsrHDzdyw63HoCQMtMm2mibDAWAV5m9bi7KmFY6wralYmPADpzp2s0O+CFb6\n" + Filename: src/test/unit/java/RSAPEMImportExportTest.java "1X/H7Ftm9En79mJZEy0BPbNd+8+/AFd0v1q3Jzx8wOhm92+OyICGXheKMAXDx944\n" + ------"lgAAFPxyYOVwnP+Xu2B/odjniQNr0NM0ZsJRGcsIrKfOzuVBiXAMtECR9eFGVIuB\n" + "1U2xNzrptjaPXQef6ETWNuCejaN/3DmmZXOfZRB/cWIhrnFCspi1yYDI0NJpUmS5\n" + import com.transmisms.core.util.crypto.RSA; "THShYpF4hjbwrETbXW75CNXSa0KK38YvPaspprR6ja0a4I3+Gt20hwdw2afHW2Kg\n" + import com.transmisms.core.util.crypto.RSADecoderException; "QQIDAQAB\n" + "-----END PUBLIC KEY-----"; import org.apache.commons.codec.binary.Base64; byte[] x509Format1 = Base64.decodeBase64( "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1SgRBuihq+k9vW" + import java.io.ByteArrayInputStream; "wuGBlErB4XntzDtTlE53wL48F/Re49BrX8X2cczneLwGLDSTj34w0AZ2zH" + import java.io.ByteArrayOutputStream; "K9Ue/NmpLtrvsxVaFzhFOLZ7o9kcI/ncbNktlyrNmyeMDL0tZI84lvTWfc" + import java.io.IOException; "xf7pFxaXotef+y5RImRYMOI5kv00dSgujHhM/ToUar2/bITKVVo7bhsA1h" + import java.util.Arrays; "gat7YeuD7EJBPb6n061IPNtNEw0TonCp82kqg+bYKOssTXcxMsuOBGvp+Z" + "wNkneY7b2IzF7ZtDs746FBNF9qLI0BnqBdUA0n9JdPJCltIYaMmUIgtcv9" + import static org.junit.Assert.assertEquals; "tXjbpY1Y3FAIh4/Mg4J7KoGWIRoXXGrcjiW/7QIDAQAB"); import static org.junit.Assert.assertNotEquals; byte[] x509Format2 = Base64.decodeBase64( import static org.junit.Assert.assertTrue; "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3wgs9G9FWccRDQ" + import static org.junit.Assert.assertFalse; "d9d++nOT6HGsrHDzdyw63HoCQMtMm2mibDAWAV5m9bi7KmFY6wralYmPAD" + import static org.junit.Assert.fail; "pzp2s0O+CFb61X/H7Ftm9En79mJZEy0BPbNd+8+/AFd0v1q3Jzx8wOhm92" +
141 "+OyICGXheKMAXDx944lgAAFPxyYOVwnP+Xu2B/odjniQNr0NM0ZsJRGcsI" + "Ew0TonCp82kqg+bYKOssTXcxMsuOBGvp+ZwNkneY7b2IzF7ZtDs746FBNF9qLI0B\n" + "rKfOzuVBiXAMtECR9eFGVIuB1U2xNzrptjaPXQef6ETWNuCejaN/3DmmZX" + "nqBdUA0n9JdPJCltIYaMmUIgtcv9tXjbpY1Y3FAIh4/Mg4J7KoGWIRoXXGrcjiW/\n" + "OfZRB/cWIhrnFCspi1yYDI0NJpUmS5THShYpF4hjbwrETbXW75CNXSa0KK" + "7QIDAQAB").getBytes(); "38YvPaspprR6ja0a4I3+Gt20hwdw2afHW2KgQQIDAQAB"); RSA.readPublicKeyFromPEM(new ByteArrayInputStream(pemFileByteArray)); { } ByteArrayOutputStream baos = new ByteArrayOutputStream(); String expectedPEMString = expectedPEMString1; @Category(ExceptionTests.class) byte[] x509Format = x509Format1; @Test(expected = RSADecoderException.class) public void mismatchedPEMHeadersShouldThrowRSADecoderException() RSA.writePublicKeyToPEM(x509Format, baos); throws RSADecoderException { byte[] pemFileByteArray = ("-----BEGIN PUBLIC KEY-----\n" + // uses default charset (for just the test) "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1SgRBuihq+k9vWwuGBlE\n" + String PEMString = baos.toString(); "rB4XntzDtTlE53wL48F/Re49BrX8X2cczneLwGLDSTj34w0AZ2zHK9Ue/NmpLtrv\n" + assertEquals(expectedPEMString, PEMString.trim()); "sxVaFzhFOLZ7o9kcI/ncbNktlyrNmyeMDL0tZI84lvTWfcxf7pFxaXotef+y5RIm\n" + } "RYMOI5kv00dSgujHhM/ToUar2/bITKVVo7bhsA1hgat7YeuD7EJBPb6n061IPNtN\n" + { "Ew0TonCp82kqg+bYKOssTXcxMsuOBGvp+ZwNkneY7b2IzF7ZtDs746FBNF9qLI0B\n" + ByteArrayOutputStream baos = new ByteArrayOutputStream(); "nqBdUA0n9JdPJCltIYaMmUIgtcv9tXjbpY1Y3FAIh4/Mg4J7KoGWIRoXXGrcjiW/\n" + String expectedPEMString = expectedPEMString2; "7QIDAQAB\n" + byte[] x509Format = x509Format2; "-----END PUBIC KEY-----").getBytes(); RSA.readPublicKeyFromPEM(new ByteArrayInputStream(pemFileByteArray)); RSA.writePublicKeyToPEM(x509Format, baos); }
// uses default charset (for just the test) @Category(ExceptionTests.class) String PEMString = baos.toString(); @Test(expected = RSADecoderException.class) assertEquals(expectedPEMString, PEMString.trim()); public void misalignedPEMHeadersShouldThrowRSADecoderException() } throws RSADecoderException { } byte[] pemFileByteArray = ("NOISE\n-----BEGIN PUBLIC KEY-----\n\n" + "-----END PUBLIC KEY-----").getBytes(); /* RSA.readPublicKeyFromPEM(new ByteArrayInputStream(pemFileByteArray)); * NOTE: } * Exception classes: } * - IOException * - NoSuchAlgorithmException * - InvalidKeySpecException */ ------/* Filename: src/test/unit/java/SegmentManagerTest.java * Misc cases: ------* - empty * - null import com.transmisms.core.protocol.SegmentManager; * - header/footer problems * - missing header/footer import static org.junit.Assert.assertEquals; * - wrong header+footer import static org.junit.Assert.assertNotEquals; * - mismatched header and footer import static org.junit.Assert.assertTrue; */ import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; /* * Reading: import org.junit.experimental.categories.Category; * - PemReader.readPemObject() * - IOException import org.junit.Test; * - bc:DecoderException */
/* public class SegmentManagerTest { * Writing: @Category(RegularTests.class) * - PemWriter.writeObject() @Test * - IOException public void orderedUpdatesShouldComplete() { */ { // single-part SegmentManager sm = new SegmentManager(1); @Category(ExceptionTests.class) sm.updateSegment("test", 0); @Test(expected = RSADecoderException.class) assertEquals(sm.getCompletedString(), "test"); public void emptyFileShouldThrowRSADecoderException() } throws RSADecoderException { { // multi-part byte[] pemFileByteArray = ("").getBytes(); SegmentManager sm = new SegmentManager(4); RSA.readPublicKeyFromPEM(new ByteArrayInputStream(pemFileByteArray)); sm.updateSegment("t", 0); } sm.updateSegment("e", 1); sm.updateSegment("s", 2); @Category(ExceptionTests.class) sm.updateSegment("t", 3); @Test(expected = RSADecoderException.class) assertEquals(sm.getCompletedString(), "test"); public void headlessFileShouldThrowRSADecoderException() } throws RSADecoderException { } byte[] pemFileByteArray = ( "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1SgRBuihq+k9vWwuGBlE\n" +@Category(RegularTests.class) "rB4XntzDtTlE53wL48F/Re49BrX8X2cczneLwGLDSTj34w0AZ2zHK9Ue/NmpLtrv\n" +@Test "sxVaFzhFOLZ7o9kcI/ncbNktlyrNmyeMDL0tZI84lvTWfcxf7pFxaXotef+y5RIm\n" +public void unorderedUpdatesShouldComplete() { "RYMOI5kv00dSgujHhM/ToUar2/bITKVVo7bhsA1hgat7YeuD7EJBPb6n061IPNtN\n" + { "Ew0TonCp82kqg+bYKOssTXcxMsuOBGvp+ZwNkneY7b2IzF7ZtDs746FBNF9qLI0B\n" + SegmentManager sm = new SegmentManager(4); "nqBdUA0n9JdPJCltIYaMmUIgtcv9tXjbpY1Y3FAIh4/Mg4J7KoGWIRoXXGrcjiW/\n" + sm.updateSegment("e", 1); "7QIDAQAB").getBytes(); sm.updateSegment("s", 2); RSA.readPublicKeyFromPEM(new ByteArrayInputStream(pemFileByteArray)); sm.updateSegment("t", 3); } sm.updateSegment("t", 0); assertEquals(sm.getCompletedString(), "test"); @Category(ExceptionTests.class) } @Test(expected = RSADecoderException.class) { public void missingFooterShouldThrowRSADecoderException() SegmentManager sm = new SegmentManager(4); throws RSADecoderException { sm.updateSegment("e", 1); byte[] pemFileByteArray = ("-----BEGIN PUBLIC KEY-----\n" + sm.updateSegment("s", 2); "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1SgRBuihq+k9vWwuGBlE\n" + sm.updateSegment("t", 0); "rB4XntzDtTlE53wL48F/Re49BrX8X2cczneLwGLDSTj34w0AZ2zHK9Ue/NmpLtrv\n" + sm.updateSegment("t", 3); "sxVaFzhFOLZ7o9kcI/ncbNktlyrNmyeMDL0tZI84lvTWfcxf7pFxaXotef+y5RIm\n" + assertEquals(sm.getCompletedString(), "test"); "RYMOI5kv00dSgujHhM/ToUar2/bITKVVo7bhsA1hgat7YeuD7EJBPb6n061IPNtN\n" + }
142 { } SegmentManager sm = new SegmentManager(4); sm.updateSegment("t", 3); sm.updateSegment("s", 2); sm.updateSegment("e", 1); sm.updateSegment("t", 0); assertEquals(sm.getCompletedString(), "test"); ------} Filename: src/test/unit/java/SessionTest.java { // abc P0 ------SegmentManager sm = new SegmentManager(3); sm.updateSegment("a", 0); import com.transmisms.core.protocol.Session; sm.updateSegment("b", 1); sm.updateSegment("c", 2); import java.util.Arrays; assertEquals(sm.getCompletedString(), "abc"); } import static org.junit.Assert.assertEquals; { // abc P1 import static org.junit.Assert.assertNotEquals; SegmentManager sm = new SegmentManager(3); import static org.junit.Assert.assertTrue; sm.updateSegment("a", 0); import static org.junit.Assert.assertFalse; sm.updateSegment("c", 2); sm.updateSegment("b", 1); import org.junit.experimental.categories.Category; assertEquals(sm.getCompletedString(), "abc"); } import org.junit.Test; { // abc P2 SegmentManager sm = new SegmentManager(3); public class SessionTest { sm.updateSegment("b", 1); sm.updateSegment("a", 0); // constructor tests sm.updateSegment("c", 2); assertEquals(sm.getCompletedString(), "abc"); // getSessionId() tests } { // abc P3 SegmentManager sm = new SegmentManager(3); // initializeData(int) tests sm.updateSegment("c", 2); } sm.updateSegment("b", 1); sm.updateSegment("a", 0); assertEquals(sm.getCompletedString(), "abc"); } { // abc P4 ------SegmentManager sm = new SegmentManager(3); Filename: src/test/unit/java/SmsftpTest.java sm.updateSegment("b", 1); ------sm.updateSegment("c", 2); sm.updateSegment("a", 0); import com.transmisms.smsftp.protocol.SmsftpLookupTable; assertEquals(sm.getCompletedString(), "abc"); } import static org.junit.Assert.assertEquals; { // abc P5 import static org.junit.Assert.assertNotEquals; SegmentManager sm = new SegmentManager(3); import static org.junit.Assert.assertTrue; sm.updateSegment("c", 2); import static org.junit.Assert.assertFalse; sm.updateSegment("a", 0); sm.updateSegment("b", 1); import org.junit.experimental.categories.Category; assertEquals(sm.getCompletedString(), "abc"); } import org.junit.Test; }
@Category(RegularTests.class) @Test public class SmsftpTest { public void duplicateUpdatesShouldComplete() { { @Category(RegularTests.class) SegmentManager sm = new SegmentManager(4); @Test sm.updateSegment("t", 0); public void smsftpLookupTableHealthCheck() { sm.updateSegment("t", 0); SmsftpLookupTable slt = new SmsftpLookupTable(); sm.updateSegment("e", 1); } sm.updateSegment("e", 1); } sm.updateSegment("s", 2); sm.updateSegment("s", 2); sm.updateSegment("t", 3); sm.updateSegment("t", 3); assertEquals(sm.getCompletedString(), "test"); ------} Filename: src/test/unit/java/TextBasedDecoderTest.java { ------SegmentManager sm = new SegmentManager(4); sm.updateSegment("t", 3); import com.transmisms.core.protocol.CorePDUType; sm.updateSegment("t", 3); import com.transmisms.core.protocol.PDU; sm.updateSegment("s", 2); import com.transmisms.core.protocol.PDUType; sm.updateSegment("s", 2); import com.transmisms.core.protocol.PDUMalformedException; sm.updateSegment("e", 1); import com.transmisms.core.protocol.Version; sm.updateSegment("e", 1); import com.transmisms.smsftp.protocol.SmsftpTextBasedPduDecoder; sm.updateSegment("t", 0); sm.updateSegment("t", 0); import java.util.UUID; assertEquals(sm.getCompletedString(), "test"); import java.nio.ByteBuffer; } { import static org.junit.Assert.assertTrue; SegmentManager sm = new SegmentManager(4); import static org.junit.Assert.assertFalse; sm.updateSegment("t", 3); import static org.junit.Assert.assertEquals; sm.updateSegment("s", 2); import static org.junit.Assert.assertNotEquals; sm.updateSegment("e", 1); import static org.junit.Assert.assertNull; sm.updateSegment("t", 3); import static org.junit.Assert.fail; sm.updateSegment("s", 2); sm.updateSegment("e", 1); import org.junit.experimental.categories.Category; sm.updateSegment("t", 0); import org.junit.Test; assertEquals(sm.getCompletedString(), "test"); } }
143 "This is an extra message"); public class TextBasedDecoderTest { } /** * @Category(ExceptionTests.class) * @Test(expected = PDUMalformedException.class) */ public void generalMalformedSessionIdShouldThrowPDUMalformedException() private UUID hexStringsToUUID(String msb, String lsb) { throws PDUMalformedException { ByteBuffer msbBuffer = ByteBuffer.allocate(Long.BYTES).put( SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( javax.xml.bind.DatatypeConverter.parseHexBinary(msb)); "?transmismsv0.7.0\n" + ByteBuffer lsbBuffer = ByteBuffer.allocate(Long.BYTES).put( "1234567890123¿567890\n" + javax.xml.bind.DatatypeConverter.parseHexBinary(lsb)); "head/smsftp/enc\n" + msbBuffer.flip(); // required for reading "\n" + // extra \n for additional message lsbBuffer.flip(); // required for reading "This is an extra message"); return new UUID(msbBuffer.getLong(), lsbBuffer.getLong()); } } @Category(ExceptionTests.class) @Test(expected = PDUMalformedException.class) public void generalInvalidHeaderShouldThrowPDUMalformedException() throws PDUMalformedException { // ERROR TEST CASES SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( @Category(ExceptionTests.class) "?otr10\n" + @Test "12345678901234567890\n" + public void generalMalformedVersionShouldThrowPDUMalformedException() "head/smsftp/enc\n" + throws PDUMalformedException { "\n" + // extra \n for additional message try { // version is a tuple "This is an extra message"); SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( fail("expected PDUMalformedException on invalid Header: " + "?transmismsv0.7\n" + "?otr10"); "12345678901234567890\n" + } "head/smsftp/enc\n" + "\n" + // extra \n for additional message @Category(ExceptionTests.class) "This is an extra message"); @Test fail("expected PDUMalformedException on malformed Version: " + public void generalPayloadTooLongShouldThrowPDUMalformedException() "0.7"); throws PDUMalformedException { } try { // CORE_CONNECTION_REQUEST/send catch(PDUMalformedException e) {} // do nothing SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( try { // version is a quadruple "?transmismsv0.7.0\n" + SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( "12345678901234567890\n" + "?transmismsv0.7.0.1\n" + "req/smsftp/+639121234567/send/magic.txt/blah\n" + "12345678901234567890\n" + "\n" + // extra \n for additional message "head/smsftp/enc\n" + "This is an extra message"); "\n" + // extra \n for additional message fail("expected PDUMalformedException with long Payload"); "This is an extra message"); } fail("expected PDUMalformedException on malformed Version: " + catch(PDUMalformedException e) {} // do nothing "0.7.0.1"); try { // CORE_CONNECTION_REQUEST/pair } SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( catch(PDUMalformedException e) {} // do nothing "?transmismsv0.7.0\n" + try { // non-numeric version "number" "12345678901234567890\n" + SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( "req/smsftp/+639121234567/pair/blah\n" + "?transmismsvx.7.0\n" + "\n" + // extra \n for additional message "12345678901234567890\n" + "This is an extra message"); "head/smsftp/enc\n" + fail("expected PDUMalformedException with long Payload"); "\n" + // extra \n for additional message } "This is an extra message"); catch(PDUMalformedException e) {} // do nothing fail("expected PDUMalformedException on malformed Version: " + try { // CORE_CONNECTION_RESPONSE "x.7.0"); SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( } "?transmismsv0.7.0\n" + catch(PDUMalformedException e) {} // do nothing "12345678901234567890\n" + try { // negative version number "rep/smsftp/0002/Accepted by user/blah\n" + SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( "\n" + // extra \n for additional message "?transmismsv0.7.-1\n" + "This is an extra message"); "12345678901234567890\n" + fail("expected PDUMalformedException with long Payload"); "head/smsftp/enc\n" + } "\n" + // extra \n for additional message catch(PDUMalformedException e) {} // do nothing "This is an extra message"); try { // CORE_HEAD fail("expected PDUMalformedException on malformed Version: " + SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( "0.7.-1"); "?transmismsv0.7.0\n" + } "12345678901234567890\n" + catch(PDUMalformedException e) {} // do nothing "head/smsftp/enc/blah\n" + } "\n" + // extra \n for additional message "This is an extra message"); @Category(ExceptionTests.class) fail("expected PDUMalformedException with long Payload"); @Test(expected = PDUMalformedException.class) } public void generalShortSessionIdShouldThrowPDUMalformedException() catch(PDUMalformedException e) {} // do nothing throws PDUMalformedException { try { // CORE_LAST_ACK SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( "?transmismsv0.7.0\n" + "?transmismsv0.7.0\n" + "1234567890123456789\n" + "12345678901234567890\n" + "head/smsftp/enc\n" + "last/smsftp/0001/Completed without error/blah\n" + "\n" + // extra \n for additional message "\n" + // extra \n for additional message "This is an extra message"); "This is an extra message"); } fail("expected PDUMalformedException with long Payload"); } @Category(ExceptionTests.class) catch(PDUMalformedException e) {} // do nothing @Test(expected = PDUMalformedException.class) } public void generalTruncatedSessionIdShouldThrowPDUMalformedException() throws PDUMalformedException { @Category(ExceptionTests.class) SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( @Test "?transmismsv0.7.0\n" + public void generalPayloadTooShortShouldThrowPDUMalformedException() "123456789~1234567890\n" + throws PDUMalformedException { "head/smsftp/enc\n" + try { // CORE_CONNECTION_REQUEST/send "\n" + // extra \n for additional message SmsftpTextBasedPduDecoder.getInstance().decodeTextBased(
144 "?transmismsv0.7.0\n" + "This is an extra message"); "12345678901234567890\n" + fail("expected PDUMalformedException on invalid MSISDN/MIN: " + "req/smsftp/+639121234567/send\n" + "0912123+567"); "\n" + // extra \n for additional message } "This is an extra message"); catch(PDUMalformedException e) { } // do nothing fail("expected PDUMalformedException with short Payload"); } } catch(PDUMalformedException e) {} // do nothing @Category(ExceptionTests.class) // CORE_CONNECTION_REQUEST/pair doesn't make sense here @Test(expected = PDUMalformedException.class) try { // CORE_CONNECTION_RESPONSE public void SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( coreConnectionRequestInvalidIntentShouldThrowPDUMalformedException() "?transmismsv0.7.0\n" + throws PDUMalformedException { "12345678901234567890\n" + SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( "rep/smsftp\n" + "?transmismsv0.7.0\n" + "\n" + // extra \n for additional message "12345678901234567890\n" + "This is an extra message"); "req/smsftp/+639121234567/post\n" + fail("expected PDUMalformedException with short Payload"); "\n" + // extra \n for additional message } "This is an extra message"); catch(PDUMalformedException e) {} // do nothing } try { // CORE_HEAD SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( @Category(ExceptionTests.class) "?transmismsv0.7.0\n" + @Test(expected = PDUMalformedException.class) "12345678901234567890\n" + public void "head/smsftp\n" + coreConnectionRequestInvalidFilenameShouldThrowPDUMalformedException() "\n" + // extra \n for additional message throws PDUMalformedException { "This is an extra message"); SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( fail("expected PDUMalformedException with short Payload"); "?transmismsv0.7.0\n" + } "12345678901234567890\n" + catch(PDUMalformedException e) {} // do nothing "req/smsftp/+639121234567/send/test*file.zip\n" + try { // CORE_LAST_ACK "\n" + // extra \n for additional message SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( "This is an extra message"); "?transmismsv0.7.0\n" + } "12345678901234567890\n" + "last/smsftp\n" + "\n" + // extra \n for additional message // CORE_CONNECTION_RESPONSE "This is an extra message"); @Category(ExceptionTests.class) fail("expected PDUMalformedException with short Payload"); @Test(expected = PDUMalformedException.class) } public void catch(PDUMalformedException e) {} // do nothing coreConnectionResponseInvalidStatusCodeShouldThrowPDUMalformedException() } throws PDUMalformedException { SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( @Category(ExceptionTests.class) "?transmismsv0.7.0\n" + @Test(expected = PDUMalformedException.class) "12345678901234567890\n" + public void generalInvalidPayloadTypeShouldThrowPDUMalformedException() "rep/smsftp/9999/Accepted by user\n" + throws PDUMalformedException { "\n" + // extra \n for additional message SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( "This is an extra message"); "?transmismsv0.7.0\n" + } "12345678901234567890\n" + "data/smsftp/3dff01a9c072\n" + "\n" + // extra \n for additional message // CORE_HEAD "This is an extra message"); @Category(ExceptionTests.class) fail("expected PDUMalformedException on invalid Payload Type: " + @Test(expected = PDUMalformedException.class) "data"); public void coreHeadInvalidEncFieldShouldThrowPDUMalformedException() } throws PDUMalformedException { SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( @Category(ExceptionTests.class) "?transmismsv0.7.0\n" + @Test(expected = PDUMalformedException.class) "12345678901234567890\n" + public void generalInvalidProtocolNameShouldThrowPDUMalformedException() "head/smsftp/INVALID/Some sort of message\n" + throws PDUMalformedException { "\n" + SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( "This is an extra message"); "?transmismsv0.7.0\n" + } "12345678901234567890\n" + "rep/smsssh/22/root@10.11.102.193\n" + "\n" + // extra \n for additional message // CORE_LAST_ACK "This is an extra message"); @Category(ExceptionTests.class) } @Test(expected = PDUMalformedException.class) public void coreLastAckInvalidStatusCodeShouldThrowPDUMalformedException() throws PDUMalformedException { // CORE_CONNECTION_REQUEST SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( @Category(ExceptionTests.class) "?transmismsv0.7.0\n" + @Test "12345678901234567890\n" + public void "last/smsftp/XXXX/Completed without error\n" + coreConnectionRequestInvalidMsisdnShouldThrowPDUMalformedException() "\n" + // extra \n for additional message throws PDUMalformedException { "This is an extra message"); try { // non digits } SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( "?transmismsv0.7.0\n" + "12345678901234567890\n" + "req/smsftp/+63912A234567/send/testfile.zip\n" + // NORMAL TEST CASES "\n" + // extra \n for additional message "This is an extra message"); // CORE_CONNECTION_REQUEST fail("expected PDUMalformedException on invalid MSISDN/MIN: " + @Category(RegularTests.class) "+63912A234567"); @Test } public void coreConnectionRequestValuesShouldBeReturned() catch(PDUMalformedException e) { } // do nothing throws PDUMalformedException { try { // 'plus' sign in the middle of msisdn/min { // send SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( PDU connReqPDU = "?transmismsv0.7.0\n" + SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( "12345678901234567890\n" + "?transmismsv0.7.0\n" + "req/smsftp/0912123+567/pair\n" + "12345678901234567890\n" + "\n" + // extra \n for additional message "req/smsftp/+639871234567/send/testfile.zip\n" +
145 "\n" + // extra \n for additional message // Receiver MSISDN/MIN "This is an extra message"); assertTrue(data[3] instanceof String); assertEquals(connReqPDU.getPduType(), assertEquals((String)data[3], "09871234567"); CorePDUType.CORE_CONNECTION_REQUEST); // Intent Object[] data = connReqPDU.getData(); assertTrue(data[4] instanceof String); assertEquals(data.length, 7); assertEquals((String)data[4], "pair"); // Protocol Version // Additional Message assertTrue(data[0] instanceof Version); assertTrue(data[5] instanceof String); assertEquals((Version)data[0], new Version(0, 7, 0)); assertEquals((String)data[5], "This is an extra message"); // Session Id } assertTrue(data[1] instanceof UUID); } assertEquals((UUID)data[1], hexStringsToUUID( "32699b3242279c09", "32699b3242279c09")); // Transmisms Protocol // CORE_CONNECTION_RESPONSE assertTrue(data[2] instanceof String); @Category(RegularTests.class) assertEquals((String)data[2], "smsftp"); @Test // Receiver MSISDN/MIN public void coreConnectionResponseValuesShouldBeReturned() assertTrue(data[3] instanceof String); throws PDUMalformedException { assertEquals((String)data[3], "+639871234567"); { // with status message // Intent PDU connResPDU = assertTrue(data[4] instanceof String); SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( assertEquals((String)data[4], "send"); "?transmismsv0.7.0\n" + // Filename "12345678901234567890\n" + assertTrue(data[5] instanceof String); "rep/smsftp/0002/Accepted by user\n" + assertEquals((String)data[5], "testfile.zip"); "\n" + // extra \n for additional message // Additional Message "This is an extra message"); assertTrue(data[6] instanceof String); assertEquals(connResPDU.getPduType(), assertEquals((String)data[6], "This is an extra message"); CorePDUType.CORE_CONNECTION_RESPONSE); } Object[] data = connResPDU.getData(); { // send 2 assertEquals(data.length, 6); PDU connReqPDU = // Protocol Version SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( assertTrue(data[0] instanceof Version); "?transmismsv0.7.0\n" + assertEquals((Version)data[0], new Version(0, 7, 0)); "12345678901234567890\n" + // Session Id assertTrue(data[1] instanceof UUID); "req/smsftp/+639871234567/send/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW assertEquals((UUID)data[1], hexStringsToUUID( XYZ0123456789 .-#$%&'()+,i=@_\n" + "32699b3242279c09", "32699b3242279c09")); "\n" + // extra \n for additional message // Transmisms Protocol "This is an extra message too"); assertTrue(data[2] instanceof String); assertEquals(connReqPDU.getPduType(), assertEquals((String)data[2], "smsftp"); CorePDUType.CORE_CONNECTION_REQUEST); // Status Code Object[] data = connReqPDU.getData(); assertTrue(data[3] instanceof String); assertEquals(data.length, 7); assertEquals((String)data[3], "0002"); // Protocol Version // Status Message assertTrue(data[0] instanceof Version); assertTrue(data[4] instanceof String); assertEquals((Version)data[0], new Version(0, 7, 0)); assertEquals((String)data[4], "Accepted by user"); // Session Id // Additional Message assertTrue(data[1] instanceof UUID); assertTrue(data[5] instanceof String); assertEquals((UUID)data[1], hexStringsToUUID( assertEquals((String)data[5], "This is an extra message"); "32699b3242279c09", "32699b3242279c09")); } // Transmisms Protocol { // missing status message assertTrue(data[2] instanceof String); PDU connResPDU = assertEquals((String)data[2], "smsftp"); SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( // Receiver MSISDN/MIN "?transmismsv0.7.0\n" + assertTrue(data[3] instanceof String); "12345678901234567890\n" + assertEquals((String)data[3], "+639871234567"); "rep/smsftp/0002\n" + // Intent "\n" + // extra \n for additional message assertTrue(data[4] instanceof String); "This is an extra message"); assertEquals((String)data[4], "send"); assertEquals(connResPDU.getPduType(), // Filename CorePDUType.CORE_CONNECTION_RESPONSE); assertTrue(data[5] instanceof String); Object[] data = connResPDU.getData(); assertEquals((String)data[5], assertEquals(data.length, 6); // Protocol Version "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 assertTrue(data[0] instanceof Version); .-#$%&'()+,i=@_"); assertEquals((Version)data[0], new Version(0, 7, 0)); // Additional Message // Session Id assertTrue(data[6] instanceof String); assertTrue(data[1] instanceof UUID); assertEquals((String)data[6], "This is an extra message too"); assertEquals((UUID)data[1], hexStringsToUUID( } "32699b3242279c09", "32699b3242279c09")); { // pair // Transmisms Protocol PDU connReqPDU = assertTrue(data[2] instanceof String); SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( assertEquals((String)data[2], "smsftp"); "?transmismsv0.7.0\n" + // Status Code "12345678901234567890\n" + assertTrue(data[3] instanceof String); "req/smsftp/09871234567/pair\n" + assertEquals((String)data[3], "0002"); "\n" + // extra \n for additional message // Status Message "This is an extra message"); assertNull(data[4]); assertEquals(connReqPDU.getPduType(), // Additional Message CorePDUType.CORE_CONNECTION_REQUEST); assertTrue(data[5] instanceof String); Object[] data = connReqPDU.getData(); assertEquals((String)data[5], "This is an extra message"); assertEquals(data.length, 6); } // Protocol Version { // without additional message assertTrue(data[0] instanceof Version); PDU connResPDU = assertEquals((Version)data[0], new Version(0, 7, 0)); SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( // Session Id "?transmismsv0.7.0\n" + assertTrue(data[1] instanceof UUID); "12345678901234567890\n" + assertEquals((UUID)data[1], hexStringsToUUID( "rep/smsftp/0002/Accepted by user"); "32699b3242279c09", "32699b3242279c09")); assertEquals(connResPDU.getPduType(), // Transmisms Protocol CorePDUType.CORE_CONNECTION_RESPONSE); assertTrue(data[2] instanceof String); Object[] data = connResPDU.getData(); assertEquals((String)data[2], "smsftp"); assertEquals(data.length, 6);
146 // Protocol Version "?transmismsv0.7.0\n" + assertTrue(data[0] instanceof Version); "12345678901234567890\n" + assertEquals((Version)data[0], new Version(0, 7, 0)); "last/smsftp/0001/Completed without error\n" + // Session Id "\n" + // extra \n for additional message assertTrue(data[1] instanceof UUID); "This is an extra message"); assertEquals((UUID)data[1], hexStringsToUUID( assertEquals(lastAckPDU.getPduType(), CorePDUType.CORE_LAST_ACK); "32699b3242279c09", "32699b3242279c09")); Object[] data = lastAckPDU.getData(); // Transmisms Protocol assertEquals(data.length, 6); assertTrue(data[2] instanceof String); // Protocol Version assertEquals((String)data[2], "smsftp"); assertTrue(data[0] instanceof Version); // Status Code assertEquals((Version)data[0], new Version(0, 7, 0)); assertTrue(data[3] instanceof String); // Session Id assertEquals((String)data[3], "0002"); assertTrue(data[1] instanceof UUID); // Status Message assertEquals((UUID)data[1], hexStringsToUUID( assertTrue(data[4] instanceof String); "32699b3242279c09", "32699b3242279c09")); assertEquals((String)data[4], "Accepted by user"); // Transmisms Protocol // Additional Message field is missing assertTrue(data[2] instanceof String); assertNull(data[5]); assertEquals((String)data[2], "smsftp"); } // Status Code } assertTrue(data[3] instanceof String); assertEquals((String)data[3], "0001"); // Status Message // CORE_HEAD assertTrue(data[4] instanceof String); @Category(RegularTests.class) assertEquals((String)data[4], "Completed without error"); @Test // Additional Message public void coreHeadValuesShouldBeReturned() assertTrue(data[5] instanceof String); throws PDUMalformedException { assertEquals((String)data[5], "This is an extra message"); { // with additional message } PDU headPDU = { // missing status message SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( PDU lastAckPDU = "?transmismsv0.7.0\n" + SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( "12345678901234567890\n" + "?transmismsv0.7.0\n" + "head/smsftp/enc\n" + "12345678901234567890\n" + "\n" + // extra \n for additional message "last/smsftp/0001\n" + "This is an extra message"); "\n" + // extra \n for additional message assertEquals(headPDU.getPduType(), CorePDUType.CORE_HEAD); "This is an extra message"); Object[] data = headPDU.getData(); assertEquals(lastAckPDU.getPduType(), CorePDUType.CORE_LAST_ACK); assertEquals(data.length, 5); Object[] data = lastAckPDU.getData(); // Protocol Version assertEquals(data.length, 6); assertTrue(data[0] instanceof Version); // Protocol Version assertEquals((Version)data[0], new Version(0, 7, 0)); assertTrue(data[0] instanceof Version); // Session Id assertEquals((Version)data[0], new Version(0, 7, 0)); assertTrue(data[1] instanceof UUID); // Session Id assertEquals((UUID)data[1], hexStringsToUUID( assertTrue(data[1] instanceof UUID); "32699b3242279c09", "32699b3242279c09")); assertEquals((UUID)data[1], hexStringsToUUID( // Transmisms Protocol "32699b3242279c09", "32699b3242279c09")); assertTrue(data[2] instanceof String); // Transmisms Protocol assertEquals((String)data[2], "smsftp"); assertTrue(data[2] instanceof String); // Encryption assertEquals((String)data[2], "smsftp"); assertTrue(data[3] instanceof String); // Status Code assertEquals((String)data[3], "enc"); assertTrue(data[3] instanceof String); // Additional Message assertEquals((String)data[3], "0001"); assertTrue(data[4] instanceof String); // Status Message assertEquals((String)data[4], "This is an extra message"); assertNull(data[4]); } // Additional Message { // without additional message assertTrue(data[5] instanceof String); PDU headPDU = assertEquals((String)data[5], "This is an extra message"); SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( } "?transmismsv0.7.0\n" + { // without additional message "12345678901234567890\n" + PDU lastAckPDU = "head/smsftp/noenc"); SmsftpTextBasedPduDecoder.getInstance().decodeTextBased( assertEquals(headPDU.getPduType(), CorePDUType.CORE_HEAD); "?transmismsv0.7.0\n" + Object[] data = headPDU.getData(); "12345678901234567890\n" + assertEquals(data.length, 5); "last/smsftp/0001/Completed without error"); // Protocol Version assertEquals(lastAckPDU.getPduType(), CorePDUType.CORE_LAST_ACK); assertTrue(data[0] instanceof Version); Object[] data = lastAckPDU.getData(); assertEquals((Version)data[0], new Version(0, 7, 0)); assertEquals(data.length, 6); // Session Id // Protocol Version assertTrue(data[1] instanceof UUID); assertTrue(data[0] instanceof Version); assertEquals((UUID)data[1], hexStringsToUUID( assertEquals((Version)data[0], new Version(0, 7, 0)); "32699b3242279c09", "32699b3242279c09")); // Session Id // Transmisms Protocol assertTrue(data[1] instanceof UUID); assertTrue(data[2] instanceof String); assertEquals((UUID)data[1], hexStringsToUUID( assertEquals((String)data[2], "smsftp"); "32699b3242279c09", "32699b3242279c09")); // Encryption // Transmisms Protocol assertTrue(data[3] instanceof String); assertTrue(data[2] instanceof String); assertEquals((String)data[3], "noenc"); assertEquals((String)data[2], "smsftp"); // Additional Message field is missing // Status Code assertNull(data[4]); assertTrue(data[3] instanceof String); } assertEquals((String)data[3], "0001"); } // Status Message assertTrue(data[4] instanceof String); assertEquals((String)data[4], "Completed without error"); // CORE_LAST_ACK // Additional Message field is missing @Category(RegularTests.class) assertNull(data[5]); @Test } public void coreLastAckValuesShouldBeReturned() } throws PDUMalformedException { } { // with status message PDU lastAckPDU = SmsftpTextBasedPduDecoder.getInstance().decodeTextBased(
147 ------@Category(ExceptionTests.class) Filename: src/test/unit/java/VersionTest.java @Test(expected = IllegalArgumentException.class) ------public void negativeParam1ShouldThrowIllegalArgumentException() { Version tester = new Version(-3, 7, 5); import com.transmisms.core.protocol.Version; } import static org.junit.Assert.assertEquals; @Category(ExceptionTests.class) import static org.junit.Assert.assertNotEquals; @Test(expected = IllegalArgumentException.class) import static org.junit.Assert.assertTrue; public void negativeParam2ShouldThrowIllegalArgumentException() { import static org.junit.Assert.assertFalse; Version tester = new Version(3, -7, 5); } import org.junit.experimental.categories.Category; @Category(ExceptionTests.class) import org.junit.Test; @Test(expected = IllegalArgumentException.class) public void negativeParam3ShouldThrowIllegalArgumentException() { Version tester = new Version(3, 7, -5); } public class VersionTest { }
// no, we won't check for a.hashCode() == b.hashCode() == a.equals(b) // but we "followed" the java.lang.Object contract anyway
@Category(RegularTests.class) @Test public void sameVersionShouldBeEquivalent() { Version tester1 = new Version(0, 0, 0); Version tester2 = new Version(0, 0, 0); Version tester3 = new Version(0, 0, 0);
// symmetric test of equality assertEquals(tester2, tester1); assertEquals(0, tester2.compareTo(tester1));
// transitive test of equality assertEquals(tester1, tester2); assertEquals(0, tester1.compareTo(tester2)); assertEquals(tester2, tester3); assertEquals(0, tester2.compareTo(tester3)); assertEquals(tester1, tester3); assertEquals(0, tester1.compareTo(tester3)); }
@Category(RegularTests.class) @Test public void diffVersionShouldNotBeEquivalent() { Version tester1 = new Version(0, 0, 0); Version tester2 = new Version(0, 0, 0); Version tester4 = new Version(3, 7, 2);
// tests for inequality assertNotEquals(tester1, tester4); assertNotEquals(0, tester1.compareTo(tester4));
// other misc tests assertNotEquals(tester4, tester1); assertNotEquals(0, tester4.compareTo(tester1)); assertNotEquals(tester2, tester4); assertNotEquals(0, tester2.compareTo(tester4)); }
@Category(RegularTests.class) @Test public void newerVersionShouldBeMore() { Version tester1 = new Version(6, 8, 9); Version tester2 = new Version(6, 8, 8); Version tester3 = new Version(6, 7, 9); Version tester4 = new Version(5, 8, 9);
assertTrue(tester1.compareTo(tester2) > 0); assertTrue(tester1.compareTo(tester3) > 0); assertTrue(tester1.compareTo(tester4) > 0);
assertTrue(tester2.compareTo(tester3) > 0); }
@Category(RegularTests.class) @Test public void olderVersionShouldBeLess() { Version tester1 = new Version(6, 8, 2); Version tester2 = new Version(6, 8, 3); Version tester3 = new Version(6, 9, 2); Version tester4 = new Version(7, 8, 2);
assertTrue(tester1.compareTo(tester2) < 0); assertTrue(tester1.compareTo(tester3) < 0); assertTrue(tester1.compareTo(tester4) < 0);
assertTrue(tester2.compareTo(tester3) < 0); }
148 Android Companion App:
149 import zmq.ZError; plr.poll(100); if(plr.pollin(0)) { String str = sck.recvStr(0); ClientAction cAction = public class CompanionService extends Service { CompanionService.this.gson.fromJson( public class CompanionBinder extends Binder { str, ClientAction.class); CompanionService getService() { if(cAction != null && cAction.action != null && return CompanionService.this; cAction.action.equals("ping")) { } // send pong back } String jsonStr = CompanionService .this.gson.toJson( private static final int ONGOING_NOTIFICATION_ID = 1; new ServerAction('p')); sck.send(jsonStr); private String msisdn; } else if(cAction.action.equals("send") && private Gson gson = new Gson(); cAction.recipient != null && cAction.message != null) { private CompanionBinder mBinder = new CompanionBinder(); // send sms private Handler mHandler; SmsManager.getDefault().sendTextMessage( private Thread mThread; cAction.recipient, null, private boolean mIsRunning = false; cAction.message, null, null); private SmsReceiver smsReceiver = new SmsReceiver(); } else if(cAction.action.equals("msisdn")) { // create and send the msisdn // server action object as json @Override String jsonStr = CompanionService public void onCreate() { .this.gson.toJson( super.onCreate(); new ServerAction( this.mHandler = new Handler(); CompanionService.this.getMsisdn())); sck.send(jsonStr); // create PendingIntent to start MainActivity from the notification } Intent notificationIntent = new Intent(this, MainActivity.class); } notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | // exhaust queue then proceed Intent.FLAG_ACTIVITY_SINGLE_TOP); String jsonStr = PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, CompanionService.this.smsReceiver.pop(); notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); while(jsonStr != null) { Notification notification = new Notification.Builder(this) sck.send(jsonStr); .setContentTitle("Transmisms Companion") jsonStr = CompanionService .setContentText("Tap to go to the app") .this.smsReceiver.pop(); .setSmallIcon(R.drawable.ic_import_export_white_24dp) } .setContentIntent(pendingIntent) } .setTicker("Transmisms companion service is running") catch(ZMQException e) { .build(); // exit loop on zmq exception // start service as a foreground service break; startForeground(ONGOING_NOTIFICATION_ID, notification); } } plr.close(); } @Override // cleanup for zmq public IBinder onBind(Intent intent) { if(sck != null) { //return null; try { return this.mBinder; sck.close(); } } catch(ZError.IOException e) { @Override // do nothing if already "closed" public int onStartCommand(Intent intent, int flags, int startId) { } super.onStartCommand(intent, flags, startId); } if(this.mThread == null) { if(ctx != null && !ctx.isTerminated()) { // load previously loaded settings, if any try { SharedPreferences sPrefs = // NOTE: This was purposively commented out due PreferenceManager.getDefaultSharedPreferences(this); // to some Android version's implementation final String savedPortValue = sPrefs.getString( // of libcore causing problems with nio MainActivity.PORT_PREF_KEY, "8767"); // methods invoked by explicitly term() or this.getMsisdn(); // try to get msisdn from config asap // destroying zeromq Contexts. These this.mThread = new Thread() { // Contexts will be eventually destroyed @Override // outside the main loop after exiting this public void run() { // thread // set necessary flags //ctx.term(); CompanionService.this.mIsRunning = true; } catch(ZError.IOException e) { // register receiver // do nothing if already "closed" IntentFilter filter = new IntentFilter(); } filter.addAction( } android.provider.Telephony.Sms.Intents .SMS_RECEIVED_ACTION); // cleanup when the service exits filter.setPriority(999); // set to slightly high CompanionService.this.mIsRunning = false; CompanionService.this.registerReceiver( CompanionService.this.stopSelf(); CompanionService.this.smsReceiver, filter); mHandler.post(new Runnable() { @Override // initialize zmq connection public void run() { String addr = "0.0.0.0:" + savedPortValue; Toast.makeText(CompanionService.this, Context ctx = ZMQ.context(1); "Service stopped", Toast.LENGTH_SHORT).show(); Socket sck = ctx.socket(ZMQ.PAIR); } sck.setLinger(0); }); sck.bind("tcp://" + addr); } // serve until stopped }; while(CompanionService.this.mIsRunning && this.mThread.start(); !Thread.currentThread().isInterrupted()) { } // set zmq poller Poller plr = ctx.poller(); // we want this service to continue running until it is explicitly plr.register(sck, Poller.POLLIN); // stopped try { return Service.START_STICKY;
150 } RECEIVE_SMS_REQUEST_CODE); } @Override public void onDestroy() { super.onDestroy(); this.mIsRunning = false; // set the stop flag private ServiceConnection mServiceConnection = new ServiceConnection() { this.mThread = null; // and cleanup the thread references @Override this.unregisterReceiver(this.smsReceiver); public void onServiceConnected(ComponentName className, } IBinder service) { CompanionBinder cb = (CompanionBinder)service; public boolean isRunning() { MainActivity.this.mService = cb.getService(); return this.mIsRunning; MainActivity.this.setFabImageAsStart(true); } }
public String getMsisdn() { @Override // get value from config, if not accessed yet by this service public void onServiceDisconnected(ComponentName className) { if(this.msisdn == null) { MainActivity.this.mService = null; SharedPreferences sPrefs = } PreferenceManager }; .getDefaultSharedPreferences( CompanionService.this); String savedMsisdnValue = sPrefs.getString( @Override MainActivity.MSISDN_PREF_KEY public void onCreate(Bundle savedInstanceState) { , null); super.onCreate(savedInstanceState); CompanionService.this.msisdn = savedMsisdnValue; SharedPreferences sPrefs = } PreferenceManager.getDefaultSharedPreferences(this); return this.msisdn; String savedMsisdn = } sPrefs.getString(MainActivity.MSISDN_PREF_KEY, null); } String savedPort = sPrefs.getString(MainActivity.PORT_PREF_KEY, null); if(savedMsisdn != null && savedPort != null) { this.setContentView(R.layout.main_layout); Toolbar appBar = (Toolbar)(this.findViewById(R.id.mainAppBar)); ------this.setSupportActionBar(appBar); Filename: src/main/java/com/transmisms/androidcompanion/MainActivity.java this.mFab = (FloatingActionButton)(this.findViewById( ------R.id.floatingActionButton)); this.attemptStartCompanionService(); package com.transmisms.androidcompanion; } else { import com.transmisms.androidcompanion.CompanionService.CompanionBinder; this.onSetupMenuItemClick(null); } import android.Manifest; } import android.content.ComponentName; import android.content.Context; @Override import android.content.Intent; public void onStart() { import android.content.ServiceConnection; super.onStart(); import android.content.SharedPreferences; TextView textView = (TextView) findViewById(R.id.text_view); import android.content.pm.PackageManager; textView.setText("Main world!"); import android.os.Bundle; } import android.os.IBinder; import android.preference.PreferenceManager; @Override import android.support.design.widget.FloatingActionButton; public void onResume() { import android.support.design.widget.Snackbar; super.onResume(); import android.support.v4.app.ActivityCompat; // change FAB's icon depending on the CompanionService's state import android.support.v4.content.ContextCompat; this.setFabImageAsStart(this.isServiceRunning()); import android.support.v7.app.AppCompatActivity; } import android.support.v7.widget.Toolbar; import android.view.Menu; @Override import android.view.MenuInflater; public void onDestroy() { import android.view.MenuItem; super.onDestroy(); import android.view.View; // unbind the service but don't stop it from running import android.widget.TextView; if(this.mService != null) { import android.widget.Toast; this.unbindService(this.mServiceConnection); this.mService = null; import java.util.HashMap; } import java.util.Map; }
@Override public boolean onCreateOptionsMenu(Menu menu) { public class MainActivity extends AppCompatActivity { MenuInflater inflater = getMenuInflater(); public final static String MSISDN_PREF_KEY = "msisdn"; inflater.inflate(R.menu.main_menu, menu); public final static String PORT_PREF_KEY = "port"; return true; } public final static int READ_SMS_REQUEST_CODE = 0; public final static int SEND_SMS_REQUEST_CODE = 1; @Override public final static int RECEIVE_SMS_REQUEST_CODE = 2; public void onRequestPermissionsResult(int requestCode, public final static int READ_PHONE_STATE_CODE = 3; String permissions[], int[] grantResults) { private final static Map
151 Snackbar.make( this.mService = null; this.findViewById(R.id.main_layout), } "Service cannot be started. " + } "Allow permission requested and try again.", } Snackbar.LENGTH_LONG).show(); } break; } default: { ------// do nothing Filename: src/main/java/com/transmisms/androidcompanion/ServerAction.java } ------} } package com.transmisms.androidcompanion;
// handler when the setup menu item is clicked public class ServerAction { public void onSetupMenuItemClick(MenuItem m) { final public String action; if(this.isServiceRunning()) { this.stopCompanionService(); // recv } final public String sender; Intent setupIntent = new Intent(this, SetupActivity.class); final public String message; this.startActivity(setupIntent); final public Long timestamp; this.finish(); } // error final public String code; // handler when the FAB is clicked final public String[] alternativecodes; // optional public void onFabButtonClick(View v) { boolean isServiceStarted = this.isServiceRunning(); // pong if(isServiceStarted) { // NOTHING FOLLOWS this.stopCompanionService(); } public ServerAction() { else { this.action = null; this.attemptStartCompanionService(); this.code = null; } this.alternativecodes = null; this.setFabImageAsStart(!isServiceStarted); this.sender = null; } this.message = null; this.timestamp = null; // attempts to start service with permission checks beforehand } public void attemptStartCompanionService() { // check for permissions // recv boolean permissionGranted = true; public ServerAction(String sender, String newReqPermission = null; String message, long timestamp) { for(String permission : MainActivity.permissionCodeMap.keySet()) { this.action = "recv"; permissionGranted = permissionGranted && this.sender = sender; (ContextCompat.checkSelfPermission(this, permission) == this.message = message; PackageManager.PERMISSION_GRANTED); this.timestamp = timestamp; if(!permissionGranted) { this.code = null; newReqPermission = permission; this.alternativecodes = null; } } } // permission denied // error if(!permissionGranted && newReqPermission != null) { public ServerAction(String code, String[] alternativecodes) { ActivityCompat.requestPermissions(this, new String[]{ this.action = "error"; newReqPermission}, this.code = code; MainActivity.permissionCodeMap.get(newReqPermission)); this.alternativecodes = alternativecodes; } this.sender = null; else { // permission granted this.message = null; this.startCompanionService(); // just start the service ASAP this.timestamp = null; Toast.makeText(this, "Service started", Toast.LENGTH_SHORT).show(); } } } // msisdn public ServerAction(String msisdn) { //// utility functions this.action = "msisdn"; private void setFabImageAsStart(boolean serviceStarted) { this.message = msisdn; if(serviceStarted) { // service started // no fields // set icon as stop this.code = null; this.mFab.setImageDrawable(ContextCompat.getDrawable(this, this.alternativecodes = null; R.drawable.ic_stop_white_24dp)); this.sender = null; } this.timestamp = null; else { // service stopped } // set icon as start this.mFab.setImageDrawable(ContextCompat.getDrawable(this, // pong R.drawable.ic_play_arrow_white_24dp)); public ServerAction(char c) { } this.action = "pong"; } // no fields this.code = null; private boolean isServiceRunning() { this.alternativecodes = null; return this.mService != null && this.mService.isRunning() == true; this.sender = null; } this.message = null; this.timestamp = null; private void startCompanionService() { } Intent intent = new Intent(this, CompanionService.class); } this.startService(intent); this.bindService(intent, this.mServiceConnection, Context.BIND_AUTO_CREATE); } ------private void stopCompanionService() { Filename: src/main/java/com/transmisms/androidcompanion/SetupActivity.java if(this.mService != null) { ------this.unbindService(this.mServiceConnection); Intent intent = new Intent(this, CompanionService.class); package com.transmisms.androidcompanion; this.stopService(intent);
152 import android.Manifest; public void detectMsisdn() { import android.app.AlertDialog; // get msisdn import android.content.Context; TelephonyManager tMgr = (TelephonyManager)(this.getSystemService( import android.content.DialogInterface; Context.TELEPHONY_SERVICE)); import android.content.Intent; String msisdn = tMgr.getLine1Number().trim(); import android.content.SharedPreferences; // check for null, empty, or invalid msisdns import android.content.pm.PackageManager; if(msisdn != null && msisdn.length() != 0 && !msisdn.contains("?")) { import android.os.Bundle; // put msisdn on appropriate text fields import android.preference.PreferenceManager; this.msisdnField.setText(msisdn); import android.support.v4.app.ActivityCompat; } import android.support.v4.content.ContextCompat; else { // probably we got null or "???" import android.support.v7.app.AppCompatActivity; // ask user to input number manually import android.support.v7.widget.Toolbar; Toast.makeText(this, "Could not detect this device's number. " + import android.telephony.TelephonyManager; "Please enter the number manually.", import android.text.Editable; Toast.LENGTH_LONG).show(); import android.text.TextWatcher; } import android.view.LayoutInflater; } import android.view.View; import android.view.ViewGroup; public void onFinishButtonClick(View v) { import android.widget.Button; SharedPreferences sPrefs = import android.widget.EditText; PreferenceManager.getDefaultSharedPreferences(this); import android.widget.Toast; // save settings SharedPreferences.Editor sPrefsEditor = sPrefs.edit(); sPrefsEditor.putString(MainActivity.MSISDN_PREF_KEY, this.msisdnField.getText().toString()); sPrefsEditor.putString(MainActivity.PORT_PREF_KEY, public class SetupActivity extends AppCompatActivity { this.bindPort); private EditText msisdnField; sPrefsEditor.apply(); private Button finishButton; private String bindPort = "8767"; // then start the main activity afterwards Intent mainIntent = new Intent(this, MainActivity.class); this.startActivity(mainIntent); this.finish(); @Override } public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); public void onPortCustButtonClick(View v) { // initialize views an widgets // initialize the portPicker builder dialog setContentView(R.layout.setup_layout); AlertDialog.Builder builder = new AlertDialog.Builder(this); Toolbar appBar = (Toolbar)(this.findViewById(R.id.setup_appbar)); View viewInflated = LayoutInflater.from(this).inflate( this.setSupportActionBar(appBar); R.layout.port_input_layout, // "bind" setup_finish_button's enable property to (ViewGroup)(this.findViewById(android.R.id.content)), false); // setup_msisdn_field's !emptiness builder.setView(viewInflated); this.finishButton = (Button)(this.findViewById( final EditText portInput = R.id.setup_finish_button)); (EditText)(viewInflated.findViewById(R.id.port_field)); this.msisdnField = (EditText)(this.findViewById( portInput.setText(this.bindPort); R.id.setup_msisdn_field)); builder.setMessage("Enter desired port number (1024-65535):"); this.msisdnField.addTextChangedListener(new TextWatcher() { builder.setPositiveButton("Save", @Override new DialogInterface.OnClickListener() { public void afterTextChanged(Editable s) { @Override SetupActivity.this.finishButton.setEnabled(s.length() != 0); public void onClick(DialogInterface dialog, int w) { } dialog.dismiss(); @Override SetupActivity.this.bindPort = String.valueOf(Integer.parseInt( public void beforeTextChanged(CharSequence s, int start, portInput.getText().toString())); int count, int after) {} } @Override }); public void onTextChanged(CharSequence s, int start, int before, builder.setNegativeButton("Cancel", int count) {} new DialogInterface.OnClickListener() { }); @Override // load previously loaded settings, if any public void onClick(DialogInterface dialog, int w) { SharedPreferences sPrefs = dialog.cancel(); PreferenceManager.getDefaultSharedPreferences(this); } String savedMsisdnValue = sPrefs.getString( }); MainActivity.MSISDN_PREF_KEY, null); // set listeners for portInput to change the confirmation (Save) String savedPortValue = sPrefs.getString( // button's enabled property MainActivity.PORT_PREF_KEY, null); final AlertDialog portPicker = builder.create(); if(savedMsisdnValue != null) { portInput.addTextChangedListener(new TextWatcher() { this.msisdnField.setText(savedMsisdnValue); @Override } public void afterTextChanged(Editable s) { if(savedPortValue != null) { boolean enableButton = (s.length() != 0); this.bindPort = savedPortValue; if(enableButton) { } int i = Integer.parseInt(s.toString()); // try to autodetect msisdn asap enableButton = (i > 1024 && i < 65536); this.onDetectMsisdnButtonClicked( } this.findViewById(android.R.id.content)); portPicker.getButton( } DialogInterface.BUTTON_POSITIVE).setEnabled( enableButton); public void onDetectMsisdnButtonClicked(View v) { } // check for permissions first @Override if(ContextCompat.checkSelfPermission( public void beforeTextChanged(CharSequence s, int start, this, Manifest.permission.READ_PHONE_STATE) == int count, int after) {} PackageManager.PERMISSION_GRANTED) { // granted @Override this.detectMsisdn(); public void onTextChanged(CharSequence s, int start, int before, } int count) {} else { // denied }); // try to request permissions // finally show portPicker ActivityCompat.requestPermissions(this, new String[]{ portPicker.show(); Manifest.permission.READ_PHONE_STATE}, } MainActivity.READ_PHONE_STATE_CODE); } @Override } public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
153 switch(requestCode) { android:id="@+id/main_layout" case MainActivity.READ_PHONE_STATE_CODE: { android:layout_width="match_parent" if(grantResults.length > 0 && android:layout_height="match_parent" grantResults[0] == PackageManager.PERMISSION_GRANTED) android:fitsSystemWindows="true"{ detectMsisdn(); android:noHistory="true" } android:launchMode="singleTop" else { tools:context="com.rootbearlabs.androidtest1.MainActivity"> // notify user about insufficient permissions Toast.makeText(this, "Cannot autodetect number due to " +
private Gson gson = new Gson(); android:layout_gravity="end|bottom" private Queue public String pop() { return this.msgQueue.poll(); } @Override public void onReceive(Context context, Intent intent) { Bundle bundle = intent.getExtras(); if(bundle != null) { ------Object[] pduArr = (Object[])bundle.get("pdus"); Filename: src/main/res/layout/port_input_layout.xml SmsMessage[] msgArr = new SmsMessage[pduArr.length]; ------ // NOTE: this receiver does not support multipart messages; // received multipart messages' parts will be just treated // as individual messages ------Filename: src/main/res/layout/setup_layout.xml 154 155 ------Filename: src/main/res/values/styles.xml ------ /* * You can also request a copy of this source from the library or the * laboratory manager/assistant. I have also archived this in case both * copies are not available. It's not recommended to copy source from this * document since binary dependencies such as images are not included. */ 156 Acknowledgements For the mouths that are deprived that their hunger be satisfied, For the worker who toils that his burden be relieved, For the benevolent master and their followers to succeed, And for the hopeful who kept on waiting that their wishes be fulfilled. 157