Improving standard compliance with transclusion

Inserting fragments of a standard specification – IETF RFC or other – as comments in the source code that implement it seems to be a simple way to assure a good conformance. Unfortunately doing so can create legal issues if not done carefully.

These days I am spending a lot of time implementing IETF’s (and other SDO’s) protocols for the Nephelion Project. I am no stranger to network protocol implementation, as this is mostly what I am doing since more than 25 years, but this time the very specific code that is needed for this project is required to be as close as possible to the standard. So I am constantly referring to the text inside the various RFCs to verify that my code is conformant. Obviously copying the text fragments as comments would greatly simplify the development and at the end make the translation between the English text that is used to describe the protocol and the programming language I use to implement it a lot more faithful.

At this point I should insert the usual IANAL but, at least to my understanding, that is something that is simply not possible. My intent is to someday release this code under a Free Software license, but even if it was not the case, I believe that all software should be built with the goal of licensing it in the future, this license being a commercial one or a FOSS one. The issue here is that the RFCs are copyrighted and that modifying is simply not permitted by the IETF Trust and, in my opinion, rightly so as a standard that anybody can freely modify is not much of a standard. But publishing my code under a FOSS license would give everyone the right to modify it (under the terms of the license), and that would apply too to the RFC fragments inserted in the source code.

So the solution I use to at the same time keep the specification and the implementation as close as possible and to not have to worry about code licensing is to use transclusion. Here’s an example of comment in the code source for the UDP module:

% @transclude file:///home/petithug/rsync/ietf/rfc/rfc768.txt#line=48,51

The syntax follows the Javadoc (and Pldoc, and Scaladoc) syntax. The @transclude tag indicates that the text referenced by the URL must be inserted in the source code but only when displayed in a text editor. Here’s what the same code looks like when loaded in VIM (the fragment for RFC 768 is reproduced here under fair use):

% @transclude file:///home/petithug/rsync/ietf/rfc/rfc768.txt#line=48,51
% {@transcluded
% Source Port is an optional field, when meaningful, it indicates the port
% of the sending process, and may be assumed to be the port to which a
% reply should be addressed in the absence of any other information. If
% not used, a value of zero is inserted.
% @@@}

(I chose this example because, until few days ago, I did not even know that using a UDP source port of 0 was conformant).

The @transcluded inline tag is dynamically generated by a VIM plugin but this tag will never appear anywhere else than in the VIM buffer, even after saving it to the disk. The fragment syntax is from RFC 5147, and permits to select the lines that must be copied (An RFC will never changes, so hardcoding the line number in the code source cannot break in the future).

The plugin can be installed from my Debian repository with the usual “apt-get install vim-transclusion”. The plugin is kind of rough for now: only the #line=<from>,<to> syntax is supported, hardcoding the full path is not very friendly, curl is required, etc… But that is still a huge improvement over having to keep specification and implementation separate.

Keeping work and personal computers separated

I try as much as possible to keep my personal stuff separated from the work stuff. Even if both California and Utah laws are clear that what I develop on my own time and my own hardware is mine (as long as it is not related to my day job – that’s the big difference with French laws where I own what I developed on my own time, even if it is on my employer’s business or computers), that did not prevent one of my former employers to try to claim ownership on what was rightfully mine. Because it is very expensive to get justice in the USA, getting things as separated as possible from the beginning seems like a good idea.

The best way to do that is simply to not work during one’s free time on anything that could have a potential business value – these days, I spend a lot of time learning about cryptography, control system engineering and concurrent systems validation. But keeping things separated still creates some issues, like having to carry two laptops when traveling. I did this twice for IETF meetings, and it is really no fun.

The solution I finally found was to run my personal laptop as an encrypted hard drive in a virtual machine on the company laptop. My employer provided me with a MacBook, which has nice hardware but whose OS is not very good. I had to put a reminder in my calendar to reboot it each week if I did not want to see it regularly crashing or freezing. Mac OSX is a lot like like Windows, excepted that you are not ashamed to show it to your friends. Anyway here’s how to run your own personal computer on your employer’s laptop:

First you need a portable hard drive, preferably one that does not require a power supply. I use the My Passport Ultra 500GB with the AmazonBasics Hard Carrying Case. Next step is to install and configure VirtualBox on your laptop. You will need to install the Oracle VM VirtualBox Extension Pack if, like me, you need to use in your personal computer a USB device that is connected to the employer laptop (in my case, a smart-card dongle that contains the TLS private key to connect to my servers). Next step is to change the owner of your hard drive (you unfortunately will have to do that each time you plug the hard drive):

sudo chown <user> /dev/disk2

After this you can create a raw vdmk file that will reference the external hard drive:

cd VirtualBox VMs
VBoxManage internalcommands createrawvmdk -filename ExternalDisk.vmdk -rawdisk /dev/disk2

After this, you just have to create a VM in VirtualBox that is using this vdmk file. I installed Debian sid which encryption, which took the most part of the day as the whole disk as to be encrypted sector by sector. I also installed gogo6 so I could have an IPv6 connection in places that still live in the dark age. Debian contains the correct packages (apt-get install virtualbox-guest-utils) so the X server in the personal computer will adapt its display size automatically to the size of the laptop.

To restore the data from my desktop, I configured VirtualBox on it too, so I could also run the personal computer on it. Then, thanks to the same Debian packages, I was able to mount my backups as a shared folder and restore all my data in far less time than an scp command would take.

And after all of this I had a secure and convenient way to handle my personal emails without having to carry two laptops.

Things I learned last week (2)

Debian packages without repository

There is probably a better way to do that, but here is a command that display all the installed packages that are not part of a configured repository:

apt-show-versions -a |grep "No available version in archive"

These can be packages that are installed directly using dpkg -i, but I was surprised to find that many of them were in fact packages from repositories that I removed from my configuration. The problem is that without a repository there is no way to receive the new versions, which can be a problem when a security problem is discovered. Most of these packages were not used, but it is bad idea to keep obsolete stuff around – a malicious script can still try to call code from obsolete packages in the hope that an unpatched security bug can be exploited.

Android build still requires Sun Java

For the reasons explained above, I removed the sun-java6-jdk package from my system, but that made the Android build fail, which means that Android still requires the Sun JDK 1.6 (openjdk does not work) for its build, which is kind of crazy knowing that this package is no longer maintained. Even the Android web page is wrong as Ubuntu also no longer carry this package. I first tried to rebuild the package using make-pkg, but I stopped when I saw that registration is now required to download the JDK from Oracle. Finally I found that sun-java6-jdk was still available in the oldstable Debian repository, but for how long?

Here’s the line to add to /etc/apt/sources.list:

deb http://ftp.us.debian.org/debian oldstable main contrib non-free

Asus KCMA-D8 Sensors

One of the reasons I built a new computer is because the previous one was overheating even under moderate CPU load, so I wanted to be sure that the new computer would be able to to sustain continuous load without crashing. Because my system uses a RAID array, I wanted to do the testing with Debian Live instead of the target OS. The problem was that the sensors were not displayed at all, and it took multiple hours of research (even using an I2C monitor to find out the address of the sensors chip) to finally find the reason: The PIIX4 chip has in fact 2 SMBus, but the second bus (which is the one connecting the chip managing the sensors) was not implemented in the Linux kernel version 3.2. After switching to a version 3.9 kernel the sensors were finally accessible, which showed that the north bridge was overheating. I installed a fan on top of the heatsink and now the north bridge temperature is under control and cpuburn tests shows that that new system does not overheat or crash even after one hour with the 12 cores used at 100%.

KVM and Virtualbox

Another reason for a new computer was to be able to use kvm to run the Android emulator at decent speed. But it seems that it is not possible to run a kvm application and virtualbox at the same time.
This means that I will not be able to run an Android app and its server in a virtualbox, so I’ll have to convert my servers to kvm.

Things I learned last week (1)

Android emulator

There was a lot of progress made on the Android emulator since the last time I used it for development; sensors can now be emulated, the display can use the GPU of the host and most important, the emulator can use the virtualization support of the host to run an x86 image at close to native speed. The GPU support and virtualization are now more or less mandatory because of the size of the display of modern Android devices, so it is worth the effort to configure Android for this. That requires a computer that can run KVM and to install the x86 system images.

The command line to start the emulation looks like this:

$ emulator -avd avd_name -gpu on -qemu -m 512 -enable-kvm

Unfortunately when the AVD is new, the window displayed stays black. The solution is to run it the first time with kvm disabled:

$ emulator -avd avd_name -gpu on -qemu -m 512 -disable-kvm

After this, the first command line above can be used.

CPU heatsink

My desktop computer is dying, so it is time to build a new one. I replaced the Asus K8N-DL by a KCMA-D8 motherboard and ordered the CPUs and memory to build it last week-end. Unfortunately I did not planned that the CPUs would arrive without heatsink, and unfortunately heatsinks for C32 socket are not available in stores. I suppose it makes sense that the CPUs comes without heatsink as these kind of motherboard can be used in 1U chassis, which requires a very different type of heatsink than in a tower. But now I have to wait until I receive the heatsink to finish the build.

Rescue USB flash drive

I run Debian Sid on all my non-server computers, which means that from time to time there is something to repair after an apt-get upgrade – that’s not as insane as it seems as upgrading a computer each day with the latest packages and fixing whatever broke is a great way to learn stuff. After all I am a computer professional, not a museum curator.
To repair the most broken installation I keep a Debian Live distribution on a flash drive. On the other hand my desktop computer also use a flash drive to boot GRUB (this machine uses a RAID10 array, which cannot be used for booting), so for this new build I decided to put the Debian Live distribution on the same flash drive, so I do not have to search the rescue flash drive next time I break something. It took me a while, but here the process:

Download a recent Debian Live ISO file, mount it on a loop and copy the content of the live directory on the flash drive:

# mount -o loop Downloads/debian-live-7.0.0-amd64-rescue.iso /mnt
# mkdir /boot/live
# cp /mnt/live/* /boot/live/
# umount /mnt

Then add the following in /etc/grub.d/40_custom:

menuentry "Debian rescue" {
    echo 'Loading Debian rescue ...'
    linux /live/vmlinuz boot=live live-config live-media-path=/live
    echo 'Loading initial ramdisk ...'
    initrd /live/initrd.img
}

Then update grub.cfg with the following command:

# update-grub

Note that in this configuration the flash drive is mounted on /boot.

Jarc: Annotation processors

Version 0.2.27 of jarc now supports to run annotations processors when a jar file is built. The syntax is simple, just add a X-Jarc-Processors attribute in the manifest file header with a list of jar files, and jarc will automatically run the processors found by querying the META-INF/services/javax.annotation.processing.Processor file inside the jar files:

Manifest-Version: 1.0
Main-Class: org.implementers.apps.turnuri.Main
X-Jarc-Target: 1.7
X-Jarc-Processors: /usr/share/lib/processor.jar

Name: org/implementers/apps/turnuri/Main.class

In turn, it is easy to build an annotation processor with jarc, here’s the manifest file for a processor I am currently developing:

Manifest-Version: 1.0
Class-Path: /usr/share/java/jcip.jar
X-Jarc-Target: 1.6

Name: org/implementers/management/annotations/processing/Main.class
X-Jarc-Service: javax.annotation.processing.Processor

Note that the Java compiler does not run processors on dependent files, so you need to add a “Name:” attribute for all Java files that need to be processed.

Also starting with this version, the JDK 1.7 is required to run jarc – but jarc can still cross-compile for all the versions of Java starting with 1.5.

Jarc: Now running Java 1.8

I like to learn new Java features before they are officially released, and that requires using unstable builds. The difficulty is to integrate the new compiler into a build – for the JDK 1.7 I released jarc as an experimental package, but that was not a very good solution.

Since version 0.2.26, jarc can use an experimental compiler, like the one supporting lambda. If you installed the new JDK at /home/petithug/jdk8, you will only need to add the following lines to the /etc/jarc.conf file to be able to build jar files that use closures:

jdk-java_8-openjdk=/home/petithug/jdk8/bin/java
jdk-tools_8-openjdk=/home/petithug/jdk8/lib/tools.jar
canonical_8=1.8-openjdk
canonical_1.8=1.8-openjdk
canonical_8-openjdk=1.8-openjdk
canonical_1.8-openjdk=1.8-openjdk
jre-check_1.8-openjdk=/home/petithug/jdk8/jre/bin/java
jre-bootclasspath_1.8-openjdk=/home/petithug/jdk8/jre/lib/rt.jar:/home/petithug/jdk8/jre/lib/jce.jar
jre-source_1.8-openjdk=1.8
jre-exec_1.8-openjdk=/home/petithug/jdk8/jre/bin/java

Jarc always use by default the most recent compiler, but you can override this with the -Jjdk=7 or -Jjdk=6 option.

The new version of jarc also support passing parameters to the JVM – either at build time or at run time – by using the -J option.

Finally it is now possible to add an X-Jarc-Debug parameter at the manifest level. This option works just like the -g option in javac. I added this option to be able to build programs for aparapi – more about this in a future post.

RELOAD: The Wireshark dissector

I talked in a previous blog entry about the importance of assembling a set of good tools for development, and one of my favorite tool is Wireshark.

With my colleague Stèphane, we prepared a new version of the RELOAD dissector that now covers not only the full specification (draft-ietf-p2psip-base) but also the current standard extensions of RELOAD. The goal for this new version was not only to cover 100% of the specification, but also to do it in a way that help primarily the developers, because even if I dislike the idea that people develop protocol implementations by using packet dissection, the reality is that people are doing it, so we may as well be sure that the information displayed by the dissector are correct. So we tried as much as possible to dissect the protocol in a way that present the information on the screen as close as possible to the way it is described in the specification.

As for the RELOAD extensions, the following data structures are decoded by the new code:

  • SipRegistration in the “SIP Usage for RELOAD” (draft-ietf-p2psip-sip) specification.
  • RedirServiceProvider in the “Service Discovery Usage for RELOAD” (draft-ietf-p2psip-service-discovery) specification.
  • SelfTuningData in the “Self-tuning DHT for RELOAD” (draft-ietf-p2psip-self-tuning) specification.
  • DiagnosticsRequest, DiagnosticsResponse, PathTrackReq and PathTrackAns in the “P2PSIP Overlay Diagnostics” (draft-ietf-p2psip-diagnostics) specification.
  • ExtensiveRoutingModeOption in the “An extension to RELOAD to support Direct Response Routing” (draft-zong-p2psip-drr) specification.

We even prepared the work to decode RELOAD messages inside HIP, as described in the “HIP BONE Instance Specification for RELOAD” (draft-ietf-hip-reload-instance) specification.

The new code is not yet committed in the Wireshark tree, but it is available in the bug database (please vote for it if you can).

On request I can provide a compiled Debian/Ubuntu package for the i386 or amd64 architectures.

10/06/2011: The main patch is now commited in the Wireshark trunk. The fix for a bug in defragmentation still need to be manually applied.

10/08/2011: All the patches are now committed in the Wireshark trunk. Enjoy!

RELOAD: Client/server mode

In a previous post I quickly talked about an extension called “Client connected to bootstrap node”, which is now implemented in version 0.7.0 of the libreload-java package that was released few minutes ago.

Section 3.2.1 of the RELOAD specification talks about the two modes that a RELOAD client can be using to interact with an overlay. In the first mode, the client attaches itself to the responsible peer and process Update messages to always be connected to the current responsible peer. In the second mode, a client attaches itself to any peer and store a destination list somewhere in the overlay so other node can reach it (P2P SIP can use both of theses modes).

I think that there is a third mode that is missing in the specification, so I added it in my list of extensions. In this mode, the client is connected to one of the bootstrap peer, so it is similar to the second mode described above, but without having to send an Attach. One interesting characteristic of his mode is that this is the initial mode for all nodes. Client in either of the two modes described above have to start with this third mode. Peers have to start with this third mode, then switch to the first mode, then go to the peer mode. So describing it is, in my opinion, kind of a fundamental step.

Another interesting characteristic of that mode is that an overlay made of only bootstrap peers and clients using this mode is in fact our good old client/server architecture. Bootstrap peers replicates the data between each other and help establish direct connections between clients, all in a secure way.

The new version of the package contains only the code for the client side, and only implement the Store message at this time. Because having this code is not that useful without a bootstrap node, I also installed one in the test server (see the this post for instruction on how to retrieve the configuration file and request a certificate. The configuration file now contains the address of the bootstrap server to use with the client).

The Node class is the fundamental abstraction that application developers need to care about. A Node object is instantiated with 3 parameters, a Configuration object that contains the initial configuration of the overlay to join (and especially the list of bootstrap servers), a SignerIdentity object that contains the identity of the node to create, and the private key that was used to create the identity.

After creating the node the application can store or modify data in the bootstrap peers by calling the modify method. Here is an example of code that stores the certificate of the node in the bootstrap servers:


Set<DataModel<? extends Value>> kinds = new HashSet<DataModel<? extends Value>>();
Certificate certificate = new Certificate(signer.certificateChain().get(0));
CertificateByNode certificatesByNode = new CertificateByNode();
certificatesByNode.add(certificate);
kinds.add(certificatesByNode);
node.modify(signer.nodeId().toBytes(), kinds);

RELOAD: Configuration and Enrollment

The libreload-java package version 0.2.0 was released few minutes ago. Because this version starts to have bits and pieces related to RELOAD itself, the access control policy script tester that was explained in a previous post is now in a separate package named libreload-java-dev. There is nothing new in this package as I am waiting for a new version of draft-knauf-p2psip-share to update it.

The libreload-java package itself now contains the APIs required to build a RELOAD PKI. The package does not contains the configuration and enrollment servers themselves, as there is many different implementations possible, from command line tools to a deployable servlets. The best way to understand how this API can be used is to walk through the various components of a complete RELOAD PKI:

1. The enrollment server

The enrollment server provides X.509 certificates to RELOAD node (clients or peers). It acts as the Certificate Authority (CA) for a whole RELOAD overlay without requiring the service of a top-level CA. That means that anybody can deploy and manage its own RELOAD overlay (note that the enrollment server itself requires an X.509 certificate signed by a CA for the HTTPS connection, but it is independent from the PKI function itself). Creating the CA private key and certificate is simple, for example with openssl:

$ openssl genrsa -out ca.rsa 2048
$ openssl pkcs8 -in ca.rsa -out ca.key -topk8 -nocrypt
$ openssl req -new -key ca.key -out ca.csr
$ openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt

Obviously it is very important to keep the secret key, hmm, secret so using a physical token like TPM or a smartcard can be a good idea.

The second thing that the enrollment server must do is to restrict the creation of certificates to people authorized to do so. The simplest way is to issue login/passwords, but there is obviously other ways.

The enrollment server will receive a certificate request (formatted with PKCS#10), and will send back a X.509 certificate containing one or more URLs each containing a Node-IDs. The RELOAD API provides a static method to build a valid Node-ID that can be used like this:

NodeId nodeId = NodeId.newInstance(16, new SecureRandom());
new URI("reload", nodeId.toUserInfo(), "example.org", -1, "/", null, null);

The resulting URI looks something like this:

reload://0110534f8a94ec317834a9bb0d66bd3ae708@example.org/

That’s the only API required for the enrollment server. All the other stuff – certificate request parsing, certificate generation, can be easily done by using a standard library like Bouncycastle.

Note that an enrollment server is not required when self signed certificates are used.

2. The configuration server

The configuration server provides configurations files, that are XML documents that contain all the information required to join a specific overlay. A configuration file is represented in the API as a Configuration object, which is immutable. A Configuration object can be created either by parsing an existing document (using the parse static method) or by creating one from scratch by using the Builder helper class. The Builder helper class currently supports only the subset needed for implementing a PKI, i.e. it permits to set the CA certificate, the URL to the enrollment server and the signer of the configuration document. The API can be used like this to generate an initial configuration document:

Configuration.Builder builder = new Configuration.Builder("example.org");
builder.rootCertificates().add(caCert);
builder.enrollmentServers().add(new URI("https://example.org/enrollment"));
builder.kindSigners().add(nodeId);
builder.bootstrapNodes().add(new InetSocketAddress("10.1.1.0", 6084));
Configuration configuration = builder.build(signer, signerPrivateKey);
byte[] config = configuration.toByteArray();

The signerPrivateKey object is the private key that was created for the signer certificate, and the signer object is an instance of the SignerIdentity class that was created from the same certificate:

SignerIdentity signer = SignerIdentity.identities(certificate).get(0);

Subsequent versions of the configuration document for a specific overlay will need to be signed by the same signer.

It is easy to create a new version of a configuration document by using the API. For example to change the no-ice value and resign the document:

conf = new Configuration.Builder(conf).noIce(true).build(signer, signerPrivateKey);

The sequence number will automatically increase by one (modulo 65535) in the new document.

Note that this API can change in future versions. It will be frozen only in version 1.0.0, which cannot happen until the RELOAD specification is approved by the IESG for publication.

RELOAD: Access control policy script tester

There was some interest at the last IETF meeting in my presentation of Access Control Policy script, so I spent some time working in improving this. The first step was to release a new version of the I-D. To encourage adoption of this draft, I also released a library permitting to develop and test new access control scripts without having a complete implementation of RELOAD. This was released as a Debian package named libreload-java in my Debian/Ubuntu repository. This package is also a reference implementation for the I-D but, as it contains a subset of the RELOAD library I am current developing, it can also be considered as an early release of this library. As always, this is released under an Affero GPL 3 license, and the code source is available.

To explain how to develop a new access control script, I will now go through the example which is also in the libreload-java package:

import static org.implementers.net.reload.AccessControlPolicyTester.kind;
import static org.implementers.net.reload.DataModel.Standard.*;

These two lines permit to simplify the code by importing the kind() static method and the available Data Models.

private static final long KIND_ID = 4026531841l;

Here we declare a constant defining a new Kind-Id in the private range.

AccessControlPolicyTester tester = new AccessControlPolicyTester("myusername",
  kind(KIND_ID, SINGLE,
    "String.prototype['bytes'] = function() {n" +
    "  var bytes = [];n" +
    "    for (var i = 0; i < this.length; i++) {n" +
    "      bytes[i] = this.charCodeAt(i);n" +
    "    }n" +
    "    return bytes;n" +
    "};n" +
    "return resource.equalsHash(signature.user_name.bytes());n"));

This code creates a new instance of the tester by passing a user name (that will be used to generate an X509 certificate) and a new kind. The new kind uses the Kind-ID declared previously, uses the SINGLE Data Model and will use an Access Control Policy as described in the ECMAScript code. Note that more calls to the kind() method can be added to the constructor of the tester.

Map<Long, DataModel<?>> kinds = new HashMap<Long, DataModel<?>>();

This code declares a variable that will contains the data items meant to be stored. The key of the map is a long that contains a Kind-Id. The value is either an instance of the DataModel.Single class, the DataModel.Array class or the DataModel.Dictionnary if the Data Model is respectively SINGLE, ARRAY or DICTIONARY. In our case, we use the SINGLE Data Model, meaning that only one item can be stored at a specific Resource-ID for this specific Kind-ID.

kinds.put(KIND_ID, new DataModel.Single(KIND_ID, 0l, new Value.NotExists(System.currentTimeMillis(), 60, tester.signer(), tester.privateKey())));

This piece of code does multiple things, so let’s decompose:

First it retrieves the signer (an Identity instance that consist of a X509 Certificate, a user name and a Node-ID) and the private key that was used for the certificate of the signer. These two objects are automatically created by the constructor of the tester object.

Then it creates a new Value instance with a store_time equal to the current time, a lifetime of one minute, an exists value of FALSE and the identity and private key just retrieved. This is the object that we want to store (it is more or less equivalent to StoredData).

Then a new instance of DataModel.Single is created with our new Kind-ID, a generation_counter of 0 and the value to store (it is more or less equivalent to StoreKindData).

Finally this object is stored in the kinds map, with the same Kind-ID as key.

tester.store(tester.signer().userName().getBytes(UTF_8), 0, kinds)

In this code, we finally try to store the items that we just created. The first parameter is the Resource-Name, which in our case is the user name (converted to a byte array). The second parameter is the replica number, 0 for the original replica. The third parameter is the set of items. The method will return false if one items fails the access control policy verification, and true if all the items pass the verification.

The DataModel.Array and DataModel.Dictionary works in the same way, excepted that DataModel.Array is a sparse array implementing java.util.List and DataModel.Dictionary is implementing java.util.Map.

The program can be compiled and executed with the following commands:

$ javac -classpath /usr/lib/reload/reload.jar Main.java
$ java -classpath usr/lib/reload/reload.jar:. Main

In addition to the jar file and the example, the package contains the Javadoc for all the classes and methods accessible to the tester, and a copy of the I-D.

Update 2011/05/14:

The command lines above do not work. I uploaded a new package (0.1.1) so the following command lines should work now:

$ javac -classpath /usr/lib/reload/reload.jar AcpTest.java
$ java -classpath usr/lib/reload/reload.jar:. AcpTest