diff --git a/apps/server/lib/prodigy/server/service/logoff.ex b/apps/server/lib/prodigy/server/service/logoff.ex
index d02321f..89480ab 100644
--- a/apps/server/lib/prodigy/server/service/logoff.ex
+++ b/apps/server/lib/prodigy/server/service/logoff.ex
@@ -1,4 +1,4 @@
-# Copyright 2022, Phillip Heller
+# Copyright 2022-2025, Phillip Heller
#
# This file is part of Prodigy Reloaded.
#
@@ -14,75 +14,176 @@
# see .
defmodule Prodigy.Server.Service.Logoff do
- @behaviour Prodigy.Server.Service
@moduledoc """
Handle Logoff requests
"""
+ @behaviour Prodigy.Server.Service
+
require Logger
require Ecto.Query
+ use EnumType
+
import Ecto.Changeset
+ import Ecto.Query, only: [from: 2]
alias Prodigy.Core.Data.{Repo, User}
- alias Prodigy.Server.Protocol.Dia.Packet, as: DiaPacket
+ alias Prodigy.Server.Protocol.Dia.Packet
alias Prodigy.Server.Protocol.Dia.Packet.Fm0
alias Prodigy.Server.Context
alias Prodigy.Server.SessionManager
- defp clear_id_in_use(user_id, reason \\ "normal") do
- user =
- User
- |> Ecto.Query.where([u], u.id == ^user_id)
- |> Ecto.Query.first()
- |> Repo.one()
+ @normal_logoff_dest 0x00D202
+ @disconnect_logoff_dest 0x00D201
+
+ defenum Status do
+ @moduledoc "An enumeration of Logoff service responses"
+
+ value SUCCESS, 0x0
+ end
+
+ @doc """
+ Handles incoming logoff requests
+ """
+ def handle(%Fm0{} = request, %Context{user: nil}) do
+ # No user in context - just send empty response
+ response = build_empty_response(request)
+ {:ok, %Context{}, response}
+ end
+
+ def handle(%Fm0{} = request, %Context{user: user} = context) do
+ request
+ |> parse_request(user)
+ |> and_then(&update_last_logon_time/1)
+ |> and_then(&end_session/1)
+ |> and_then(&determine_result/1)
+ |> and_then(&build_response/1)
+ |> finalize_response(context)
+ end
+ @doc """
+ Handles abnormal disconnection (e.g., connection drop)
+ """
+ def handle_abnormal(nil), do: :ok
+
+ def handle_abnormal(user) do
+ {:ok, %{user: user, reason: :abnormal, request: nil}}
+ |> update_last_logon_time()
+ |> and_then(&end_session/1)
+
+ :ok
+ end
+
+ # Railway-oriented combinator - handles any :ok tuple
+ defp and_then({:error, _} = error, _func), do: error
+ defp and_then(success_tuple, func) when elem(success_tuple, 0) == :ok, do: func.(success_tuple)
+
+ # Pipeline functions
+
+ defp parse_request(request, user) do
+ reason = case request.dest do
+ @disconnect_logoff_dest -> :disconnect
+ @normal_logoff_dest -> :normal
+ _ -> :normal
+ end
+
+ {:ok, %{user: user, reason: reason, request: request}}
+ end
+
+ defp update_last_logon_time({:ok, %{user: user} = state}) do
# Because some pages specifically say "Eastern"
now = Timex.now() |> Timex.Timezone.convert("US/Eastern")
- user
- |> change(%{
- prf_last_logon_date: Timex.format!(now, "{0M}/{0D}/{YYYY}"),
- prf_last_logon_time: Timex.format!(now, "{h24}.{m}")
- })
- |> Repo.update()
-
- # Close the connection
- status = case reason do
- "normal" -> :normal
- "abnormal" -> :abnormal
- _ -> :abnormal
+ last_logon_date = Timex.format!(now, "{0M}/{0D}/{YYYY}")
+ last_logon_time = Timex.format!(now, "{h24}.{m}")
+
+ result = Repo.transaction(fn ->
+ fetch_user_for_update(user.id)
+ |> change(%{
+ prf_last_logon_date: last_logon_date,
+ prf_last_logon_time: last_logon_time
+ })
+ |> Repo.update!()
+ end)
+
+ case result do
+ {:ok, _updated_user} ->
+ {:ok, state}
+ {:error, _reason} ->
+ # Log but don't fail the logoff
+ Logger.warning("Failed to update last logon time for user #{user.id}")
+ {:ok, state}
end
+ end
- SessionManager.close_session(user_id, status)
- Logger.info("User #{user_id} logged off (#{reason})")
- :ok
+ defp end_session({:ok, %{user: user, reason: reason} = state}) do
+ session_status = case reason do
+ :normal -> :normal
+ :disconnect -> :normal
+ :abnormal -> :abnormal
+ end
+
+ SessionManager.close_session(user.id, session_status)
+
+ log_reason = case reason do
+ :disconnect -> "Disconnect"
+ :normal -> "Normal"
+ :abnormal -> "Abnormal"
+ end
+
+ Logger.info("User #{user.id} logged off (#{log_reason})")
+
+ {:ok, state}
end
- def handle(%Fm0{} = request, %Context{user: nil}) do
- {:ok, %Context{}, DiaPacket.encode(Fm0.make_response(<<>>, request))}
+ defp determine_result({:ok, %{reason: reason} = state}) do
+ result = case reason do
+ :disconnect -> :disconnect
+ _ -> :ok
+ end
+
+ {:ok, Map.put(state, :result, result)}
+ end
+
+ defp build_response({:ok, %{request: nil} = state}) do
+ # Abnormal termination - no response needed
+ {:ok, state}
end
- def handle(%Fm0{dest: dest} = request, %Context{user: user}) do
- {:ok, result} =
- Repo.transaction(fn ->
- clear_id_in_use(user.id)
+ defp build_response({:ok, %{request: request} = state}) do
+ # TODO: Determine what this timestamp should actually be
+ response_payload = <>
- case dest do
- 0x00D201 -> :disconnect
- 0x00D202 -> :ok
- end
- end)
+ response = response_payload
+ |> Fm0.make_response(request)
+ |> Packet.encode()
- {result, %Context{auth_timeout: Context.set_auth_timer()},
- DiaPacket.encode(Fm0.make_response(<<0, "xxxxxxxx01011988124510">>, request))}
+ {:ok, Map.put(state, :response, response)}
end
- def handle_abnormal(nil) do
- :ok
+ defp finalize_response({:ok, %{result: result, response: response}}, _context) do
+ new_context = %Context{auth_timeout: Context.set_auth_timer()}
+ {result, new_context, response}
end
- def handle_abnormal(user) do
- clear_id_in_use(user.id, "abnormal")
- :ok
+ defp finalize_response({:ok, _state}, context) do
+ # Abnormal termination path - no response
+ {:ok, context, nil}
+ end
+
+ # Helper functions
+
+ defp fetch_user_for_update(user_id) do
+ from(u in User,
+ where: u.id == ^user_id,
+ lock: "FOR UPDATE"
+ )
+ |> Repo.one!()
+ end
+
+ defp build_empty_response(request) do
+ <<>>
+ |> Fm0.make_response(request)
+ |> Packet.encode()
end
-end
+end
\ No newline at end of file
diff --git a/apps/server/lib/prodigy/server/service/logon.ex b/apps/server/lib/prodigy/server/service/logon.ex
index 5d7c107..3f979da 100644
--- a/apps/server/lib/prodigy/server/service/logon.ex
+++ b/apps/server/lib/prodigy/server/service/logon.ex
@@ -1,4 +1,4 @@
-# Copyright 2022, Phillip Heller
+# Copyright 2022-2025, Phillip Heller
#
# This file is part of Prodigy Reloaded.
#
@@ -14,16 +14,18 @@
# see .
defmodule Prodigy.Server.Service.Logon do
- @behaviour Prodigy.Server.Service
@moduledoc """
Handle Logon requests
"""
+ @behaviour Prodigy.Server.Service
+
require Logger
require Ecto.Query
use EnumType
import Prodigy.Core.Util
+ import Ecto.Query, only: [from: 2]
alias Comeonin.Ecto.Password
alias Prodigy.Core.Data.{Repo, User}
@@ -33,283 +35,304 @@ defmodule Prodigy.Server.Service.Logon do
alias Prodigy.Server.SessionManager
alias Prodigy.Server.Context
+ @supported_versions ["06.03.10", "06.03.17"]
+
defenum Status do
@moduledoc "An enumeration of Logon service responses"
- value SUCCESS, 0x0 do
- @moduledoc false
+ value SUCCESS, 0x0
+ value ENROLL_OTHER, 0x1
+ value ENROLL_SUBSCRIBER, 0x2
+ value BAD_PASSWORD, 0x5
+ value ID_IN_USE, 0x7
+ value BAD_VERSION, 0x9
+ value ACCOUNT_PROBLEM, 0xD
+ end
+
+ @doc """
+ Handles incoming logon requests
+ """
+ def handle(%Fm0{} = request, %Context{auth_timeout: auth_timeout} = context) do
+ request
+ |> parse_request()
+ |> and_then(&validate_version/1)
+ |> and_then(&authenticate_user/1)
+ |> and_then(&validate_user_status/1)
+ |> and_then(&check_enrollment/1)
+ |> and_then(&begin_session/1)
+ |> and_then(&build_response/1)
+ |> finalize_response(context, auth_timeout)
+ end
+
+ # Railway-oriented combinator - handles any :ok tuple
+ defp and_then({:error, _, _} = error, _func), do: error
+ defp and_then(success_tuple, func) when elem(success_tuple, 0) == :ok, do: func.(success_tuple)
+
+ # Pipeline functions
+
+ defp parse_request(%Fm0{payload: <<_, user_id::binary-size(7), pwlen, password::binary-size(pwlen),
+ version::binary-size(8), _rest::binary>>} = request) do
+ {:ok, %{user_id: user_id, password: password, version: version, request: request, user: nil}}
+ end
+
+ defp parse_request(%Fm0{} = request) do
+ {:error, Status.ACCOUNT_PROBLEM, request}
+ end
+
+ defp validate_version({:ok, %{version: version} = state}) when version in @supported_versions do
+ Logger.debug("User is connecting with RS #{version}")
+ {:ok, state}
+ end
+
+ defp validate_version({:ok, %{request: request}}) do
+ Logger.warning("User is connecting with an unacceptable software version")
+ {:error, Status.BAD_VERSION, request}
+ end
+
+ defp authenticate_user({:ok, %{user_id: user_id, password: password, request: request} = state}) do
+ Logger.debug("Retrieving user '#{user_id}' (password: '#{password}')")
+
+ user = fetch_user_with_associations(user_id)
+
+ case user do
+ nil ->
+ Logger.warning("User #{user_id} attempted to logon, but does not exist in the database")
+ {:error, Status.BAD_PASSWORD, request}
+
+ user ->
+ case verify_password(user, password) do
+ :ok ->
+ {:ok, Map.put(state, :user, user)}
+ :error ->
+ Logger.warning("User #{user.id} attempted logon, but failed authentication")
+ {:error, Status.BAD_PASSWORD, request}
+ end
end
+ end
+
+ defp validate_user_status({:ok, %{user: user, request: request} = state}) do
+ cond do
+ user_deleted?(user) ->
+ Logger.debug("User is deleted")
+ {:error, Status.ACCOUNT_PROBLEM, request}
+
+ not household_active?(user) ->
+ Logger.debug("Household inactive")
+ {:error, Status.ACCOUNT_PROBLEM, request}
- value ENROLL_OTHER, 0x1 do
- @moduledoc false
+ true ->
+ {:ok, state}
end
+ end
+
+ defp check_enrollment({:ok, %{user: user} = state}) do
+ normalized_user_id = String.upcase(user.id)
+
+ enrollment = cond do
+ user.date_enrolled != nil ->
+ Logger.debug("User is enrolled")
+ :enrolled
- value ENROLL_SUBSCRIBER, 0x2 do
- @moduledoc false
+ String.ends_with?(normalized_user_id, "A") ->
+ Logger.debug("Subscriber is unenrolled")
+ :enroll_subscriber
+
+ true ->
+ Logger.debug("Household member is unenrolled")
+ :enroll_other
end
- value BAD_PASSWORD, 0x5 do
- @moduledoc false
+ {:ok, Map.put(state, :enrollment, enrollment)}
+ end
+
+ defp begin_session({:ok, %{user: user, enrollment: enrollment, version: version, request: request} = state}) do
+ session_status = case enrollment do
+ :enrolled -> :success
+ :enroll_subscriber -> :enroll_subscriber
+ :enroll_other -> :enroll_other
end
- value ID_IN_USE, 0x7 do
- @moduledoc false
+ case SessionManager.create_session(user, session_status, version) do
+ {:ok, session} ->
+ status = case enrollment do
+ :enrolled -> Status.SUCCESS
+ :enroll_subscriber -> Status.ENROLL_SUBSCRIBER
+ :enroll_other -> Status.ENROLL_OTHER
+ end
+ {:ok, state |> Map.put(:status, status) |> Map.put(:session_id, session.id)}
+
+ {:error, :concurrency_exceeded} ->
+ Logger.warning("User #{user.id} attempted logon, but exceeded concurrency limit")
+ {:error, Status.ID_IN_USE, request}
end
+ end
+
+ defp build_response({:ok, %{status: status, user: user, session_id: session_id, request: request} = state}) do
+ response = make_response_payload({status, user, session_id})
+ |> Fm0.make_response(request)
+ |> Packet.encode()
+
+ {:ok, Map.put(state, :response, response)}
+ end
- value BAD_VERSION, 0x9 do
- @moduledoc false
+ defp finalize_response({:ok, %{status: status, user: user, session_id: session_id, version: version, response: response}}, _context, auth_timeout) do
+ Context.cancel_auth_timer(auth_timeout)
+
+ log_message = case status do
+ Status.SUCCESS -> "logged on (Normal)"
+ Status.ENROLL_SUBSCRIBER -> "logged on (Enroll Subscriber)"
+ Status.ENROLL_OTHER -> "logged on (Enroll Other)"
end
- value ACCOUNT_PROBLEM, 0xD do
- @moduledoc false
+ Logger.info("User #{user.id} #{log_message}")
+
+ new_context = %Context{
+ user: user,
+ session_id: session_id,
+ rs_version: version
+ }
+
+ {:ok, new_context, response}
+ end
+
+ defp finalize_response({:error, status, request}, context, _auth_timeout) do
+ response = make_response_payload({status, nil, nil})
+ |> Fm0.make_response(request)
+ |> Packet.encode()
+
+ {:error, context, response}
+ end
+
+ # Helper functions
+
+ defp fetch_user_with_associations(user_id) do
+ from(u in User,
+ where: u.id == ^user_id,
+ preload: [:household, :data_collection_policy]
+ )
+ |> Repo.one()
+ end
+
+ defp verify_password(user, password) do
+ # TODO: Remove this once tooling supports creating users with initial password hashed
+ encrypted_pw = if String.starts_with?(user.password, "$pbkdf2-sha512$") do
+ user.password
+ else
+ Pbkdf2.hash_pwd_salt(user.password)
+ end
+
+ if Password.valid?(password, encrypted_pw) do
+ Logger.debug("Retrieved user with matching password")
+ :ok
+ else
+ :error
end
end
- def make_response_payload({Status.SUCCESS, user, _session_id}) do
- now = Calendar.DateTime.now_utc()
- # TODO refactor to use Timex
- date = now |> Calendar.strftime("%m%d%y")
- # TODO refactor to use Timex
- time = now |> Calendar.strftime("%H%M%S")
+ defp user_deleted?(user) do
+ user.date_deleted != nil
+ end
- Logger.debug("user record: #{inspect(user, limit: :infinity)}")
- new_mail_indicator = bool2int(Messaging.unread_messages?(user))
+ defp household_active?(user) do
+ user.household.disabled_date == nil and user.household.enabled_date != nil
+ end
- data_collection =
- cond do
- Ecto.assoc_loaded?(user.data_collection_policy) == false ->
- <<0::32>>
-
- user.data_collection_policy == nil ->
- <<0::32>>
-
- true ->
- # these constants may not be entirely right - see rs-rsk-c.pdf, page # 124
- _dc = user.data_collection_policy
-
- <<
- 1::1,
- 0::3,
- bool2int(user.data_collection_policy.ad)::1, # 'A'
- bool2int(user.data_collection_policy.pwindow)::1, # 'W'
- bool2int(user.data_collection_policy.element)::1, # 'E'
- bool2int(user.data_collection_policy.template)::1, # 'T'
-
- # function codes
- bool2int(user.data_collection_policy.exit)::1, # 'E'
- bool2int(user.data_collection_policy.undo)::1, # 'U'
- bool2int(user.data_collection_policy.path)::1, # 'P'
- bool2int(user.data_collection_policy.help)::1, # 'H'
- bool2int(user.data_collection_policy.jump)::1, # 'J'
- bool2int(user.data_collection_policy.back)::1, # 'B'
- bool2int(user.data_collection_policy.next)::1, # 'N'
- bool2int(user.data_collection_policy.commit)::1, # 'C'
- 0::6,
- bool2int(user.data_collection_policy.action)::1, # 'A'
- bool2int(user.data_collection_policy.look)::1, # 'L'
- 0::8
- >>
- end
+ # Response payload building
- last_logon_date = val_or_else(user.prf_last_logon_date, " ")
- last_logon_time = val_or_else(user.prf_last_logon_date, " ")
+ def make_response_payload({Status.SUCCESS, user, _session_id}) do
+ {date, time} = get_formatted_datetime()
+ new_mail_indicator = bool2int(Messaging.unread_messages?(user))
+ data_collection = build_data_collection_bits(user)
+ {last_logon_date, last_logon_time} = get_last_logon_info(user)
- res = <<
+ <<
0x0,
- "M", # TODO set PRF_GENDER from user record, N.B. nil breaks encoding
+ "M", # TODO: set PRF_GENDER from user record
data_collection::binary,
0x0, # unknown
0x0, # PRF_BUSINESS_CLIENT_CODE
0x0, # PRF_USER_SECURITY_LEVEL
0x0, # PRF_AUTO_SKIP
0x0, # PRF_GAME_PROFILE
- date::binary-size(6)-unit(8), # SYS_DATE
- time::binary-size(6)-unit(8), # SYS_TIME
+ date::binary-size(6),
+ time::binary-size(6),
new_mail_indicator,
0x0::16, # PRF_CUG_ID
0x0::16, # ??
0x0::16, # PRF_CUG_SERVICE_ID
- 0x0::unsigned-integer-size(16), # PRF_USER_CLASS
-
- # Additional information expected by TLPEADDS
+ 0x0::16, # PRF_USER_CLASS
0::16, # PRF_TIER_OF_SERVICE
0::16, # PRF_CCL_VERSION_NUM
0, # PRF_SEL_NUM_ITEMS
0,
0::16,
- last_logon_date::binary-size(8), # PRF_LAST_LOGON_DATE
- last_logon_time::binary-size(5) # PRF_LAST_LOGON_TIME
- # PRF_SEL_ITEM_LIST::2
+ last_logon_date::binary-size(8),
+ last_logon_time::binary-size(5)
>>
-
- Logger.debug("logon response: #{inspect(res, base: :hex, limit: :infinity)}")
- res
end
def make_response_payload({status, _, _}) do
- now = Calendar.DateTime.now_utc()
- date = now |> Calendar.strftime("%m%d%y")
- time = now |> Calendar.strftime("%H%M%S")
+ {date, time} = get_formatted_datetime()
<<
status.value(),
- 0x0::unsigned-integer-size(80),
- date::binary-size(6)-unit(8), # SYS_DATE
- time::binary-size(6)-unit(8), # SYS_TIME
- 0x0::unsigned-integer-size(56)
+ 0x0::80,
+ date::binary-size(6),
+ time::binary-size(6),
+ 0x0::56
>>
end
- defp get_user(user_id, password) do
- # TODO need to check the household if user_id ends in A and there is no User record
- user =
- User
- |> Ecto.Query.where(id: ^user_id)
- |> Ecto.Query.first()
- |> Ecto.Query.preload([:household])
- |> Ecto.Query.preload([:data_collection_policy])
- |> Repo.one()
-
- Logger.debug("retrieving user '#{user_id} (password: '#{password}')")
- Logger.debug("#{inspect(user)}")
-
- if user == nil do
- Logger.warning("User #{user_id} attempted to logon, but does not exist in the database")
- :bad_password
- else
- # TODO remove this once tooling supports creating users with initial password hashed
- encrypted_pw =
- case String.starts_with?(user.password, "$pbkdf2-sha512$") do
- true -> user.password
- false -> Pbkdf2.hash_pwd_salt(user.password)
- end
-
- case Password.valid?(password, encrypted_pw) do
- true ->
- Logger.debug("retrieved user with matching password")
- {:ok, user}
-
- false ->
- Logger.warning("User #{user_id} attempted logon, but failed authentication")
- :bad_password
- end
- end
+ defp get_formatted_datetime do
+ now = Calendar.DateTime.now_utc()
+ date = Calendar.strftime(now, "%m%d%y")
+ time = Calendar.strftime(now, "%H%M%S")
+ {date, time}
end
- defp enrolled(user) do
- normalized_user_id = String.upcase(user.id)
-
- cond do
- # TODO check date_enrolled is today or prior
- user.date_enrolled != nil ->
- Logger.debug("User is enrolled")
- true
-
- String.ends_with?(normalized_user_id, "A") ->
- Logger.debug("Subscriber is unenrolled")
- {:enroll_subscriber, user}
-
- true ->
- Logger.debug("Household member is unenrolled")
- {:enroll_other, user}
- end
+ defp get_last_logon_info(user) do
+ last_logon_date = val_or_else(user.prf_last_logon_date, " ")
+ last_logon_time = val_or_else(user.prf_last_logon_time, " ")
+ {last_logon_date, last_logon_time}
end
- defp household_active(user) do
- # TODO check enabled_date is today or earlier and disabled_date is nil or after today
- if user.household.disabled_date == nil and user.household.enabled_date != nil do
- true
- else
- Logger.debug("household inactive")
- :account_problem
+ defp build_data_collection_bits(user) do
+ policy = cond do
+ not Ecto.assoc_loaded?(user.data_collection_policy) -> nil
+ true -> user.data_collection_policy
end
- end
- defp deleted(user) do
- # TODO check that date_deleted is nil or after today
- if user.date_deleted == nil do
- false
- else
- Logger.debug("User is deleted")
- :account_problem
- end
- end
+ case policy do
+ nil ->
+ <<0::32>>
- defp version_ok(version) do
- if version in ["06.03.10", "06.03.17"] do
- Logger.debug("User is connecting with RS #{version}")
- true
- else
- Logger.warning("User is connecting with an unacceptable software version")
- :bad_version
+ policy ->
+ encode_data_collection_policy(policy)
end
end
- def handle(
- %Fm0{
- payload:
- <<_, user_id::binary-size(7)-unit(8), pwlen, password::binary-size(pwlen)-unit(8),
- version::binary-size(8)-unit(8), _rest::binary>>
- } = request,
- %Context{auth_timeout: auth_timeout} = context
- ) do
- result =
- with true <- version_ok(version),
- {:ok, user} <- get_user(user_id, password),
- false <- deleted(user),
- true <- household_active(user),
- enrollment_status <- enrolled(user) do
-
- # create context based on enrollment status
- status = case enrollment_status do
- true -> :success
- {:enroll_subscriber, _} -> :enroll_subscriber
- {:enroll_other, _} -> :enroll_other
- end
-
- case SessionManager.create_session(user, status, version) do
- {:ok, db_session} ->
- case enrollment_status do
- true -> {Status.SUCCESS, user, db_session.id}
- {:enroll_subscriber, user} -> {Status.ENROLL_SUBSCRIBER, user, db_session.id}
- {:enroll_other, user} -> {Status.ENROLL_OTHER, user, db_session.id}
- end
- {:error, :concurrency_exceeded} ->
- Logger.warning("User #{user.id} attempted logon, but exceeded concurrency limit")
- {Status.ID_IN_USE, nil, nil}
- end
- else
- :bad_version -> {Status.BAD_VERSION, nil, nil}
- :bad_password -> {Status.BAD_PASSWORD, nil, nil}
-# {:enroll_subscriber, user} -> {Status.ENROLL_SUBSCRIBER, user}
-# {:enroll_other, user} -> {Status.ENROLL_OTHER, user}
- :id_in_use -> {Status.ID_IN_USE, nil, nil}
- _ -> {Status.ACCOUNT_PROBLEM, nil, nil}
- end
-
- response =
- make_response_payload(result)
- |> Fm0.make_response(request)
- |> Packet.encode()
-
- case result do
- {Status.SUCCESS, user, session_id} ->
- Context.cancel_auth_timer(auth_timeout)
- Logger.info("User #{user_id} logged on (Normal)")
- {:ok, %Context{user: user, session_id: session_id, rs_version: version}, response}
-
- {Status.ENROLL_SUBSCRIBER, user, session_id} ->
- Context.cancel_auth_timer(auth_timeout)
- Logger.info("User #{user_id} logged on (Enroll Subscriber)")
- {:ok, %Context{user: user, session_id: session_id, rs_version: version}, response}
-
- {Status.ENROLL_OTHER, user, session_id} ->
- Context.cancel_auth_timer(auth_timeout)
- Logger.info("User #{user_id} logged on (Enroll Other)")
- {:ok, %Context{user: user, session_id: session_id, rs_version: version}, response}
-
- _ ->
- {:error, context, response}
- end
+ defp encode_data_collection_policy(policy) do
+ <<
+ 1::1,
+ 0::3,
+ bool2int(policy.ad)::1, # 'A'
+ bool2int(policy.pwindow)::1, # 'W'
+ bool2int(policy.element)::1, # 'E'
+ bool2int(policy.template)::1, # 'T'
+
+ # Function codes
+ bool2int(policy.exit)::1, # 'E'
+ bool2int(policy.undo)::1, # 'U'
+ bool2int(policy.path)::1, # 'P'
+ bool2int(policy.help)::1, # 'H'
+ bool2int(policy.jump)::1, # 'J'
+ bool2int(policy.back)::1, # 'B'
+ bool2int(policy.next)::1, # 'N'
+ bool2int(policy.commit)::1, # 'C'
+ 0::6,
+ bool2int(policy.action)::1, # 'A'
+ bool2int(policy.look)::1, # 'L'
+ 0::8
+ >>
end
-end
+end
\ No newline at end of file