Created
April 11, 2013 10:31
-
-
Save kafecho/5362350 to your computer and use it in GitHub Desktop.
Bit wrangling in Scala...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.kafecho.learning.akka | |
import akka.util.ByteString | |
import org.slf4j.LoggerFactory | |
object Timer{ | |
def timeThis[T](fn : => T) : T = { | |
val start = System.currentTimeMillis() | |
val t = fn | |
val stop = System.currentTimeMillis() | |
println (s"It took ${stop-start} msecs to execute the code.") | |
t | |
} | |
} | |
trait Logging{ | |
val log = LoggerFactory.getLogger(this.getClass) | |
} | |
/** A class which extracts bit-level data from an Akka ByteString. | |
* | |
* You can use the BitWrangler to read boolean values at a certain bit offset within a ByteString, or to read integer values of a given length from a given offset within a ByteString. | |
* For the time being, I assume big endian-ness. | |
* | |
* I've written this to see how I can simplify extracting bit-level information from network packets (UDP) which I am processing with Akka. | |
* Using the BitWrangler to extract bit-level info is more readable than doing the bit shifting by hand, however, it is slower. | |
* I am thinking of writing a Scala Macro to emit the bit-shifting code, that would make it faster I hope. | |
* | |
*/ | |
object BitWrangler extends Logging{ | |
class ByteStringBitIterator(implicit src: ByteString){ | |
var offset = 0 | |
def boolean = { | |
val res = BitWrangler.boolean(offset) | |
offset += 1 | |
res | |
} | |
def int(nbBits: Int) = { | |
val res = BitWrangler.int(offset, nbBits) | |
offset += nbBits | |
res | |
} | |
} | |
private[this] val bitMasks = Map( | |
0 -> 0x80, | |
1 -> 0x40, | |
2 -> 0x20, | |
3 -> 0x10, | |
4 -> 0x08, | |
5 -> 0x04, | |
6 -> 0x02, | |
7 -> 0x01) | |
private[this] val lengthMasks = Map( | |
1 -> 0x01, | |
2 -> 0x03, | |
3 -> 0x07, | |
4 -> 0x0F, | |
5 -> 0x1F, | |
6 -> 0x3F, | |
7 -> 0x7F, | |
8 -> 0xFF) | |
private[this] def int(b: Byte, offset: Int, nbBits: Int): Int = { | |
((b & 0xff) >> (8 - offset - nbBits)) & lengthMasks(nbBits) | |
} | |
/** Read a boolean value at a given bit offset within a ByteString */ | |
def boolean(offset: Int)(implicit bytes: ByteString): Boolean = { | |
val byteOffset = offset / 8 | |
val bitOffset = offset % 8 | |
val asInt = bytes(byteOffset) & 0xFF | |
(asInt & bitMasks(bitOffset)) != 0 | |
} | |
/** Read an integer value at a given bit offset with a given lenght within a ByteString */ | |
def int(offset: Int, nbBits: Int)(implicit bytes: ByteString): Int = { | |
require(nbBits > 0) | |
require(nbBits <= 32) | |
val startByte = offset / 8 | |
val endByte = (offset + nbBits - 1) / 8 | |
val subset = bytes.slice(startByte, endByte + 1) | |
var i = 0 | |
var value = 0 | |
var size = 0 | |
var lastRead = 0 | |
while (i < subset.size) { | |
if (i == 0){ | |
val firstOffset = offset % 8 | |
lastRead = nbBits.min(8 - firstOffset) | |
size = nbBits - lastRead | |
value = int(subset(i), firstOffset, lastRead) | |
} else { | |
value = (value << lastRead) | |
lastRead = 8.min(nbBits - size) | |
val anotherValue = int(subset(i), 0, lastRead) | |
value = value | int(subset(i), 0, lastRead) | |
size = size - lastRead | |
} | |
i += 1 | |
} | |
value | |
} | |
} | |
/** A simple hand-written parser which extracts information from a RTP packet. | |
* See http://en.wikipedia.org/wiki/Real-time_Transport_Protocol | |
*/ | |
trait RTPPacketParser { | |
val bytes: ByteString | |
def version = (bytes(0) & 0xc0) >> 6 | |
def padding = (bytes(0) & 0x20) != 0 | |
def extension = (bytes(0) & 0x10) != 0 | |
def csrcCount = (bytes(0) & 0x0f) | |
def marker = (bytes(1) & 0x80) != 0 | |
def payloadType = (bytes(1) & 0x7f) | |
def sequenceNumber = (bytes(2) & 0xff) << 8 | (bytes(3) & 0xff) | |
def timestamp = ((bytes(4) & 0xff) << 24) | ((bytes(5) & 0xff) << 16) | ((bytes(6) & 0xff) << 8) | (bytes(7) & 0xff) | |
def srscID = ((bytes(8) & 0xff) << 24) | ((bytes(9) & 0xff) << 16) | ((bytes(10) & 0xff) << 8) | (bytes(11) & 0xff) | |
def payload = bytes.drop(12 + csrcCount * 4) | |
override def toString = "RTPPacket(" + | |
"version = " + version + ", " + | |
"padding = " + padding + ", " + | |
"extension = " + extension + ", " + | |
"csrcCount = " + csrcCount + ", " + | |
"marker = " + marker + ", " + | |
"payloadType = " + payloadType + ", " + | |
"sequenceNumber = " + sequenceNumber + ", " + | |
"timestamp = " + timestamp + ", " + | |
"srscID = " + srscID + ")" | |
} | |
/** A parser which uses the wrangler to extract information from an RTP packet.*/ | |
trait RTPPacketParser2{ | |
implicit val bytes : ByteString | |
import BitWrangler._ | |
def version = int(0,2) | |
def padding = boolean(2) | |
def extension = boolean(3) | |
def csrcCount = int(4,4) | |
def marker = boolean(8) | |
def payloadType = int(9,7) | |
def sequenceNumber = int(16,16) | |
def timestamp = int(32,32) | |
def srscID = int(64,32) | |
def payload = bytes.drop(12 + csrcCount * 4) | |
override def toString = "RTPPacket(" + | |
"version = " + version + ", " + | |
"padding = " + padding + ", " + | |
"extension = " + extension + ", " + | |
"csrcCount = " + csrcCount + ", " + | |
"marker = " + marker + ", " + | |
"payloadType = " + payloadType + ", " + | |
"sequenceNumber = " + sequenceNumber + ", " + | |
"timestamp = " + timestamp + ", " + | |
"srscID = " + srscID + ")" | |
} | |
case class RTPPacket(bytes: ByteString) extends RTPPacketParser | |
case class RTPPacket2(bytes: ByteString) extends RTPPacketParser2 | |
object Test extends App with Logging{ | |
val bytes = ByteString(-128, -95, 101, 110, 69, -59, -30, 0, 51, 14, -91, 27, 71, 64, 68, 29, 0, 0, 1, -64, 1, 10, -128, -128, 5, 43, 23, 23, -11, -115, -1, -15, 80, -128, 32, 95, -4, 33, 76, -2, -1, -4, -99, -102, 80, -92, -77, 66, -92, -53, -81, 109, -15, -41, 83, 121, -65, 35, 87, -58, 126, 49, -27, -33, 90, -112, -66, 79, -19, 124, 107, -26, 94, 49, -76, -66, 111, 103, 73, -87, -111, 115, -74, -17, 89, 122, -101, -108, -10, -15, 15, -74, -107, 29, -57, 116, 122, 44, 41, -110, 12, -54, 44, 103, 114, 107, -117, 114, 127, 116, -75, -47, 20, 99, 73, 115, 7, -100, -121, -46, -18, -95, -2, -118, -33, -84, -128, 22, 26, -89, 50, 26, -12, 122, 32, -85, 47, 102, -67, -20, -29, 10, -46, -85, -53, 126, 27, 50, -18, 122, 114, -18, 58, 91, 26, -36, 45, 7, -57, -13, -47, -81, 73, -23, -30, 43, -78, -101, 47, 55, -68, -62, 14, -100, -24, -99, -84, -121, -76, -52, -24, -18, -22, -23, -123, -72, -44, 116, 57, -115, 0, -110, -50, 96, -67, 13, -42, 123, 17, 44, 71, 0, 69, 19, 62, 70, -51, 41, -75, -14, 115, -115, 124, -11, -8, 16, -61, 122, -77, 125, -118, -30, 104, 31, 102, -123, 34, 78, 125, 36, -81, 62, 97, 25, -21, 21, 56, -98, 77, 68, -8, 34, -122, -115, -15, -35, 89, -115, -87, 62, -30, 88, 55, -121, 86, 86, -113, -95, 86, -12, -90, 44, 32, 38, -86, -67, -115, -8, 69, -34, 49, -128, -30, 75, -53, 55, -119, 55, 85, -54, -109, 112, 29, 119, -85, -75, 87, 15, 111, -126, -22, 13, 12, -125, -101, -33, 99, 77, 66, -23, 120, -102, 10, 31, 107, 102, 101, 122, 15, 115, 114, 115, 91, 117, -65, -26, -6, 83, -109, 20, 48, -114, 55, 5, 11, 105, 96, -31, 5, 18, -22, 11, 49, -19, 115, 114, 3, -28, -113, 120, 57, 104, 33, -101, 68, 70, -36, -17, -61, 44, 64, -32, -21, -78, 122, 108, -13, 1, -125, 14, 97, -27, -116, 71, 71, 75, 123, 13, 61, -47, -108, -31, -42, -4, 116, -75, -44, 90, -70, -27, -39, -37, -74, 48, -60, 1, -104, -71, 71, 0, 69, 20, -108, 123, 31, 117, -60, 73, 28, 102, 50, -16, -4, 60, -107, -2, 8, 28, 69, -51, -20, -13, -120, -25, -28, 98, 4, 103, -72, 0, 60, 59, -7, -57, -71, -87, -11, -35, 39, -115, -87, 97, 25, 28, -48, 62, -75, 30, -105, -93, -103, 75, 117, -56, 12, -35, -94, -76, -42, 121, -8, -9, 88, -104, 16, -87, -116, 68, 35, 116, -71, -61, 76, -102, 63, 19, -62, 118, 4, 71, 67, 104, 111, 9, -98, -98, 23, 73, -72, 5, -111, 60, -70, 13, -112, -104, 107, -41, 13, -47, -30, 92, -111, -28, -81, 126, 109, 120, -113, -102, 23, -71, 96, -62, 22, -125, -48, -25, 39, -12, -36, -127, 61, -126, -2, 118, 43, -7, -45, -65, 21, 113, 95, -75, 69, -73, -60, -3, -17, 95, -48, 27, 83, 42, -48, 14, 58, -106, -40, 67, -58, 4, 17, -15, 8, 17, -26, 38, 110, 104, 80, -99, 33, 63, -117, 6, 98, 33, 72, 24, 69, -69, -124, -13, -83, 53, 120, 19, -58, -9, 84, 53, -63, -42, -128, -9, 71, 0, 69, 21, -78, 118, -111, 53, 31, 48, 110, -5, -36, -126, 78, 62, 76, 23, 35, -125, 109, 70, 17, 25, 24, 8, 103, 9, 104, -19, -118, -85, 56, -14, 89, -32, 39, 39, 114, -60, -126, -113, 82, -104, 74, 43, -54, 35, -7, -64, 80, -90, 80, -119, 46, 28, -56, -22, 71, 92, -100, 29, -36, -52, -87, 82, -54, 111, 121, 67, 84, -117, -18, -4, 29, -47, -106, -96, -104, 72, 86, 7, 72, -60, -119, 29, 80, -72, 127, -120, -78, -112, 49, -24, -24, -70, 5, 59, 86, -48, 58, 83, -86, -109, 62, -47, 19, -68, -14, 14, -38, 83, 100, 49, -13, -44, 88, 54, 96, 58, -110, -109, 76, 17, -67, -44, 27, -57, 35, 18, 22, 64, -47, 33, 4, 94, 24, -76, 31, 44, -43, 37, -11, -121, 86, -75, -59, 122, -35, 86, -122, -21, 86, 70, -7, 20, -70, -50, -6, -54, -10, -60, -95, 5, 23, 90, -60, -44, 50, -92, -124, -74, 55, -90, 86, 87, 53, -118, -85, -83, 0, 67, -70, 120, 21, 116, 21, 72, 71, 0, 69, 22, -103, -116, -77, -28, -88, 33, 81, 20, 40, -121, 88, -19, -71, -117, -76, -75, -18, 68, -6, 76, 97, -73, 69, -5, 21, 82, -59, -64, -73, 2, -83, -112, -84, -91, 99, 84, 1, -9, -93, -111, 19, -24, -116, 62, 42, 3, 103, -116, 48, 2, -97, -101, 126, -64, 119, 17, -104, 23, -84, 43, 118, -14, 68, -114, -121, -37, -119, 103, 103, 53, 69, -71, -126, -108, 37, 89, 89, 54, 123, 103, 16, 18, -59, 7, -12, -67, 96, -106, -45, 7, -57, 86, -35, -22, -123, -62, -31, -127, 124, 67, -20, -34, -39, 58, 18, -115, 55, -9, 58, -6, 18, -53, -128, 36, -62, -109, -114, 106, 71, -23, -68, 77, 104, -28, -17, -126, 71, 63, 57, 109, -101, 19, 63, 116, 62, -9, -67, -5, -6, -43, 21, -21, 78, 110, 79, -82, 105, 112, 114, -123, -116, -31, 103, -98, 22, -12, -48, -27, -76, -95, -65, 83, 59, -3, 39, -16, 19, 7, -121, -6, 90, -19, 45, -76, -12, -94, -127, -24, 3, 99, -64, -102, 90, 97, 71, 0, 69, 55, -86, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -40, 126, -122, -73, -74, 101, 62, -73, -59, 93, -52, -1, -80, 71, 64, 69, 24, 0, 0, 1, -32, 20, 127, -128, -64, 10, 59, 23, 25, 52, -27, 27, 23, 23, -6, 61, 0, 0, 0, 1, 9, -32, 0, 0, 0, 1, 65, -101, -86, 73, -31, 15, 38, 83, 2, 43, -1, -21, 61, 32, -4, 114, -4, -17, -84, 29, -31, -123, -127, -31, 47, 73, 109, 68, -11, 120, -109, 46, -38, 25, -1, 13, 10, 106, -10, 70, 12, 64, 65, -3, 96, 60, -83, 40, -82, -99, 79, -85, -26, -68, 56, -72, 29, -56, -26, 65, 24, -107, -115, -74, -76, -91, 38, 94, 71, -101, -73, 110, -36, 82, -86, -55, 52, -11, -76, 85, 79, 56, 55, -128, 57, -117, -87, 54, -103, -55, -95, -83, -23, 94, -107, -33, -106, 124, -117, -28, 89, -3, -58, -48, -117, 58, -46, -25, -108, 18, -98, -105, 31, 38, 47, -72, -121, 108, 20, 3, -35, -115, 34, -63, -103, 90, -56, 54, -117, 36, -75, 109, -75, -120, 20, -32, 66, 36, 77, -101, 107, -48, 69, -39, -90, 78, 26, 99, -13, -1, -18, -62, 33, -43, -82) | |
val rtpPacket1 = RTPPacket(bytes) | |
val rtpPacket2 = RTPPacket2(bytes) | |
log.info("RTPPacket 1:{}.",rtpPacket1) | |
log.info("RTPPacket 2:{}.",rtpPacket2) | |
val nbIterations = 1000000 | |
for (i <- 0 to 20){ | |
Timer.timeThis{ | |
for ( i <- 0 to nbIterations){ | |
val rtpPacket = RTPPacket(bytes) | |
rtpPacket.toString | |
} | |
} | |
Timer.timeThis{ | |
for ( i <- 0 to nbIterations){ | |
val rtpPacket = RTPPacket2(bytes) | |
rtpPacket.toString | |
} | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment