Example of registrar module

-- Copyright (c) 2008 Xeepe project http://xeepe.com
-- Under the terms of the MIT License
-- http://www.opensource.org/licenses/mit-license.html

local r = require"resip"
local DebugLog = r.DebugLog
local InfoLog  = r.InfoLog
local WarnLog  = r.WarnLog
local ErrLog   = r.ErrLog

local app = require"app"

module(..., package.seeall)
--module(...)

--in memory registration database ;-)
db = {}
function getRegDatabase()
    return db
end
--object passed to this module from main application
--exposes basic interface function like send()
local stack

-- headers we use, make local for fast access
local h = r.h
local h_Contacts = h.Contacts
local h_To = h.To
local h_Expires = h.Expires
local h_Authorizations = h.Authorizations
-- parameters we use, make local for fast access
local p = r.p
local p_username = p.username
local p_realm = p.realm
local p_expires = p.expires

local DEFAULT_EXPIRE = 3600

local function updateContact(aorString,uri,expires)
    InfoLog( "updateContact: aor:",aorString,"contact:",uri,"expires:",expires)
    local contacts = db[aorString]
    if not contacts then
        DebugLog( "No existing contact, create new record:" )
        contacts = r.newNameAddrs()
        local newContact = r.newNameAddr()
        newContact:uri():set(uri)
        newContact:paramset(p_expires,expires)
        contacts:push_back( newContact )
        db[aorString] = contacts
        DebugLog( tostring(db[aorString]))
        return
    end
    local find,err = contacts:iterate( function(item)
        if item:uri() == uri then
            item:paramset(p_expires,expires)
            return  true
        end
    end)
    if not find and not err then
        local newContact = r.newNameAddr()
        newContact:uri(uri)
        newContact:paramset(p_expires,expires)
        contacts:push_back( newContact )
    end
    if err then
        ErrLog( err )
    end
end

local function removeContact(aorString,uri)
    InfoLog( "removeContact: aor: "..tostring(aor).." contacts: "..tostring(uri))
    local contacts = db[aorString]
    if not contacts then
        return
    end
    local updatedContact = contacts:clone()
    local find,err = contacts:iterate( function(item)
        if item:uri() ~= uri then
            updatedContact:push_back(item)
        end
    end)
    if updatedContact:size() == 0 then
        db[aorString] = nil
    else
        db[aorString] = updatedContacts
    end
end

--in contacts - with absolute expires values (as stored within db)
--in now current time
--return contacts with relative expires
local function fixExpires(from,to,now)
    to:clear()
    from:iterate( function(item)
        local e = tonumber(item:param(p_expires):value()) -- now
        local newContact = r.newNameAddr()
        newContact:uri():set(item:uri())
        newContact:paramset(p_expires,e)
        to:push_back( newContact )
    end )
end

-- return response to send if we must stop processing
-- return nil otherwise
function authenticate(msg)
    --don't check for ACK,BYE - it is for registrar!
    InfoLog("authenticate")
    if msg:exists(h_Authorizations) then
        local auths = msg:header(h_Authorizations)
        -- find our auth
        local auth,err = auths:iterate( function(item)
            tostring(item) --ToDo - workaround about access to parameters
            if app.isMyDomain( item:param(p_realm):value() ) then
                return item
            end
        end)
        if err then
            return r.makeResponse(msg,500)
        end
        if auth then
            InfoLog("authenticate found our auth")
            local user = auth:param(p_username):value()
            --may username be empty??
            local a1 = app.getA1(user)
            if not a1 then
                return r.makeResponse( msg, 403 )
            end
            InfoLog("authenticate 1")
            local result = r.advancedAuthenticateRequest(msg,app.getMyRealm(),a1,600)
            InfoLog("authenticate 2")
            if result == 1 then --Failed
                return r.makeResponse(msg, 403, "Authentication Failed")
            elseif result == 2 then --Authenticated
                return
            elseif result == 3 then --Expired
                return r.makeChallenge(msg, app.getMyRealm(),
                    false,  --useAuth
                    true ) --stale
            elseif result == 4 then --BadlyFormed
                return r.makeResponse(msg, 403, "Badly formed")
            else
                ErrLog( "advancedAuthenticateRequest return wrong value: "..result )
                return r.makeResponse(msg,500)
            end
        end
    end
    return r.makeChallenge(msg, app.getMyRealm(), false)
end

function processSipMessage( msg )
    InfoLog( "processSipMessage" )
    if msg:method() ~= "REGISTER" or msg:isResponse() then
        return
    end
    assert( msg:isRequest() )
    local aor = msg:header(h_To):uri():getAorAsUri()
    local aorString = tostring( aor )
    if aor:scheme() ~= "sip" then
        local failure = r.makeResponse( msg, 400 )
        stack:send(failure)
        return
    end
    local response = authenticate(msg)
    if response then
        stack:send(response)
        return
    end
    local globalExpire
    if msg:exists(h_Expires) and msg:header(h_Expires):isWellFormed() then
        globalExpire = msg:header(h_Expires):value()
    else
        globalExpire = DEFAULT_EXPIRE
    end
    local now = os.time()
    if not msg:exists(h_Contacts) then
        --Query
        local ok = r.makeResponse(msg,200)
        if db[aorString] then
            fixExpires(db[aorString],ok:header(h_Contacts),now)
            stack:send(ok)
        end
        stack:send(ok)
        return
    end
    local contacts = msg:header(h_Contacts)
    local done, err = contacts:iterate( function( item )
        DebugLog( "iterate: "..tostring(item) )
        if not item:isWellFormed() then
            local failure = r.makeResponse( msg, 400, "Mailformed contact" )
            stack:send(failure)
            return 1
        end
        local expires
        if item:exists(p_expires) then
            expires = item:param(p_expires):value()
        else
            expires = globalExpire
        end
        --Check for "Contact: *" style deregistration
        if item:isAllContacts() then
            if contacts:size() > 1 or expires ~= 0 then
                local failure = r.makeResponse( msg, 400, "Invalid use of 'Contact: *'" )
                stack:send(failure)
                return 1
            end
            db[aorString] = nil
            return
        end
        if expires == 0 then
            removeContact(aorString,item:uri())
            return
        end
        updateContact(aorString,item:uri(),expires+now)
    end )
    if done then
        InfoLog("stop")
        assert( not err ) --ToDo - send 500??!!
        return
    end
    local ok = r.makeResponse(msg,200)
    if db[aorString] then
        fixExpires(db[aorString],ok:header(h_Contacts),now)
    else
        ok:remove(h_Contacts)
    end
    stack:send(ok)
end

function processAppMessage(msg)
    ErrLog( "processAppMessage: Why we are here?" )
end

function processTermMessage( msg )
end

function init( stack_, params )
    stack = stack_
end

function fini()
end