Tuesday, May 12

Convert 8bit to 7bit GSM / SMS alphabet in .NET

The following function will convert a 8bit encoded string into it's 7 bit GSM 03.38 equivalent. Some code samples on the net simply use ASCII characters and ‘fix’ them by replacing "0x00" / Chr(0) with an @ / Chr(64) which will work for all standard letters and some symbols as the GSM 7 bit alphabet has a 1-1 mapping with unicode for these, however it won't work for other special characters. Using the GSMCharMap class also included below solves this.

Decode function:

Private Shared Function octetByteArray(ByVal str As String) As Byte()

Dim b As New List(Of Byte)

For i As Integer = 0 To CInt((str.Length)) - 1 Step 2
b.Add(Convert.ToByte(String.Format("0x{0}", str.Substring(i, 2)), 16))
Next

Return b.ToArray

End Function

Shared Function BinaryToInt(ByVal Binary As String) As Integer
Dim Result As Integer = 0
For i As Integer = 0 To Binary.Length - 1
Result += CInt(CInt(Binary.Substring(Binary.Length - i - 1, 1)) * 2 ^ i)
Next
Return Result
End Function

Public Shared Function convert8bitTo7bit(ByVal str8bit As String) As String

Dim byte8() As Byte = octetByteArray(str8bit)
Dim revBinaryString As String = String.Empty

Dim sb8 As New StringBuilder

' Append in reverse to simply pull 8th bit out in next loop
For iByte As Integer = byte8.Length - 1 To 0 Step -1
revBinaryString = revBinaryString & Convert.ToString(byte8(iByte), 2).PadLeft(8, CChar("0"))
Next

Dim charCount7bit As Integer = CInt(Math.Floor(CDbl(str8bit.Length / 2 * 8 / 7)))

Dim isChar27 As Boolean = False

For i As Integer = 1 To charCount7bit
' Convert 7 bits of binary to integer
Dim char7 As Integer = BinaryToInt(revBinaryString.Substring(revBinaryString.Length - i * 7, 7))

Dim map As New GSMCharMap

If Not isChar27 Then
If char7 <> 27 Then
sb8.Append(map.getChar(char7))
Else
isChar27 = True
End If
Else
sb8.Append(map.getChar27(char7))
isChar27 = False
End If
Next

Return sb8.ToString

End Function




GSMCharMap class





Public Class GSMCharMap


Private m_map(128) As Char
Private m_27map(128) As Char

Public Sub New()

m_map(0) = Chr(64) '@
m_map(1) = Chr(163)
m_map(2) = Chr(36) '$
m_map(3) = Chr(165)
m_map(4) = Chr(232)
m_map(5) = Chr(233)
m_map(6) = Chr(249)
m_map(7) = Chr(236)
m_map(8) = Chr(242)
m_map(9) = Chr(199)
m_map(10) = Chr(10) 'Line feed
m_map(11) = Chr(216)
m_map(12) = Chr(248)
m_map(13) = Chr(13) 'Carriage return
m_map(14) = Chr(197)
m_map(15) = Chr(229)
m_map(16) = CChar("Δ") 'Δ Capital greek delta, not in Unicode
m_map(17) = Chr(95) '_
m_map(18) = CChar("Φ") 'Φ Capital greek phi, not in Unicode
m_map(19) = CChar("Γ") 'Γ Capital greek gamma, not in Unicode
m_map(20) = CChar("Λ") 'Λ Capital greek lambda, not in Unicode
m_map(21) = CChar("Ω") 'Ω Capital greek omega, not in Unicode
m_map(22) = CChar("Π") 'Π Capital greek pi, not in Unicode
m_map(23) = CChar("Ψ") 'Ψ Capital greek psi, not in Unicode
m_map(24) = CChar("Σ") 'Σ Capital greek sigma, not in Unicode
m_map(25) = CChar("Θ") 'Θ Capital greek theta, not in Unicode
m_map(26) = CChar("Ξ") 'Ξ Capital greek xi, not in Unicode
m_map(27) = Chr(10) 'Escape to map27 list

m_27map(10) = Chr(12) ' Form feed
m_27map(20) = Chr(94) ' ^
m_27map(40) = Chr(123) '{
m_27map(41) = Chr(125) '}
m_27map(47) = Chr(92) '\
m_27map(60) = Chr(91) ' [
m_27map(61) = Chr(126) '~
m_27map(62) = Chr(93) ' ]
m_27map(64) = Chr(124) ' |
m_27map(101) = CChar("€") '€ Euro sign

m_map(28) = Chr(198)
m_map(29) = Chr(230)
m_map(30) = Chr(223) ' ß
m_map(31) = Chr(201) ' É
m_map(32) = Chr(32) '
m_map(33) = Chr(33) ' !
m_map(34) = Chr(34) ' "
m_map(35) = Chr(35) ' #
m_map(36) = CChar("¤") '¤ currency sign
m_map(37) = Chr(37) ' %
m_map(38) = Chr(38) ' &
m_map(39) = Chr(39) ' '
m_map(40) = Chr(40) ' (
m_map(41) = Chr(41) ' )
m_map(42) = Chr(42) ' *
m_map(43) = Chr(43) ' +
m_map(44) = Chr(44) ' ,
m_map(45) = Chr(45) ' -
m_map(46) = Chr(46) ' .
m_map(47) = Chr(47) ' /
m_map(48) = Chr(48) ' 0
m_map(49) = Chr(49) ' 1
m_map(50) = Chr(50) ' 2
m_map(51) = Chr(51) ' 3
m_map(52) = Chr(52) ' 4
m_map(53) = Chr(53) ' 5
m_map(54) = Chr(54) ' 6
m_map(55) = Chr(55) ' 7
m_map(56) = Chr(56) ' 8
m_map(57) = Chr(57) ' 9
m_map(58) = Chr(58) ' :
m_map(59) = Chr(59) ' ;
m_map(60) = Chr(60) ' <
m_map(61) = Chr(61) ' =
m_map(62) = Chr(62) ' >
m_map(63) = Chr(63) ' ?
m_map(64) = Chr(161) ' ¡
m_map(65) = Chr(65) ' A
m_map(66) = Chr(66) ' B
m_map(67) = Chr(67) ' C
m_map(68) = Chr(68) ' D
m_map(69) = Chr(69) ' E
m_map(70) = Chr(70) ' F
m_map(71) = Chr(71) ' G
m_map(72) = Chr(72) ' H
m_map(73) = Chr(73) ' I
m_map(74) = Chr(74) ' J
m_map(75) = Chr(75) ' K
m_map(76) = Chr(76) ' L
m_map(77) = Chr(77) ' M
m_map(78) = Chr(78) ' N
m_map(79) = Chr(79) ' O
m_map(80) = Chr(80) ' P
m_map(81) = Chr(81) ' Q
m_map(82) = Chr(82) ' R
m_map(83) = Chr(83) ' S
m_map(84) = Chr(84) ' T
m_map(85) = Chr(85) ' U
m_map(86) = Chr(86) ' V
m_map(87) = Chr(87) ' W
m_map(88) = Chr(88) ' X
m_map(89) = Chr(89) ' Y
m_map(90) = Chr(90) ' Z
m_map(91) = Chr(196) ' Ä
m_map(92) = Chr(214) ' Ö
m_map(93) = Chr(209) ' Ñ
m_map(94) = Chr(220) ' Ü
m_map(95) = Chr(167) ' §
m_map(96) = Chr(191) ' ¿
m_map(97) = Chr(97) ' a
m_map(98) = Chr(98) ' b
m_map(99) = Chr(99) ' c
m_map(100) = Chr(100) ' d
m_map(101) = Chr(101) ' e
m_map(102) = Chr(102) ' f
m_map(103) = Chr(103) ' g
m_map(104) = Chr(104) ' h
m_map(105) = Chr(105) ' i
m_map(106) = Chr(106) ' j
m_map(107) = Chr(107) ' k
m_map(108) = Chr(108) ' l
m_map(109) = Chr(109) ' m
m_map(110) = Chr(110) ' n
m_map(111) = Chr(111) ' o
m_map(112) = Chr(112) ' p
m_map(113) = Chr(113) ' q
m_map(114) = Chr(114) ' r
m_map(115) = Chr(115) ' s
m_map(116) = Chr(116) ' t
m_map(117) = Chr(117) ' u
m_map(118) = Chr(118) ' v
m_map(119) = Chr(119) ' w
m_map(120) = Chr(120) ' x
m_map(121) = Chr(121) ' y
m_map(122) = Chr(122) ' z
m_map(123) = Chr(228) ' ä
m_map(124) = Chr(246) ' ö
m_map(125) = Chr(241) ' ñ
m_map(126) = Chr(252) ' ü
m_map(127) = Chr(224) ' à

End Sub

Public Function getChar(ByVal index As Integer) As Char
If index = 27 Then
Throw New ApplicationException("Char 27 maps to extended character table and cannot be accessed directly")
End If
Return m_map(index)
End Function

Public Function getChar27(ByVal index As Integer) As Char
Return m_27map(index)
End Function


End Class





Any questions re: decoding SMS messages otherwise, just ask, there’s not a lot of easy to find info out there so I may do a follow up post.



Lars Pettersson has a great SMS resource over at www.dreamfabric.com/sms/ which is worth checking out too.

3 comments:

Lovebloom said...

how to us this can i have a example
i need to convert a string to 7 bit gsm byts before sumitting on smpp

J-Man said...

These functions are just for the encoding, use like

Dim 7bitMessage as string = convert8bitTo7bit(originalMessage)

which will give you a string encoded as 7-bit. This will then form the message part of the PDU for submission, you need to add the originator number, timestamp etc. to the front of the message - see the dreamfabic link above for the exact format.

Anonymous said...

Awesome stuff man!

Post a Comment