Adopting some newer C++ features

I’ve been continuously updating my skills in C++, adopting features from newer features of the language, like from the version C++17. For fun and learning, I’ve been updating some older apps to use these newer features, and implementing some new tools adopting newer features like std::variant, algorithms (instead of traditional loops) and attributes. Some examples below.

Attributes

Instead of commenting in a switch/case structure that fallthrough is OK, use the [[fallthrough]] attribute:

      switch (argc) {
         case 4:
            outputFileName = argv[3];
            [[fallthrough]];
            
         case 3:

Reader is then aware that the missing break; is not actually missing by accident, but intentional. Improves code readability and quality, and silences the compiler warning about the missing break.

To make sure the caller of the function handles the return value, use the [[nodiscard]] attribute:

[[nodiscard]]
int readFile(const std::string & fileName, std::vector<std::string> & entries);

Compiler will warn you that the return value is not handled. This again improves code quality.

nodiscard attribute warns you that essential return value is not handled.

Using using instead of typedef

I wanted to use a shorter name for a complex data structure. Usually done with typedef. Instead, using the using keyword, the one used usually with namespaces, is neat:

using queue_package_type = std::map<std::string, std::pair<int,int>>;
queue_package_type queuePackageCounts;

Or similarily:

using NodeContainer = std::vector<NodeView>;
NodeContainer nodes;
// ...
SPConfigurator::NodeContainer nodes = configurator->getNodes();
std::for_each(std::begin(nodes), std::end(nodes), [this](const NodeView & node) {
   std::string description = node.getInputAddressWithPort() + "\t" + node.getName() + "\t" + node.getOutputAddressWithPort();
   QString logEntry = QString::fromStdString(description);
   ui->LogView->appendPlainText(logEntry);
});

Small thing but makes better looking code, in my opinion. When working with templates, The alias declaration with using is compatible with templates, whereas the C style typedef is not.

Algorithms

In a recent post, I mentioned algorithms like std::iota and std::shuffle, useful in generating test data. When handling containers (vectors, lists), the “old way” is to use either indexes or iterators to handle the items. Implementing these carelessly may lead to bugs. The better alternative is to use algorithms from the standard library, readily developed and rigorously tested, also considering performance. An example from a small tool app I recently made, which searches if id values read from one file are contained in lines read from another file:

std::for_each(std::begin(indexes), std::end(indexes), [&matchCount, &dataEntries, &output](const std::string & index) {
   std::any_of(std::begin(dataEntries), std::end(dataEntries), [&matchCount, &index, &output](const std::string & dataEntry) {
      if (dataEntry.find(index) != std::string::npos) {
         *output << matchCount+1 << "   " << dataEntry << std::endl;
         matchCount++;
         return true; // Not returning from the app but from the lambda function.
      }
      return false;   // Not returning from the app but from the lambda function.
   });
});

std::for_each replaces loops created by using iterators (or indexes to the container), and when some additional logic is needed, std::any_of is a nice solution to end the search when a match is found.

A bit more complicated example, using std::find_if, std::all_of and a boolean predicate object assisting in the search when calling std::find_if. In this example (full source code is here), there is a composite design pattern implemented for handling hierarchical key-value -pairs. The code sample below implements removing a specific key-value pair from the object hierarchy.

/**
 A helper struct to assist in finding an Entity with a given name. Used
 in EntityComposite::remove(const std::string &) to find an Entity with a given name.
 */
struct ElementNameMatches {
   ElementNameMatches(const std::pair<std::string,std::string> & nameValue) {
      searchNameValue = nameValue;
   }
   std::pair<std::string,std::string> searchNameValue;
   bool operator() (const Entity * e) {
      return (e->getName() == searchNameValue.first && e->getValue() == searchNameValue.second);
   }
};

/**
 Removes and deletes a child entity from this Entity.
 If the child is not an immediate child of this entity, then it is given
 to the children to be removed from there, if it is found.
 If the child is a Composite, removes and deletes the children too.
 @param nameValue A child with the equal name and value properties to remove from this entity.
 @return Returns true if the entity was removed, otherwise false.
 */
bool EntityComposite::remove(const std::pair<std::string,std::string> & nameValue) {
   bool returnValue = false;
   auto iter = std::find_if(children.begin(), children.end(), ElementNameMatches(nameValue));
   if (iter != children.end()) {
      Entity * entity = *iter;
      children.remove(*iter);
      delete entity;
      returnValue = true;
   } else {
      // child was not an immediate child. Check if one of the children (or their child) has the child.
      // Use a lambda function to go through the children to find and delete the child.
      // std::all_of can be stopped when the child is found by returning false from the lambda.
      std::all_of(children.begin(), children.end(), [nameValue, &returnValue](Entity * entity) {
         if (entity->remove(nameValue)) {
            returnValue = true;
            return false;
         } else {
            return true;
         }
      });
   }
   return returnValue;
}

// And then call remove() like this, for example, with 
// key "customer", and value "Antti Juustila":
newComposite->remove({"customer", "Antti Juustila"});

What you get is more robust code without your own bugs implemented in “custom” loops with indexes and iterators.

std::variant from C++17

What if your app has some data that can be manipulated in two formats? For example, first you get the data from the network in JSON, and then later you parse the JSON string and create an application specific object holding that parsed data. Later on, you again export the data from the internal object type to JSON to be send over to the network.

You could implement this so that you have both the JSON/string object and the application internal class object in memory. Then just add logic to know which currently has the data and should be used, and ignore the other variable until it is needed. An alternative is to use the good old union to handle this, if you want to save memory. This could be quite complicated to implement.

C++17 provides a more well managed option — std::variant. When using union, you have to keep track what the union contains, but using the variant, it knows which type of object it is currently holding and you can check that.

Following the scenario above, a class could have a member variable holding the JSON in a string, or alternatively, after parsing it, in an application specific object, within an unique pointer assisting with memory management:

std::variant<std::string, std::unique_ptr<DataItem>> payload;

In the class containing the payload member variable, you can initialise it to an empty string:

Package::Package()
: payload("")

Then you can provide setters to change from one representation of the data to another:

// Set the data to be a JSON string:
void Package::setPayload(const std::string & d) {
   payload = d;
}
// ...or a DataItem object, parsed from the string:
void Package::setPayload(std::unique_ptr<DataItem> item) {
   payload = std::move(item);
}

When you access the data to use it somewhere, you can check what is actually stored in the variant and return it. If the representation is not the one requested, return an empty value or null pointer to indicate to the caller that the requested representation of the data is not available currently:

// Get the string, using std::get_if:
const std::string & Package::getPayloadString() const {
   auto item = std::get_if<std::string>(&payload);
   if (item) {
      return *item;
   }
   return emptyString;
}
// Get the DataItem, using std::get_if
const DataItem * Package::getPayloadObject() const {
   auto item = std::get_if<std::unique_ptr<DataItem>>(&payload);
   if (item) {
      return item->get();
   }
   return nullptr;
}

Next I’d like to take a look at how to use the new async programming features of C++, as well as the Boost asio library…

Xcode quick action scripts to jump to GitHub or Bitbucket repository

Stumbled upon this handy little macOS quick action script project in GitHub which support opening the code selected in Xcode in a browser so that the selected code file in GitHub repository is opened and selected lines are highlighted in the browser. Or you could copy the link from Xcode to GitHub hosted project, while from Xcode, and share the link to someone by email or whatever.

Image from the original repo by wojteklu

Neat, wanted to try it out and cloned the project and installed the action scripts. That was easy, just double click on the scripts and they are installed.

I tried this with a project that is actually hosted in Bitbucket.org. Obviously it didn’t work out well. I forked the project, cloned it and copied and opened the action scripts in Automator app, edited the scripts to fit Bitbucket and now I have another pair of scripts which do work also with Bitbucket hosted repositories!

The changes to the scripts were quite easy to implement. Just change .com to .org, and instead of GitHub’s “blob” path, use the Bitbucket’s “src” path for the code files. And instead of addressing code lines the GitHub way (#L54-L58), use the Bitbucket’s way (#lines-33:35). Here’s an example link to a GitHub repo…

https://github.com/anttijuu/PluginDllDemo/blob/0f7b83f7ada5613cb0561a63a8c7d09f95f46144/EasyCryptoReversePlugin/EasyCryptoPlugin.cpp#L54-L58

… and here’s an example of a Bitbucket link (note: these links to BitBucket project ohar2014 are no longer valid since I have chopped the project into separate components in separate repositories):

https://bitbucket.org/anttijuu/ohar2014/src/e6fd26ef3d142d3da0cbe5969e6f037e465cede5/BaseLayer/ConfigurationDataItem.cpp#lines-33:35

The long code in both of the links is the current commit id of the version I’m viewing in Xcode. And here’s the relevant parts of my new shiny Action script source code…

on run {input, parameters}
	tell application "Xcode"
		set activeDocument to document 1 whose name ends with (word -1 of (get name of window 1))
		set activeDocumentPath to path of activeDocument
		set currentLines to selected paragraph range of activeDocument
		set startLine to get item 1 of currentLines
		set endLine to get item 2 of currentLines
	end tell
	
	set repositoryUrl to do shell script "cd $(dirname " & activeDocumentPath & "); git remote -v | grep fetch | awk '{print $2}' | sed 's/git@/https:\\/\\//' | sed 's/org:/org\\//' | sed 's/\\.git//'"
	
	set pathToRepositoryDirLocation to do shell script "cd $(dirname " & activeDocumentPath & "); git rev-parse --show-toplevel "
	
	set activeFilePathInRepository to replace_text(activeDocumentPath, pathToRepositoryDirLocation, "")
	
	set currentCommit to do shell script "cd $(dirname " & activeDocumentPath & "); git rev-parse HEAD"
	
	set bitbucketUrl to repositoryUrl & "/src/" & currentCommit & activeFilePathInRepository & "#lines-" & startLine & ":" & endLine
	
	set the clipboard to bitbucketUrl
	
	return input
end run

…and the script itself installed and visible in the Xcode menu!

Action scripts installed for Bitbucket.

I also made a pull request to the original project, in case he wants to merge my contribution into it.

Generating test data with C++

The last time I held the Software Architectures course, I wanted to demonstrate students how to test the performance and reliability quality attributes of a distributed software system. The system already had a feature to process data as a batch by reading data files and processing that data in the networked nodes. All I needed to do was to generate data files with thousands of records to read and process in the system. I implemented a small tool app to generate this test data.

First of all, when generating thousands of records, I wanted to preallocate the necessary buffers to make sure that during data creation no unnecessary buffer allocations are made, making data generation faster:

std::vector<int> generatedStudentNumbers;
if (verbose) std::cout << "Creating numbers for students..." << std::endl;
generatedStudentNumbers.resize(studentCount);

resize() allocates big enough vetor for the data. For creating student numbers (int), std::iota and std::shuffle are quite useful:

// Generate student numbers starting from one to studentCount.
std::iota(generatedStudentNumbers.begin(), generatedStudentNumbers.end(), 1);
// Shuffle the numbers randomly.
std::shuffle(generatedStudentNumbers.begin(), generatedStudentNumbers.end(), std::mt19937{std::random_device{}()});

std::iota fills the container with continuous values starting from 1 in this case. std::shuffle puts the numbers in random order. Voilá, you have a long vector of randomly ordered student numbers you can use in the data generation with only four lines of code!

Next, I needed random names for the students for the data set. For that, I needed a vector of names and then randomly get a name from that vector when creating the student records:

std::vector<std::string> firstNames;
firstNames = {"Antti", "Tiina", "Pentti", "Risto", "Päivi", "Jaana", "Jani", "Esko", "Hanna", "Oskari"};

// Initialize the random engine
std::random_device rd;
std::default_random_engine generator(rd());

// Generate a random int from a range
int  generateInt(int maxValue) {
   std::uniform_int_distribution<int> distribution(0,maxValue);
   return distribution(generator);
}

// Pick one random name
const std::string & getFirstName() {
   int index = generateInt(firstNames.size()-1);
   return firstNames[index];
}

generateInt() helper function is used to get a random name from the firstNames array. The same procedure was used to generate a last name and the study program name for the student. Then all these pieces of information was stored into a record, basically a tab separated std::string. Records, in turn, were contained in a vector of strings.

What is then left is storing the test data records into a file:

std::ofstream datafile(fileName, isFirstRound ? std::ios::trunc : std::ios::app);

// Shuffle the records randomly.
std::shuffle(buffer.begin(), buffer.end(), std::mt19937{std::random_device{}()});
auto save = [&datafile](const std::string & entry) { if (entry.length() > 0) datafile << entry << std::endl; };
std::for_each(buffer.begin(), buffer.end(), save);
datafile.close();

After opening the file stream, first again use std::shuffle to put the data into random order, then use the save lambda function to define what saving a record means. Then just pass this lambda to std::for_each to tell what to do to each of the data records — save them into the std::ofstream.

Finally I made the data generator tool configurable with command line parameters, using Sarge:

Sarge sarge;
sarge.setUsage("./GenerateTestData -[hv]s <number> [-e <number>]");
sarge.setDescription("A test data generator for StudentPassing system. (c) Antti Juustila, 2019.\nUses Sarge Copyright (c) 2019, Maya Posch All rights reserved.");
sarge.setArgument("h", "help", "Display help for using GenerateTestData.", false);
sarge.setArgument("v", "verbose", "Display detailed messages of test data generation process.", false);
sarge.setArgument("s", "students", "Number of students to generate in test data files.", true);
sarge.setArgument("e", "exercises", "Number of exercises generated, default is 6 if option not provided.", true);
sarge.setArgument("b", "bufsize", "Size of the buffer used in generating data", true);

I used the test data generator tool to generate up to 10 000 records and used those test data files to see and demonstrate students how the system manages high data throughput and what which performance. It was also interesting to see what the performance bottlenecks were in the system.

Next year (the last time teaching this course) I’ll demonstrate how to use a thread to read the data files, while at the same time reading test data from the network in another thread. There is a large impact on whether using std::thread.join() or .detach() to control how the networking and data file reading threads cooperate.

Changes at work

Our curriculum, Information processing Science, has been and is going through big changes. Many courses are put to rest and new ones created. Personally, this means many changes to my teaching portfolio.

Firstly, the course on Embedded Software Development Environments will cease to exist. ESDE, a master level course, was a successor to Mobile systems development course I taught in cooperation with Tampere University of Technology. Even before that, I was involved with mobile development related courses Symbian programming. So the years long track of teaching mobile programming, lately on Android, is now closing for me.

Another master level course disappearing is the Open Source Software Development, where I was responsible for course exercises for the past two years. There the students worked — under my watchful eye — on a distributed Java client-server project hosted in GitHub. The goal of that project was to learn how to communicate and coordinate fixing issues and providing new features to the project by using git: forking, branching, committing and providing pull requests to the main project, following the contribution guides and license terms of the project.

Third course I have a long history with is the Software engineering (BSc) course. My first paid job as a student at the Department of Information Processing Science somewhere around the end of 1980s was to type the handwritten lecture notes of professor Koskela to a Word document. Printouts were then sold as course material by the student guild Blanko. I also taught the course at the Open University, travelling to many cities and villages in Northern Finland for Friday night and Saturday lecturing sessions. Later, I was responsible for the course and the development of it for many years. The course is now changed from a 2nd study year course, integrating previous courses’ content before practical software project, to an introductory course in the first study year. Others will take charge.

Finally, I have decided to leave the Software architectures course. This 2nd year bachelor level course also has a long history of cooperation with Tampere University of Technology. The course is their making, based on their significant research on software architectures. At some point, I decided to take the exercises here in Oulu to more practical direction, studying source code of existing distributed systems and transforming the architecture based on new functional and non-functional requirements. In my opinion, this approach better illustrates the practical significance of various architectural solutions and the purpose of architectural diagrams, than just drawing pictures without any concrete connection to actual source code. The cooperation with TUT ceased some years ago but since their material was open sourced, I continued to use the lecture slides (updated by me yearly) with my own exercises and exercise projects.

Leaving now these courses behind, I will return back to teaching programming courses in bachelor level. I will take charge of Data structures and algorithms course, and will spend a considerable amount of teaching hours on a new course on server side app development. There is also one new course in the master level on platforms and ecosystems I am participating in, but the course is in the very early stages of development, so not much to say about that yet.

Remember to join

When I forget to join (or detach) a C++ std::thread, it’ll crash with SIGABRT(6) on macOS. And obviously the stack dump does not tell me what is going on. So I hunt the bug for some hours, digging the log files, then finally remember that I just implemented a thread…

***** FATAL SIGNAL RECEIVED *******
Received fatal signal: SIGABRT(6)	PID: 17403

***** SIGNAL SIGABRT(6)

*******	STACKDUMP *******
	stack dump [1]  1   libg3logger.1.3.2-80.dylib          0x0000000100cb2163 _ZN12_GLOBAL__N_113signalHandlerEiP9__siginfoPv + 83
	stack dump [2]  2   libsystem_platform.dylib            0x00007fff72f73b1d _sigtramp + 29
	stack dump [3]  3   ???                                 0x0000000000000400 0x0 + 1024
	stack dump [4]  4   libsystem_c.dylib                   0x00007fff72e49a1c abort + 120
	stack dump [5]  5   libc++abi.dylib                     0x00007fff6fef2bc8 __cxa_bad_cast + 0
	stack dump [6]  6   libc++abi.dylib                     0x00007fff6fef2ca6 _ZL28demangling_terminate_handlerv + 48
	stack dump [7]  7   libc++abi.dylib                     0x00007fff6feffda7 _ZSt11__terminatePFvvE + 8
	stack dump [8]  8   libc++abi.dylib                     0x00007fff6feffd68 _ZSt9terminatev + 56
	stack dump [9]  9   BasicInfoGUI                        0x0000000100af1ffa _ZN11OHARStudent14StudentHandler8readFileEv + 42
	stack dump [10]  10  BasicInfoGUI                        0x0000000100af28df _ZN11OHARStudent14StudentHandler7consumeERN8OHARBase7PackageE + 2223
	stack dump [11]  11  BasicInfoGUI                        0x0000000100a6d101 _ZN8OHARBase13ProcessorNode14passToHandlersERNS_7PackageE + 897
	stack dump [12]  12  BasicInfoGUI                        0x0000000100a6a69e _ZN8OHARBase13ProcessorNode10threadFuncEv + 2462
	stack dump [13]  13  BasicInfoGUI                        0x0000000100a79061 _ZNSt3__1L8__invokeIMN8OHARBase13ProcessorNodeEFvvEPS2_JEvEEDTcldsdeclsr3std3__1E7forwardIT0_Efp0_Efp_spclsr3std3__1E7forwardIT1_Efp1_EEEOT_OS6_DpOS7_ + 113
	stack dump [14]  14  BasicInfoGUI                        0x0000000100a78f6e _ZNSt3__1L16__thread_executeINS_10unique_ptrINS_15__thread_structENS_14default_deleteIS2_EEEEMN8OHARBase13ProcessorNodeEFvvEJPS7_EJLm2EEEEvRNS_5tupleIJT_T0_DpT1_EEENS_15__tuple_indicesIJXspT2_EEEE + 62
	stack dump [15]  15  BasicInfoGUI                        0x0000000100a78796 _ZNSt3__114__thread_proxyINS_5tupleIJNS_10unique_ptrINS_15__thread_structENS_14default_deleteIS3_EEEEMN8OHARBase13ProcessorNodeEFvvEPS8_EEEEEPvSD_ + 118
	stack dump [16]  16  libsystem_pthread.dylib             0x00007fff72f7ed36 _pthread_start + 125
	stack dump [17]  17  libsystem_pthread.dylib             0x00007fff72f7b58f thread_start + 15

Exiting after fatal event  (FATAL_SIGNAL). Fatal type:  SIGABRT
Log content flushed sucessfully to sink

So I am writing this down, just to remember next time. Join or detach. Join or detach….

   void StudentHandler::readFile() {
      std::thread( [this] {
         StudentFileReader reader(*this);
         using namespace std::chrono_literals;
         std::this_thread::sleep_for(50ms);
         reader.read(node.getDataFileName());
      }).join();
   }

Edit: Actually, I changed join() to detach(). With join, the calling thread waited for this file reading thread to finish before continuing to handle incoming data from network. The file was read totally in memory, and only then data from network was combined with data from file and send ahead to next node in the network. With 5000 test records, all of them were held in memory waiting for the data from network to arrive when using join().

When I switched to use detach(), calling thread could continue reading data from network, while simultaneously file reading thread was reading data from file. Whenever a match was found in a list, either one of these threads, the data was combined and send ahead to the next node in the network. So with join, maximum of 5000 records were held in memory all the time, as with detach(), about 1300-1800 records were held in memory at most. Because combined data could be send ahead to next node in the network and discarded from this node. A significant change in the amount of memory the nodes use. So it does matter which you use, depending on the purpose of the threads in your app.

Authentication to a server in an Android app

I have been playing around with an Android app as a side hobby for a while now. It is a simple game app, which can load new game boards from a server using HTTP GET. So the data does not contain any personal or business critical data.

But since I like to (at least try to) implement things well and learn more in my toy projects, I decided to protect the game board directory in the server side using .htaccess and to move to using HTTPS. This way the authentication HTTP headers containing the user name and password in the request are encrypted, protecting login info while it is transmitted over the network.

HttpsURLConnection urlConnection = null;
String[] games = null;
try {
  URL serverUrl = new URL(serverAddress + scriptName);
  urlConnection = (HttpsURLConnection) serverUrl.openConnection();
  String authString = getUserNameAndPassword();
  String authEncodedString = Base64.getEncoder().encodeToString(authString.getBytes());
  urlConnection.setRequestProperty("authorization", "Basic " + authEncodedString);
  response = urlConnection.getResponseCode();
  if (response == HttpURLConnection.HTTP_OK) {
// ....

The method getUserNameAndPassword() returns a string username:hashed-password, used in the authentication of the client.

Since using HTTPS, that information is encrypted while transmitted. But what about in the actual application binary? If you store the username and password as plain strings in the code, it would be relatively easy by investigating the binary to dig out this data and use it for unauthorised access to the server. In my app, that wouldn’t be such an issue, since the server side code only supports HTTPS GET to list the available game board files and there is nothing else you could do there.

Anyways, I decided to find a way to try to make it a bit more difficult for anyone to dig out authentication strings from the client binary. I searched for possible solutions, of which this article is a good summary.

In my app, I decided not to use any proxies or cloud services, since all that trouble is not worth it, considering the nature of data my app uses. There is no perfect way to do this since any method has their weaknesses. I picked one simple way to do it to learn how it would actually work in my case.

What I decided to do was this:

  1. First and most obviously, I used long and cryptic username and password values when creating the username and password for .htaccess.
  2. I made sure that the .password file is not in the server directories the HTTP server could read, thus inaccessible from the network using HTTP. This is also something very obvious one should always do.
  3. I took the generated and hashed password, and further obfuscated it and the username using (for example) xor and then rot13. To do this, I implemented a small tool app which takes in the username:hashed-password String and outputs the obfuscated byte array from this step. Xor nor rot13 are not very efficient encryption methods, but still manage to make finding out the passwords a bit more difficult. When doing the xor, I use a byte array, not a String. You could add even more steps here, using other simple additional obfuscation methods, as long as you make sure that you can reverse the process and arrive — using the exact opposite steps — to the original username and password.
  4. I placed the obfuscated username and password bytes in the client app source code not in a String but in a byte array in the client code. So anyone looking for username or password Strings from the code is not going to find it. Obviously the bytes used in xor step need to be in the client code too.
  5. I further divided the byte arrays from step 4 into 2-3 different byte arrays in different places in the code, so that they are not in a continuous area of memory. At this point you could also e.g. reverse bytes in some of the byte arrays to further confuse the possible investigator of the app binary.

So, when the client code is then executed, the divided byte arrays are (some possibly reversed; step #5) combined, then a String is created from them at run time. After this, the obfuscation steps from #3 above are executed in reverse order (e.g. first rot13 then xor), producing the original username and the hashed password. This is then put in the authorization header of the HTTPS request.

Though not perfect, this is in my applications case a good enough security solution. Obviously you should not publicly share which substitution ciphers or other methods (xor, rot13, reversing, …) you use in obfuscation.

Below is the output from the tool to create obfuscated authorisation bytes.

Original string: demousername:hashed-demo-password-from-htpasswd-tool
Rotted: qrzbhfreanzr:unfurq-qrzb-cnffjbeq-sebz-ugcnffjq-gbby
encoded: EBVOVgALBQATDwkTXUFaDhgFFF8QARsFGVcGCxEPEAQCTBRRVhJAAgIRDxUHDUUZDw8VHA==
Take these bytes to a byte array in client app:
{69,66,86,79,86,103,65,76,66,81,65,84,68,119,107,84,88,85,70,97,68,104,103,70,70,70,56,81,65,82,115,70,71,86,99,71,67,120,69,80,69,65,81,67,84,66,82,82,86,104,74,65,65,103,73,82,68,120,85,72,68,85,85,90,68,119,56,86,72,65,61,61}
With this key:
{97,103,52,52,104,109,119,101,114,97,115}

Taas blogataan

Aikoinaan töissä Tietojenkäsittelytieteiden laitoksella asensin WordPressin laitoksen palvelimelle. Tein erinäisille kursseille joita silloin opetin, omat sivut WordPressiin. Ohjelmointiympäristöt, Symbian -ohjelmointi, Mobiilijärjestelmien ohjelmointi, ja niin edelleen. Lisäksi bloggasin yleisesti kiinnostavista alan asioista; koodiesimerkkejä ja muuta.  Kun laitoksen oma infra sitten lakkautettiin ja siirryttiin yliopiston palvelimien käyttöön, loppui tämän WordPress-sivun käyttö.

Bloggaus jäi tauolle, kunnes otin välivuoden ja lähdin Italiaan opiskelemaan klassista maalaustaidetta. Kirjoitin Bloggerissa tuon vuoden kuulumisia lähinnä kotiväelle, ja sitten palattuani takaisin Suomeen jatkoin tuossa taideblogissa. Taidesivusto on elänyt hiljaiseloa erinäisten syiden takia, mutta sekä taiteen tekeminen että blogin pitäminen kyllä jatkuu kunhan taas tilanteet muuttuvat.

Tämän blogin tarkoitus on olla kirjoitusalusta yleisemmille asioille; taide saa olla tuossa toisessa blogissa se kantava voima. Kirjoittelen tänne lähinnä yliopistotyöhön ja softakehitykseen liittyvistä asioista. Todennäköisesti kirjoitustahti ei ole päätä huimaava, bloggaan kunhan ehdin ja tulee jotain sanottavaa.